Java

Table of Contents

1 基本语法

1.1 标签跳转

label break/continue似乎非常必要,这种情况尤其见于多重循环,如果想直接调到最外层循环(break),或者是从外层循环继续执行(continue).缺少这种机制的话,外层循环只能够使用蹩脚的标记,一层层地逐层跳出。

class X{
  public static void main(String[] args){
    int i=0;
    int j=0;
 out:
    for(i=0;i<10;i++){
      for(j=0;j<10;j++){
        if((i+j)==15){
          break out;
        }
      }
    }
    System.out.println(i+","+j);
  }
}

1.2 final

final关键字应该是有下面几个用途:

  1. 如果放在class之前的话,那么这个class则不允许被继承。
  2. 如果放在method之前的话,那么这个method则不允许被override.
  3. 如果放在field之前的话,那么这个field则不允许被修改。(但是因为java出了基本类型之外,其他类型都是对象类似于C++指针。这里不允许被修改,是指指针本身不允许修改,而对于指针所指对象是允许被修改的)

1.3 assert

java关键字assert可以用来进行断言,和C++一样。但是天煞的java虚拟机必须指定标记才会开启assert特性。java -ea断言才会生效。java -ea:package_name…可以指定名字空间下所有类断言打开,java -ea:classname可以指定某个类断言打开。如果不带任何参数的话那么是将所有断言打开。同样使用-da可以禁用某个特定类或者是包的断言。 #note: 其实想想觉得这个还是很不错的,在运行时控制而不是在编译时控制,会让更多的人喜欢使用assertion.但是我觉得默认的话,应该是开启的除非显示关闭。

java断言可以指定检查表达式以及出错表达式,asser 条件:表达式

class X{
  public static void main(String[] args) {
    assert 0==1 : "omg";
  }
}

有些类不是由类加载器加载,而是直接由虚拟机加载。使用-ea/-da不能够应用到这些类上面。对于系统类来说,需要使用-esa/-dsa来控制断言。

1.4 位移操作

java提供了三种位移操作符:

  1. 逻辑右移 >> (保持高位)
  2. 逻辑左移 <<
  3. 算术右移 >>> (丢弃高位用0填充)

注意java只提供了有符号整数

class X{
  public static void main(String[] args){
    int x=(1 << 31);
    assert((x >>> 1) == (1 << 30));
    assert((x >> 1) == ((1 << 31) | (1 << 30)));
    assert((x << 1) == 0);
  }
}

2 面向对象

2.1 构造和析构

java相对于C++来说,在对象构造上面,需要多考虑初始化块这个概念(包括静态初始化块)。所谓初始化块,可以在对象执行构造函数之前执行的一块代码。而静态初始化块,当引用到这个类的时候第一次就会执行。有了这个特性之后,我们就可以创建不需要使用main就可以运行的例子

class App {
  static {
    System.out.println("hello,world");
    System.exit(0);
  }
}

另外相对于C++来说,java的字段都可以通过简单的赋值就完成初始化,而不需要像C++在构造函数后面接上一推init variable list.

整个java对象构造过程大致如下:

  1. 对象加载时,按照声明顺序,初始化静态字段,以及执行静态初始化块。
  2. 对象创建时,按照声明顺序,初始化字段,以及执行初始化块。
  3. 执行对象的构造函数。

对于构造函数来说,如果需要调用父类构造函数可以使用super(…),如果需要调用同类内部其他重载版本可以使用this(…)

java提供了一个finalize方法,但是这个方法并不是在析构时候执行,而是在被GC之前执行,但是你很难知道这个对象什么时候会被GC.因此最好不要复写这个方法。如果想在GC之前做一些事情的话,可以通过Runtime.addShutdownHook添加钩子来在GC之前触发。

2.2 内部类

引入内部类(inner class)主要有下面三个原因:

  1. 内部类可以访问该类定义所在的作用域中数据,包括私有数据。
  2. 内部类可以对同一个包中的其他类隐藏起来。
  3. 当想要定义一个回调函数且不想编写大量代码时,使用匿名类(anonymous)比较便捷。

关于java的内部类大概有这么几种:

  1. 内部类。(可以访问到外围类实例)
  2. 静态内部类。(C++嵌套类和静态内部类更相似)
  3. 局部类。(通常在方法内使用,可以访问到外围类实例以及方法中final参数)
  4. 匿名内部类。(局部类一种特例,方便做一个接口简单扩展)

2.2.1 内部类

class X{
  private int x=1;
  class Y{
    void foo(){
      System.out.println(x);
    }
  }
  public static void main(String[] args){
    X x=new X();
    Y y=x.new Y();
    y.foo();
  }
}

内部类生成class使用$分隔,所以可以看到X$Y.class文件。可以看到在Y里面访问x字段。原理非常简单,在Y内部生成了X的一个实例指针,同时在X里面为x字段提供了一个静态访问方法。

class X extends java.lang.Object{
    X();
    public static void main(java.lang.String[]);
    static int access$000(X); // 在X中静态访问方法
}

class X$Y extends java.lang.Object{
    final X this$0; // 在Y里面提供了外围实例指针
    X$Y(X);
    void foo();
}

了解了这些之后对于x.new Y()这样的语法就好理解了。我们首先需要一个外围实例,才能够构造Y对象出来。

2.2.2 静态内部类

但是并不是所有内部类都需要访问外围实例的。如果没有这样需求的话,我们就可以使用静态内部类static class Y.可以使用X.Y进行引用。

2.2.3 局部类

局部类是在方法中定义的内部类,生成类的规则就是X$1Y.class.1使用数字来标记区分不同的方法。

class X{
  private int x=1;
  void foo(final int y){
    class Y{
      void foo(int z){
        System.out.println(x+","+y+","+z);
      }
    }
    Y iy=new Y();
    iy.foo(20);
  }
  public static void main(String[] args){
    X x=new X();
    x.foo(10);
  }
}

这里要求参数为final原因很简单。因为局部类需要将这个参数在构造的时候就拿过来放在自己类中。final的话语义上会比较好理解。可以看看生成class内容

class X$1Y extends java.lang.Object{
    final int val$y; // 这里将外部y捕获。
    final X this$0;
    X$1Y(X, int); // 构造函数传入y
    void foo(int);
}

2.2.4 匿名类

匿名类编写回调或者是特定的接口扩展非常方便,当然也可以容易地扩展一个类。

class X{
  public static void main(String[] args) throws InterruptedException {
    Thread y=new Thread() { // 这个地方需要传入基类的构造参数。
        public void run() {
          for(int i=0;i<10;i++){
            System.out.println("run...");
          }
        }
      };
    y.start();
    y.join();
  }
}

生成的类名称为X$1.class.其中1是数字用来区别匿名类。注意匿名类都是final的。

final class X$1 extends java.lang.Thread{
    X$1();
    public void run();
}

2.3 访问修饰符

java有下面4个访问修饰符可以用来控制可见性:

  1. private:仅对本类可见。
  2. public:对所有类可见。
  3. protected:对本包和所有子类可见。
  4. 默认:对本包可见。

访问修饰符可以作用在类,方法以及字段上面,控制可见性效果是相同的。

2.4 静态导入

所谓静态导入,就是可以导入某个类下面的静态方法以及静态域,通常来说这样可以使得代码更容易阅读,比如

import static java.lang.Math.*;
class App {
  public static void main(String[] args){
    // System.out.println(Math.sqrt(Math.pow(3,2)+Math.pow(4,2)));
    System.out.println(sqrt(pow(3,2)+pow(4,2)));
  }
}

2.5 equals编写

  1. 对于参数必须是Object arg. boolean equals(Object arg)
  2. 检测两个对象是否相同,可以节省判断开销。if(this == arg) return true;
  3. 判断arg是否为null. if(arg == null) return false;
  4. 如果要求判断两者类型必须相同,那么通过getClass判断Class对象是否相同。if(getClass() != arg.getClass()) return false;
  5. 如果仅仅是想在语义上判断相同的话,那么使用instanceof判断。通常情况是,好比A,B都是容器实现,B extends A.只不过B是A另外一种实现。对于AB来说他们hold数据都是相同的。这种情况下面就是语义的判断相同。可以通过arg instanceof A.class来判断是否为A子类。
  6. 转换成为相同类型之后逐个比较字段。

2.6 import顺序

有时候import顺序还是比较重要的,比如下面这个程序com/dirlt/X.java

/* coding:utf-8
 * Copyright (C) dirlt
 */

package com.dirlt;
import com.dirlt.X.B.A;
import java.util.ArrayList;

public class X{
  public static class B extends ArrayList {
    public class A{
    }
  }
}

编译会出现如下问题

➜  ~  javac com/dirlt/X.java
com/dirlt/X.java:10: cannot find symbol
symbol  : class ArrayList
location: class com.dirlt.X
  public static class B extends ArrayList {
                                ^
1 error

这个import顺序intellj认为是正确的,而且只需要反转两个import的顺序就可以正常编译。

我不太理解java的导入顺序,但是猜想和C++的include非常类似,出现上面的问题可能就是循环依赖导致的问题

  • 当我们引入com.dirlt.X.B.A的时候,javac会去分析这个文件X.java(or X.class)
  • 因为引入的是B下面的子类,因此肯定需要分析B这个类
  • 而B继承ArrayList这个类,但是javac在当前的名字空间下面找不到ArrayList所以报错

解决这个问题最好的办法,我觉得应该就是:对于文件内部本身的类,不要使用import来导入,直接使用全称即可。

3 面向泛型

#todo:

4 JDK

4.1 浮点运算

float类型数值常量后面加上F比如3.042F,而double类型数值常量后面加上D比如3.402D.所有浮点数值计算都遵循IEEE 752规范。java提供了三种表示溢出或者计算错误的三种特殊浮点数值:

  1. 正无穷大 Double.POSITIVE_INFINITY
  2. 负无穷大 Double.NEGATIVE_INFINITY
  3. NaN(不是数字) Double.NaN. 浮点数/0的话就会得到NaN.判断是否为NaN不应该使用==因为和一个NaN比较始终都是false,而应该使用Double.isNaN(x)

对于较大浮点数应该使用BigDecimal来进行计算。

java虚拟机规范强调可移植性,对于在任何机器上来说相同的程序得到的结果应该是相同的。但是对于浮点计算的话,比如Intel CPU针对于浮点数计算所有中间结果都使用bit 80表示,而最后截取bit 64,造成和其他CPU计算结果不同。为了达到可移植性,java规范所有中间结果必须使用bit 64截断,但是遭反对,因此java提供了strictfp关键字标记某个方法,对于这个方法里面所有浮点数计算,所有中间结果使用64 bit截断,否则使用适合native方式计算。另外一些浮点数计算比如pow2,pow3,sqrt的话,一方面依赖于CPU浮点计算方式,另外一方面依赖于本身算法(如果CPU本身提供这种指令的话就可以使用CPU指令),也会造成不可移植性,比如Math.sqrt.如果希望在这方面也达到同样效果的话,可以使用StrictMath类,底层使用fdlibm,以确保所有平台上得到相同的结果。

4.2 Date & Calendar

其实一开始Date是想做成日历的。所谓日历就是说能够处理年月日这些信息。但是Date本身处理比较差,没有考虑闰秒这种东西,另外因为日历仅仅是历法其中的一种,虽然广泛使用。因此有必要将历法单独形成一个类称为Calendar,而日历是历法的一种实现在Java里面是GregorianCalendar.而现在Date仅仅用于保存一个绝对的时间点就是时刻,保存的方法就是相对于某一固定时间点的毫秒数,而这个时间点就称为纪元(epoch),它是UTC 1970.1.1 00:00:00。

因此我们在比较时刻方面的话,可以使用Date,而在处理历法方面的话需要使用GregorianCalendar.

4.3 Exception

java里面异常都是派生于Throwable,但是分解成为两个分支:

  1. Error.描述Java运行时系统的内部错误和资源耗尽。应用程序不应该抛出该类型对象。
  2. Exception.分解为RuntimeException(运行时异常)和其他(编译时异常)。

RuntimeException包括下面几种情况:

  • 错误类型转换。
  • 数组访问越界。
  • 访问空指针。

java语言规范将派生于Error或者是RuntimeException的所有异常称为未检查异常(unchecked exception),而将所有其他异常(也就是编译时异常)称为已检查异常(checked).称为已检查异常原因是因为,java的异常规格也是作为函数声明的一部分的。因此如果方法foo抛出异常X,那么调用foo的方法,要么检查异常X,要么就在自己的规则里面写上throws X传给上层处理,无论如何你都是需要面对这个异常的,所以称为已检查。

  • 抛出异常非常简单,使用new Exception()即可
  • 创建异常的话继承Throwable即可,构造参数可以传入message表示这个异常的详细信息。
  • 如果重新抛出异常的话会将异常链断开,可以通过调用initCause将原始的cause保存起来,getCause可以取出。这样可以保持异常链完整信息。

4.4 StackTrace

  • 使用Thread.getStackTrace获得某个线程的堆栈信息
  • 使用Thread.getAllStackTrace可以获得所有线程的堆栈信息
  • 异常对象可以使用e.printStackTrace打印堆栈信息

4.5 Proxy

使用代理可以动态地生成一些类或者是接口(但是不是动态生成代码)。创建一个代理对象,使用Proxy类的newProxyInstance方法,有下面三个参数:

  1. 类加载器(class loader).null表示使用默认加载器。
  2. class对象数组。表示想实现的接口。
  3. 调用处理器(invocation handler)。可以截获方法调用然后做代理。

调用处理器接口为Object invoke(Object proxy, Method method, Object… args).其中proxy表示代理对象本身,method,args表示调用方法以及参数。

import java.util.logging.*;
import java.lang.reflect.*;
class X{
  public static void main(String[] args) throws InterruptedException {
    final Runnable r=new Runnable() {
        public void run() {
          for(int i=0;i<10;i++){
            System.out.println("run...");
          }
        }
      };
    Runnable proxy=(Runnable)Proxy.newProxyInstance(r.getClass().getClassLoader(),new Class[]{Runnable.class}, new InvocationHandler() {
        public Object invoke(Object proxy, Method m, Object[] args){
          System.out.println("entering...");
          try {
            return m.invoke(r,args);
          } catch(Exception ex){
            return null;
          }
        }
      });
    Thread t=new Thread(proxy);
    t.start();
    t.join();
  }
}
  • java没有定义代理类的名字,sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。
  • 对于特定的类加载器和预设的一组接口来说,只能够有一个代理类。也就是说,如果使用同一个类加载器刚和接口数组调用newProxyInstance方法两次的话,那么只能够得到同一个类的两个对象。
  • 可以使用Proxy.getProxyClass获得对应代理类,通过Proxy.isProxyClass判断某个类是否为代理类。

4.6 Class

Class类本身表示这个类的一些元信息。通常拿到这个类的元信息之后,就可以完成一些动态事情比如反射。java有三种方式可以获得Class类:

  1. 对象调用getClass()方法。
  2. 字面量直接获取 App.class
  3. 通过类名动态查找 Class.forName("java.util.Date")

获得Class之后,就可以获取到这个class内部:

  1. fields
  2. methods
  3. constructors

这样就可以开始做一些反射工作了。#todo: more about reflection

4.7 Reflection

4.8 Runnable & Thread

线程包括下面6种状态,并且切换关系如下:

  1. new 线程创建好并且分配资源但是没有运行,调用start进入runnable状态。
  2. runnable 正在运行的状态。运行过程中如果调用return或者是exit的话,那么进入terminated状态。
  3. terminated 线程已经被终止并且进行资源回收。
  4. blocked 在runnable时候,如果acquire lock失败的话那么会进行block状态,当获得锁之后那么返回runnable状态。
  5. waiting 在runnable时候,如果等待notification那么进行这个状态,如果notification触发的话那么返回runnable状态。
  6. timed waiting 其实和waiting状态差不多,只不过这个notification状态会存在一个超时。

守护线程(daemon)和unix操作系统的daemon有些差别。在java里面如果还有存活的线程的话,即使main线程完毕那么程序依然不会结束(这个在c/c++程序里面则不然)。如果将线程设置成为daemon状态的话,那么最后剩下的线程都是daemon的话,那么jvm也会自动退出。

Runnable的run方法是不允许抛出任何异常的,对于可检查的异常可以在代码里面完成,而对于不可检查的异常因为不能够处理,因此如果触发的话那么线程终止。而对于可检查异常如果没有处理的话,那么在线程死亡之前,异常会被一个异常处理器处理:

  • Thread.UncaughtExceptionHandler接口(void uncaughtException(Thread t,Throwable e) ),通过setUncaughtExceptionHandler为单个线程安装处理器,也可以通过setDefaultUncaughtExceptionHandler为所有线程安装。
  • 默认处理器为空。如果线程安装的话,那么使用该线程的ThreadGroup对象作为异常处理器
    • 如果这个线程存在父线程组,那么交给父线程组处理。
    • 如果Thread.getDefaultUncaughtExceptionHandler为非空的话那么调用。
    • 如果Throwable为ThreadDeath实例,那么什么也不做。
    • 将线程名字和Throwable的stacktrace输出到stderr上面。

synchronized关键字其实有两个场景

  • 如果作用于对象或者是对象方法的话,那么其实相当是同步这个对象(对象存在一个mutex lock)
  • 如果作用于静态字段或者是静态方法的话,那么其实相当是同步这个类(类有一个mutex lock)

一旦理解这点之后,就比较好理解為什麼存在

  • wait
  • notify
  • notifyAll

这些方法了。其实都是相当于这个lock对应的condition本身提供的方法。

volatile关键字为 实例字段 的同步访问提供了一种免锁机制。如果声明一个字段为volatile的话,那么编译器和虚拟机就可以知道这个字段很可能会被另外一个线程并发更新。

為什麼抛弃stop和suspend方法? 其实这点非常好理解,因为这些方法都尝试破坏线程本身正常的行为。比如A,B两个线程同时acquire一个lock,如果A成功之后,B在等待,这个之后A被stop或者是suspend的话,那么情况就变成了死锁。

4.9 Collection

#todo:

4.10 JMX

jmx似乎是一个标准,在JDK里面有默认的实现。通过jmx可以暴露jvm进程的一些运行参数以及系统状态(jdk默认实现),也可以暴露应用程序状态(需要自己实现),在jvm内部用单独的线程以server运行。外部client可以通过jmx协议访问,然后输出到其他terminal上面(比如opentsdb, ganglia等,jmxtrans就是做这个事情的)。

我大致阅读了一下代码,在server有两个比较重要的概念:agent(mbean server)和mxbean. agent(mbean server)类似server启动,mxbean则是各个data source. 但是从jdk默认的实现(ManagementFactory::getPlatformMBeanServer)里面可以看到,mxbean不是一个静态基类,而是通过反射的方式将mxbean类转换成为DynamicMBean(猜测数据传输格式应该是JPO,Java Persistent Object,也就是java对象自带序列化方式,这种方式的好处就是没有限制data source format,但是却复杂了实现)。

  • com.dirlt.java.tomb.SimpleJMX 例子比较简单,显示和修改数据 #note:只有基本类型可以显示和修改。如果数据类型为object的话,那么不能显示和修改
  • MXBean允许做RMI
  • Notifcation允许RMI之后做通知
  • so advanced, so powerful, yet so complex

4.11 Future


关于Future的一点个人感想:

Future这个概念非常好,可以做成一个Callable对象的continuation. 但是曾经一段时间我非常希望将其当作一个类似Nio下面的Channel对象来看待,因为一旦如此那么便可以使用类似Select/Epoll这种多路复用组件,来管理众多的continuation,可以检测continuation是否ready或者是是否超时,并且触发回调,整个过程和Nio多路复用非常类似,然后在这上面做异步就非常容易了。但是后来考虑清楚了,这件事情是不靠谱的。原因如下:

  • 检测continuation是否ready非常容易,只需要把continuation逻辑写在发起的Callable之后即可。因此在JDK里面也有FutureTask并且衍生了一些辅助类比如ExecutorCompletionService, 但是这些组件实际上都是封装,没有解决实际问题。
  • 事实上Future和Channel存在本质的不同,Future发起的是一个Callable操作也就是CPU操作,虽然这里面可能有IO操作,但是如果当作通用的CPU操作来看的话,这个操作即使检测到超时也不能够停止,而Channel上read/write是不同的,Channel上面的操作是允许中断的。
  • 就像之前所说的,Future本质发起的Callable对象是一个CPU操作,里面可能也带有IO操作,将Callable对象放在线程池里面执行,也就是说实际上需要靠线程池数量来支撑Callable并发,这点和异步是相反思路的。

5 Tool

5.1 jvisualvm

  • 远程调试需要程序启动的时候加上下面这些选项:
    • -Dcom.sun.management.jmxremote.port=1999
    • -Dcom.sun.management.jmxremote.ssl=false 不走ssl
    • -Dcom.sun.management.jmxremote.authenticate=false 不做验证
    • -Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
  • #note: 我始终没有搞懂profiler和sampler两者的区别。从官方指南来看 Profiling Applications with VisualVM — Java.net http://visualvm.java.net/profiler.html 应该是使用profiler.
  • #note: 可能对于profiler是通过在function前后加上instructment来完成的,而sampler就是纯粹的采样。

插件 Tools->Plugins

5.2 hprof

HPROF: A Heap/CPU Profiling Tool

  • http://docs.oracle.com/javase/7/docs/technotes/samples/hprof.html
  • HPROF is actually a JVM native agent library which is dynamically loaded through a command line option, at JVM startup, and becomes part of the JVM process.
  • The binary format file from HPROF can be used with tools such as HAT to browse the allocated objects in the heap. 二进制输出可以使用HAT这个工具来察看
  • HPROF is capable of presenting
    • CPU usage,
    • heap allocation statistics,
    • and monitor contention profiles.
    • complete heap dumps and
    • states of all the monitors and threads

使用java -agentlib:hprof=help可以察看hprof的调用方式

     HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)

hprof usage: java -agentlib:hprof=[help]|[<option>=<value>, ...]

Option Name and Value  Description                    Default
---------------------  -----------                    -------
heap=dump|sites|all    heap profiling                 all
cpu=samples|times|old  CPU usage                      off
monitor=y|n            monitor contention             n
format=a|b             text(txt) or binary output     a
file=<file>            write data to file             java.hprof[{.txt}]
net=<host>:<port>      send data over a socket        off
depth=<size>           stack trace depth              4
interval=<ms>          sample interval in ms          10
cutoff=<value>         output cutoff point            0.0001
lineno=y|n             line number in traces?         y
thread=y|n             thread in traces?              n
doe=y|n                dump on exit?                  y
msa=y|n                Solaris micro state accounting n
force=y|n              force output to <file>         y
verbose=y|n            print messages about dumps     y

Obsolete Options
----------------
gc_okay=y|n

Examples
--------
  - Get sample cpu information every 20 millisec, with a stack depth of 3:
      java -agentlib:hprof=cpu=samples,interval=20,depth=3 classname
  - Get heap usage information based on the allocation sites:
      java -agentlib:hprof=heap=sites classname

Notes
-----
  - The option format=b cannot be used with monitor=y.
  - The option format=b cannot be used with cpu=old|times.
  - Use of the -Xrunhprof interface can still be used, e.g.
       java -Xrunhprof:[help]|[<option>=<value>, ...]
    will behave exactly the same as:
       java -agentlib:hprof=[help]|[<option>=<value>, ...]

Warnings
--------
  - This is demonstration code for the JVMTI interface and use of BCI,
    it is not an official product or formal part of the JDK.
  - The -Xrunhprof interface will be removed in a future release.
  - The option format=b is considered experimental, this format may change
    in a future release.
  • force=y 会删除原来的文件,如果是多个VM来同时使用hprof的话那么需要使用force=n
  • heap= sites能够看到所有的分配以及热点,而dump能够看到所有引用的对象,而all则能看到两个
    • #note: dump,all能够消耗大量内存,最好别使用,而且没有太大意义
    • 如果不希望对heap做分析的话,那么不要指定这个选项。
  • cpu=samples采用采样方式来做分析,interval则是设置采样间隔。 #note: 可能比较使用于长期运行的程序profiling
  • cpu=times采用代码注入的方式在函数entry和return部分加上代码来做profile.
  • thread=y 可以针对将不同线程区分开,每个线程单独进行profile.
  • depth=n 控制stacktrace的深度,加大深度可以看到更详细的调用栈。
  • doe=n 在exit的时候不dump任何数据

代码处理选项部分还是比较诡异的,可以看看代码是如何处理的 https://cluster.earlham.edu/trac/bccd-ng/browser/branches/skylar-install_jdk/trees/software/bccd/software/jdk1.6.0_14/demo/jvmti/hprof/src/hprof_init.c?rev=1854


How Does HPROF Work?

  • a dynamically-linked native library that uses JVM TI and writes out profiling information either to a file descriptor or to a socket in ascii or binary format. (native动态链接库完成的,使用了JVM TI接口,将数据写到socket或者是文件)
    • JVM TI Java Virtual Machine Tool Interface
    • calls to JVM TI
    • event callbacks from JVM TI,
    • and through Byte Code Insertion (BCI)
  • The cpu=samples option doesn't use BCI, HPROF just spawns a separate thread that sleeps for a fixed number of micro seconds, and wakes up and samples all the running thread stacks using JVM TI. 通过另外线程通过JVM TI来监控其他线程栈
  • The cpu=times option attempts to track the running stack of all threads, and keep accurate CPU time usage on all methods. This option probably places the greatest strain on the VM, where every method entry and method exit is tracked. Applications that make many method calls will be impacted more than others.
  • The heap=sites and heap=dump options are the ones that need to track object allocations. These options can be memory intensive (less so with hprof=sites) and applications that allocate many objects or allocate and free many objects will be impacted more with these options. On each object allocation, the stack must be sampled so we know where the object was allocated, and that stack information must be saved. HPROF has a series of tables allocated in the C or malloc() heap that track all it's information. HPROF currently does not allocate any Java objects.

5.3 jmap

#todo:

5.4 BTrace

#todo:

comments powered by Disqus