jps:虚拟机进程状况工具

可以列出正在运行的虚拟机进

程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一

ID(LVMID,Local Virtual Machine Identifier)。对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Process Identifier)是一致的,使

用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID,但如果同时启动了

多个虚拟机进程,无法根据进程名称定位时,那就必须依赖jps命令显示主类的功能才能区分了。

命令格式:

jps还可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态,参数hostid为RMI注册表中

注册的主机名。

jps [ options ] [ hostid ]
jps -l
2388 D:\Develop\glassfish\bin\..\modules\admin-cli.jar
2764 com.sun.enterprise.glassfish.bootstrap.ASMain
3788 sun.tools.jps.Jps

jstat:虚拟机统计信息监视工具

jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可

以显示本地或者远程[1]虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有

GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工

具。

参数interval和count代表查询间隔和次数,如果省略这2个参数,说明只查询一次。假设需要每250

毫秒查询一次进程2764垃圾收集状况,一共查询20次,那命令应当是:

jstat -gc 2764 250 20

选项option代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状

况。详细请参考表4-2中的描述。

jstat -gcutil 2764
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 0.00 6.20 41.42 47.20 16 0.105 3 0.472 0.577

查询结果表明:这台服务器的新生代Eden区(E,表示Eden)使用了6.2%的空间,2个Survivor区

(S0、S1,表示Survivor0、Survivor1)里面都是空的,老年代(O,表示Old)和永久代(P,表示

Permanent)则分别使用了41.42%和47.20%的空间。程序运行以来共发生Minor GC(YGC,表示Young

GC)16次,总耗时0.105秒;发生Full GC(FGC,表示Full GC)3次,总耗时(FGCT,表示Full GC

Time)为0.472秒;所有GC总耗时(GCT,表示GC Time)为0.577秒。

jmap:Java内存映像工具

jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。如

果不使用jmap命令,要想获取Java堆转储快照也还有一些比较“暴力”的手段:譬如在第2章中用过的-

XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在内存溢出异常出现之后自动生成堆转储

快照文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成堆转储快

照文件,又或者在Linux系统下通过Kill-3命令发送进程退出信号“恐吓”一下虚拟机,也能顺利拿到堆转

储快照。

jmap的作用并不仅仅是为了获取堆转储快照,它还可以查询finalize执行队列、Java堆和方法区的

详细信息,如空间使用率、当前用的是哪种收集器等。

jmap -dump:format=b,file=eclipse.bin 3500
Dumping heap to C:\Users\IcyFenix\eclipse.bin ...
Heap dump file created

jhat:虚拟机堆转储快照分析工具

JDK提供jhat(JVM Heap Analysis Tool)命令与jmap搭配使用,来分析jmap生成的堆转储快照。

jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。不过

实事求是地说,在实际工作中,除非手上真的没有别的工具可用,否则多数人是不会直接使用jhat命令

来分析堆转储快照文件的,主要原因有两个方面。一是一般不会在部署应用程序的服务器上直接分析

堆转储快照,即使可以这样做,也会尽量将堆转储快照文件复制到其他机器[1]上进行分析,因为分析

工作是一个耗时而且极为耗费硬件资源的过程,既然都要在其他机器上进行,就没有必要再受命令行

工具的限制了。另外一个原因是jhat的分析功能相对来说比较简陋,后文将会介绍到的VisualVM,以

及专业用于分析堆转储快照文件的Eclipse Memory Analyzer、IBM HeapAnalyzer [2]等工具,都能实现

比jhat更强大专业的分析功能。

使用jhat分析dump文件

jhat eclipse.bin
Reading from eclipse.bin...
Dump file created Fri Nov 19 22:07:21 CST 2010
Snapshot read, resolving...
Resolving 1225951 objects...
Chasing references, expect 245 dots....
Eliminating duplicate references...
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

屏幕显示“Server is ready.”的提示后,用户在浏览器中输入http://localhost:7000/可以看到分析结果。

分析结果默认以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的“Heap

Histogram”(与jmap-histo功能一样)与OQL页签的功能,前者可以找到内存中总容量最大的对象,后

者是标准的对象查询语言,使用类似SQL的语法对内存中的对象进行查询统计。

jstack:Java堆栈跟踪工具

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者

javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的

目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂

起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过jstack来查看各个线程的调用堆栈,

就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

jstack -l 3500
2010-11-19 23:11:26
Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.1-b03 mixed mode):
"[ThreadPool Manager] - Idle Thread" daemon prio=6 tid=0x0000000039dd4000 nid= 0xf50 in Object.wait() [0x000000003c96f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000016bdcc60> (a org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor)
at java.lang.Object.wait(Object.java:485)
at org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor.run (Executor. java:106)
- locked <0x0000000016bdcc60> (a org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor)
Locked ownable synchronizers:
- None

可视化故障处理工具

JHSDB:基于服务性代理的调试工具

/**
* staticObj、instanceObj、localObj存放在哪里?
*/
public class JHSDB_TestCase {
	static class Test {
		static ObjectHolder staticObj = new ObjectHolder();
		ObjectHolder instanceObj = new ObjectHolder();
		void foo() {
			ObjectHolder localObj = new ObjectHolder();
			System.out.println("done"); // 这里设一个断点
		}
	}
	private static class ObjectHolder {}
	public static void main(String[] args) {
		Test test = new JHSDB_TestCase.Test();
		test.foo();
	}
}

staticObj随着Test的类型信息存放在方法区,instanceObj随着Test的对象实

例存放在Java堆,localObject则是存放在foo()方法栈帧的局部变量表中。现在要做的是通过JHSDB来实践验证这一点。

jhsdb hsdb --pid 11180

命令打开的JHSDB的界面如图4-4所示。

首先点击菜单中的Tools->Heap Parameters [2],结果如图4-5所示,因为笔者的运行参数中指定了使

用的是Serial收集器,图中我们看到了典型的Serial的分代内存布局,Heap Parameters窗口中清楚列出了

新生代的Eden、S1、S2和老年代的容量(单位为字节)以及它们的虚拟内存地址起止范围。打开Windows->Console窗

口,使用scanoops命令在Java堆的新生代(从Eden起始地址到To Survivor结束地址)范围内查找

ObjectHolder的实例,结果如下所示:

hsdb>scanoops 0x00007f32c7800000 0x00007f32c7b50000 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c458 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c480 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c490 JHSDB_TestCase$ObjectHolder

果然找出了三个实例的地址,而且它们的地址都落到了Eden的范围之内,算是顺带验证了一般情

况下新对象在Eden中创建的分配规则。再使用Tools->Inspector功能确认一下这三个地址中存放的对

象,结果如图4-6所示。Inspector为我们展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关

系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口

方法表(itable)等。由于我们的确没有在ObjectHolder上定义过任何字段,所以图中并没有看到任何

实例字段数据,读者在做实验时不妨定义一些不同数据类型的字段,观察它们在HotSpot虚拟机里面是

如何存储的。

接下来要根据堆中对象实例地址找出引用它们的指针,JHSDB的Tools菜单中有Compute

Reverse Ptrs来完成这个功能。

hsdb> revptrs 0x00007f32c7a7c458
Computing reverse pointers...
Done.
Oop for java/lang/Class @ 0x00007f32c7a7b180

果然找到了一个引用该对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地

址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面

有一个名为staticObj的实例字段

hsdb>revptrs 0x00007f32c7a7c480
Computing reverse pointers...
Done.
Oop for JHSDB_TestCase$Test @ 0x00007f32c7a7c468

这个结果完全符合我们的预期,第二个ObjectHolder的指针是在Java堆中JHSDB_TestCase$Test对

象的instanceObj字段上。但是我们采用相同方法查找第三个ObjectHolder实例时,JHSDB返回了一个

null,表示未查找到任何结果:

hsdb> revptrs 0x00007f32c7a7c490
null

JConsole:Java监视与管理控制台

JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment

Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean(Managed Bean)对系统进

行信息收集和参数动态调整。JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以

运行于虚拟机之上的软件中,典型的如中间件大多也基于JMX来实现管理与监控。虚拟机对JMX

MBean的访问也是完全开放的,可以使用代码调用API、支持JMX协议的管理控制台,或者其他符合

JMX规范的软件进行访问。

1.启动JConsole

通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询,如图4-10所示。双击选择其中一个进程便可进入主界面开始监控。

JMX支持跨服务器的管理,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行

监控。

“概述”页签里显示的是整个虚拟机主要运行数据的概览信息,包括“堆内存使用情况”“线

程”“类”“CPU使用情况”四项信息的曲线图,这些曲线图是后面“内存”“线程”“类”页签的信息汇总。

2.内存监控

/**
* 内存占位符对象,一个OOMObject大约占64KB
*/
static class OOMObject {
public byte[] placeholder = new byte[64 * 1024];
}
public static void fillHeap(int num) throws InterruptedException {
	List<OOMObject> list = new ArrayList<OOMObject>();
	for (int i = 0; i < num; i++) {
	// 稍作延时,令监视曲线的变化更加明显
	Thread.sleep(50);
	list.add(new OOMObject());
	}
	System.gc();
}
public static void main(String[] args) throws Exception {
	fillHeap(1000);
}

这段代码的作用是以64KB/50ms的速度向Java堆中填充数据,一共填充1000次,使用JConsole

的“内存”页签进行监视,观察曲线和柱状指示图的变化。

程序运行后,在“内存”页签中可以看到内存池Eden区的运行趋势呈现折线状,如图4-12所示。监

视范围扩大至整个堆后,会发现曲线是一直平滑向上增长的。从柱状图可以看到,在1000次循环执行

结束,运行了System.gc()后,虽然整个新生代Eden和Survivor区都基本被清空了,但是代表老年代的柱

状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。

图4-12显示Eden空间为27328KB,因为没有设置-XX:SurvivorRadio参数,所以Eden

与Survivor空间比例的默认值为8∶1,因此整个新生代空间大约为27328KB×125%=34160KB。

执行System.gc()之后,空间未能回收是因为List<OOMObject>list对象仍然存活,

fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把

System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。

3.线程监控

/**
* 线程死循环演示
*/
public static void createBusyThread() {
Thread thread = new Thread(new Runnable() {
	@Override
	public void run() {
	while (true) // 第41行
	;
	}
	}, "testBusyThread");
thread.start();
}
/**
* 线程锁等待演示
*/
public static void createLockThread(final Object lock) {
Thread thread = new Thread(new Runnable() {
	@Override
	public void run() {
	synchronized (lock) {
	try {
	lock.wait();
	} catch (InterruptedException e) {
	e.printStackTrace();
	}
	}
	}
	}, "testLockThread");
thread.start();
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	br.readLine();
	createBusyThread();
	br.readLine();
	Object obj = new Object();
	createLockThread(obj);
}

/**
* 线程死锁等待演示
*/
static class SynAddRunalbe implements Runnable {
int a, b;
public SynAddRunalbe(int a, int b) {
	this.a = a;
	this.b = b;
}
@Override
public void run() {
	synchronized (Integer.valueOf(a)) {
			synchronized (Integer.valueOf(b)) {
			System.out.println(a + b);
			}
		}
	}
}
public static void main(String[] args) {
	for (int i = 0; i < 100; i++) {
		new Thread(new SynAddRunalbe(1, 2)).start();
		new Thread(new SynAddRunalbe(2, 1)).start();
	}
}

这段代码开了200个线程去分别计算1+2以及2+1的值,理论上for循环都是可省略的,两个线程也

可能会导致死锁,不过那样概率太小,需要尝试运行很多次才能看到死锁的效果。如果运气不是特别

差的话,上面带for循环的版本最多运行两三次就会遇到线程死锁,程序无法结束。造成死锁的根本原

因是Integer.valueOf()方法出于减少对象创建次数和节省内存的考虑,会对数值为-128~127之间的

Integer对象进行缓存[2],如果valueOf()方法传入的参数在这个范围之内,就直接返回缓存中的对象。

也就是说代码中尽管调用了200次Integer.valueOf()方法,但一共只返回了两个不同的Integer对象。假如

某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A在等待被线程B持有的

Integer.valueOf(1),线程B又在等待被线程A持有的Integer.valueOf(2),结果大家都跑不下去的情况。

出现线程死锁之后,点击JConsole线程面板的“检测到死锁”按钮,将出现一个新的“死锁”页签,如

图4-16所示。

图4-16中很清晰地显示,线程Thread-43在等待一个被线程Thread-12持有的Integer对象,而点击线

程Thread-12则显示它也在等待一个被线程Thread-43持有的Integer对象,这样两个线程就互相卡住,除

非牺牲其中一个,否则死锁无法释放。

VisualVM:多合-故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,它除了常规的运行监视、故障处理外,还将提供其他方面的能

力,譬如性能分析(Profiling)。VisualVM的性能分析功能比起JProfiler、YourKit等专业且收费的

Profiling工具都不遑多让。而且相比这些第三方工具,VisualVM还有一个很大的优点:不需要被监视的

程序基于特殊Agent去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直

接应用在生产环境中。这个优点是JProfiler、YourKit等工具无法与之媲美的。

1.VisualVM兼容范围与插件安装

VisualVM基于NetBeans平台开发工具,所以一开始它就具备了通过插件扩展功能的能力,有了插

件扩展支持,VisualVM可以做到:

·显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。

·监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)。

·dump以及分析堆转储快照(jmap、jhat)。

·方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。

·离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快

照发送开发者处进行Bug反馈。

首次启动VisualVM后,读者先不必着急找应用程序进行监测,初始状态下的VisualVM并没有加载

任何插件,虽然基本的监视、线程面板的功能主程序都以默认插件的形式提供,但是如果不在

VisualVM上装任何扩展插件,就相当于放弃它最精华的功能,和没有安装任何应用软件的操作系统差

不多。

VisualVM的插件可以手工进行安装,在网站[2]上下载nbm包后,点击“工具->插件->已下载”菜

单,然后在弹出对话框中指定nbm包路径便可完成安装。独立安装的插件存储在VisualVM的根目录,

譬如JDK 9之前自带的VisulalVM,插件安装后是放在JDK_HOME/lib/visualvm中的。手工安装插件并

不常用,VisualVM的自动安装功能已可找到大多数所需的插件,在有网络连接的环境下,点击“工具->

插件菜单”,弹出如图4-17所示的插件页签,在页签的“可用插件”及“已安装”中列举了当前版本

VisualVM可以使用的全部插件,选中插件后在右边窗口会显示这个插件的基本信息,如开发者、版

本、功能描述等。

2.生成、浏览堆转储快照

在VisualVM中生成堆转储快照文件有两种方式,可以执行下列任一操作:

·在“应用程序”窗口中右键单击应用程序节点,然后选择“堆Dump”。

·在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中单击“堆

Dump”。

生成堆转储快照文件之后,应用程序页签会在该堆的应用程序下增加一个以[heap-dump]开头的子

节点,并且在主页签中打开该转储快照,如图4-20所示。如果需要把堆转储快照保存或发送出去,就

应在heapdump节点上右键选择“另存为”菜单,否则当VisualVM关闭时,生成的堆转储快照文件会被当

作临时文件自动清理掉。要打开一个由已经存在的堆转储快照文件,通过文件菜单中的“装入”功能,

选择硬盘上的文件即可。

堆页签中的“摘要”面板可以看到应用程序dump时的运行时参数、System.getPro-perties()的内容、

线程堆栈等信息;“类”面板则是以类为统计口径统计类的实例数量、容量信息;“实例”面板不能直接

使用,因为VisualVM在此时还无法确定用户想查看哪个类的实例,所以需要通过“类”面板进入,

在“类”中选择一个需要查看的类,然后双击即可在“实例”里面看到此类的其中500个实例的具体属性信

息;“OQL控制台”面板则是运行OQL查询语句的,同jhat中介绍的OQL功能一样。

3.分析程序性能

在Profiler页签中,VisualVM提供了程序运行期间方法级的处理器执行时间分析以及内存分析。做

Profiling分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境使用这项功能,或者改用

JMC来完成,JMC的Profiling能力更强,对应用的影响非常轻微。

要开始性能分析,先选择“CPU”和“内存”按钮中的一个,然后切换到应用程序中对程序进行操

作,VisualVM会记录这段时间中应用程序执行过的所有方法。如果是进行处理器执行时间分析,将会

统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对

象所占的空间。等要分析的操作执行结束后,点击“停止”按钮结束监控过程,如图4-21所示

BTrace动态日志跟踪

BTrace [3]是一个很神奇的VisualVM插件,它本身也是一个可运行的独立程序。BTrace的作用是在

不中断目标程序运行的前提下,通过HotSpot虚拟机的Instrument功能[4]动态加入原本并不存在的调试

代码。这项功能对实际生产中的程序很有意义:如当程序出现问题时,排查错误的一些必要信息时

(譬如方法参数、返回值等),在开发时并没有打印到日志之中以至于不得不停掉服务时,都可以通

过调试增量来加入日志代码以解决问题。

在VisualVM中安装了BTrace插件后,在应用程序面板中右击要调试的程序,会出现“TraceApplication…”菜单,点击将进入BTrace面板。这个面板看起来就像一个简单的Java程序开发环境,里

面甚至已经有了一小段Java代码,如图4-22所示。

BTrace的用途很广泛,打印调用堆栈、参数、返回值只是它最基础的使用形式,在它的网站上有

使用BTrace进行性能监视、定位连接泄漏、内存泄漏、解决多线程竞争问题等的使用案例,有兴趣的

读者可以去网上了解相关信息。

BTrace能够实现动态修改程序行为,是因为它是基于Java虚拟机的Instrument开发的。Instrument是

Java虚拟机工具接口(Java Virtual Machine Tool Interface,JVMTI)的重要组件,提供了一套代理

(Agent)机制,使得第三方工具程序可以以代理的方式访问和修改Java虚拟机内部的数据。阿里巴巴

开源的诊断工具Arthas也通过Instrument实现了与BTrace类似的功能。

HotSpot虚拟机插件及工具

Ideal Graph Visualizer:用于可视化展示C2即时编译器是如何将字节码转化为理想图,然后转化为

机器码的。

·Client Compiler Visualizer [1]:用于查看C1即时编译器生成高级中间表示(HIR),转换成低级中

间表示(LIR)和做物理寄存器分配的过程。

·MakeDeps:帮助处理HotSpot的编译依赖的工具。

·Project Creator:帮忙生成Visual Studio的.project文件的工具。

·LogCompilation:将-XX:+LogCompilation输出的日志整理成更容易阅读的格式的工具。

·HSDIS:即时编译器的反汇编插件

HSDIS插件的作用是让HotSpot的-XX:+PrintAssembly指令调用它来把即时编译器动态生成的本

地代码还原为汇编代码输出,同时还会自动产生大量非常有价值的注释,这样我们就可以通过输出的

汇编代码来从最本质的角度分析问题。

使用的是SlowDebug或者FastDebug版的HotSpot,那可以直接通

过-XX:+PrintAssembly指令使用的插件;如果读者使用的是Product版的HotSpot,则还要额外加入一

个-XX:+UnlockDiagnosticVMOptions参数才可以工作。

public class Bar {
int a = 1;
static int b = 2;
public int sum(int c) {
	return a + b + c;
}
public static void main(String[] args) {
	new Bar().sum(3);
}
}
java -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:Compile-Command=compileonly,*Bar.sum test.Bar

其中,参数-Xcomp是让虚拟机以编译模式执行代码,这样不需要执行足够次数来预热就能触发即

时编译。两个-XX:CompileCommand的意思是让编译器不要内联sum()并且只编译sum(),-XX:

+PrintAssembly就是输出反汇编内容。如果一切顺利的话,屏幕上会出现类似代码清单4-13所示的内

容。

[Disassembling for mach='i386']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'test/Bar'
# this: ecx = 'test/Bar'
# parm0: edx = int
# [sp+0x20] (sp of caller)
……
0x01cac407: cmp 0x4(%ecx),%eax
0x01cac40a: jne 0x01c6b050 ; {runtime_call}
[Verified Entry Point]
0x01cac410: mov %eax,-0x8000(%esp)
0x01cac417: push %ebp
0x01cac418: sub $0x18,%esp ; *aload_0
; - test.Bar::sum@0 (line 8)
;; block B0 [0, 10]
0x01cac41b: mov 0x8(%ecx),%eax ; *getfield a
; - test.Bar::sum@1 (line 8)
0x01cac41e: mov $0x3d2fad8,%esi ; {oop(a
'java/lang/Class' = 'test/Bar')}
0x01cac423: mov 0x68(%esi),%esi ; *getstatic b
; - test.Bar::sum@4 (line 8)
0x01cac426: add %esi,%eax
0x01cac428: add %edx,%eax
0x01cac42a: add $0x18,%esp
0x01cac42d: pop %ebp
0x01cac42e: test %eax,0x2b0100 ; {poll_return}
0x01cac434: ret

1)mov%eax,-0x8000(%esp):检查栈溢。

2)push%ebp:保存上一栈帧基址。

3)sub$0x18,%esp:给新帧分配空间。

4)mov 0x8(%ecx),%eax:取实例变量a,这里0x8(%ecx)就是ecx+0x8的意思,前面代码片

段“[Constants]”中提示了“this:ecx='test/Bar'”,即ecx寄存器中放的就是this对象的地址。偏移0x8是越

过this对象的对象头,之后就是实例变量a的内存位置。这次是访问Java堆中的数据。

5)mov$0x3d2fad8,%esi:取test.Bar在方法区的指针。

6)mov 0x68(%esi),%esi:取类变量b,这次是访问方法区中的数据。

7)add%esi,%eax、add%edx,%eax:做2次加法,求a+b+c的值,前面的代码把a放在eax中,把b

放在esi中,而c在[Constants]中提示了,“parm0:edx=int”,说明c在edx中。

8)add$0x18,%esp:撤销栈帧。

9)pop%ebp:恢复上一栈帧。

10)test%eax,0x2b0100:轮询方法返回处的SafePoint。

11)ret:方法返回。

JITWatch [5]是HSDIS经常搭配使用的可视化的编译日志分析工具,为便于在JITWatch中读取,读

者可使用以下参数把日志输出到logfile文件:

-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:+LogCompilation
-XX:LogFile=/tmp/logfile.log
-XX:+PrintAssembly
-XX:+TraceClassLoading

在JITWatch中加载日志后,就可以看到执行期间使用过的各种对象类型和对应调用过的方法了。

选择想要查看的类和方法,即可查看对应的Java源代码、字节码和即时编译器生成的汇编代码