java se 6平台中提供了多种可观察性(observability)工具,这其中的许多工具都可在系统中运行,而这些工具中的只有极少数被用于挂起进程或核心复制处理。因此,在本文中,我们将分析这些可观察性工具在进程上的效果。
一、 在java se 6平台中的可观察性工具-dtrace
在java se 6软件中又引入了许多可观察性改进功能。尽管其中大多数可适用于所有的平台,但是其中的一些改进仅是特定于solaris操作系统(特别针对solaris 10及更高版本)的。在j2se 5.0平台中,引入了一种新的动态跟踪(dtrace)行为――jstack。正如我们已经了解的,jstack能够打印混合模式堆栈跟踪信息(java和本机c/c++语言是以帧方式显示的)。当从一个给定的java进程中发出pollsys系统调用时,下列d脚本将输出对混合模式堆栈的跟踪信息:
#!/usr/sbin/dtrace -s syscall::pollsys:entry / pid == $1 / { /*打印至多50帧*/ jstack(50); } |
下面是上面脚本的运行结果:
libc.so.1`__pollsys+0xa libc.so.1`poll+0x52 libjvm.so`int os_sleep(long long,int)+0xb4 libjvm.so`int os::sleep(thread*,long long,int)+0x1ce libjvm.so`jvm_sleep+0x1bc java/lang/thread.sleep dtest.method3 dtest.method2 dtest.method1 dtest.main [...篇幅所限,删去了一些输出结果...] |
其实,jstack就是java可观察性在solaris 10 os及以上版本中的一个很有用的起点。但是,java se 6中远不止这些,它新增加了许多针对java虚拟机(jvm)可观察性和java应用程序可观察性的dtrace probe。java se 6发行中增加两种jvm特定的dtrace提供者-hotspot和hotspot_jni。
二、 hotspot提供者
hotspot提供者增加了许多探针,它们可以大致地作如下分类:
? vm生命周期探针-vm启动,关闭事件
? 线程生命周期探针-线程启动,停止,等等
? 类加载探针-一个java类加载或卸载
? 垃圾收集探针-gc启动,结束
? 方法编译探针-java字节码-到-本机代码编译("hotspot编译")
? 监视器探针-java监视竞争性入口,通知,通知全部,等等
? 应用程序探针-java对象分配,java方法入口/返回,等等
有关于这些探针的更为详细的信息,请参考keith mcguigan的博客。下面是使用这些探针的一些示例。
(一) 打印java线程的名称
hotspot$1:::thread-start { self->ptr = (char*) copyin(arg0, arg1+1); self->ptr[arg1] = '/0'; self->threadname = (string) self->ptr; printf("thread %s started/n", self->threadname); } |
(二) 在d脚本中的-verbose:class等价物
你可能已经使用过-verbose:class jvm选项。当一个java类加载或卸载时,这个选项能够使jvm打印一个跟踪消息。下面是使用dtrace得到相同结果的情况:
/*当每个类加载时,打印出其各自的名称*/ hotspot$1:::class-loaded { self->str_ptr = (char*) copyin(arg0, arg1+1); self->str_ptr[arg1] = '/0'; self->name = (string) self->str_ptr; printf("class %s loaded/n", self->name); } |
(三) 打印异常中的栈跟踪信息(除了混合模式)
throwable.printstacktrace()方法打印一个包含java帧的堆栈跟踪。这个d脚本将打印java代码,java本机接口(jni)代码,c/c++代码以及os c/c++代码的所有的帧-无论何时引发一个例外。
hotspot$1:::method-entry { self->ptr = (char*)copyin(arg1, arg2+1); self->ptr[arg2] = '/0'; self->classname = (string)self->ptr; self->ptr = (char*)copyin(arg3, arg4+1); self->ptr[arg4] = '/0'; self->methodname = (string)self->ptr; } hotspot$1:::method-entry / self->classname == "java/lang/throwable" && self->methodname == "<init>" / { jstack(); } |
在java代码中的所有的异常和错误都直接或间接地派生自java.lang.throwable,因此所有的异常构造器最后都将调用java.lang.throwable的构造器。这个d脚本建立一个方法入口探针-使用一个针对该构造器的过滤器(注意,<init>是构造器方法的内部名)。当调用这个构造器时,jstack()行为将打印混合模式堆栈跟踪。
(四) java堆直方图
我们可以使用object-alloc探针来构建一个java堆直方图,如下所示:
hotspot$1:::object-alloc { self->str_ptr = (char*) copyin(arg1, arg2+1); self->str_ptr[arg2] = '/0'; self->classname = (string) self->str_ptr; @allocs_count[self->classname] = count(); @allocs_size[self->classname] = sum(arg3); } |
(五) hotspot_jni提供者
jni允许java程序与c/c++代码进行交互。如果你想观察java代码与本机代码的交互,你可以使用hotspot_jni提供者。对于每一个jni函数输入/输出而言,这个提供者将暴露一个探针。
(六) 在java程序中使用dtrace的效果
dtrace被设计为一种生产模式可观察性系统(未使用时具有零影响)。当支持探针时,被观察的系统不受影响。
由于大部分的由hotspot和hotspot_jni提供者所暴露的jvm探针都是轻量级的,因此可以用于生产机器中。然而,一些由hotspot提供者所暴露的探针要求使用一个特定的命令行选项"-xx:+extendeddtraceprobes"来启动jvm。这些探针分别是:java方法入口/方法返回,对象分配和java监视器探针。注意,这些探针都要求改变hotspot字节码解释器和hotspot编译器(字节码-到-机器代码编译器)。即使不被支持时,这些探针的代价也是比较昂贵的。 三、 在java se 6平台中的可观察性工具
除了dtrace与java技术的集成之外,java se 6发行中还包含了许多其它的可观察性工具。下面总结了这些工具,其中还包含一些更为详细的链接说明。
(一) jconsole
jconsole使用jvm的可扩展性java管理扩展(jmx)工具来提供关于运行于java平台的应用程序的性能和资源消耗的信息。
在j2se 5.0软件中,你需要启动使用-dcom.sun.management.jmxremote选项监控的应用程序。注意:在java se 6软件中,不再有这一要求。当启动该应用程序时,不需要特定的命令行选项。
在生产系统中的应用
jconsole启动一个在被观察的java程序的jvm内部的jmx代理。运行另外一部分代码仅有一点极微弱的影响-但是影响很小。
另外,尽管jconsole在监视本地应用程序的开发和快速原型开发中很有用,但在实际的应用系统中不推荐使用。理由是,jconsole本身也消耗大量的系统资源。我们推荐的方法是用远程监控来把jconsole应用程序与被监控的系统加以隔离。因此,对于应用系统来说,以远程模式使用jconsole更好些。对于安全的远程监控来说,可以使用安全选项。
(二) jps
jps相当于solaris进程工具ps。更多的信息,请参考《jps-java virtual machine process status tool》。
不象"pgrep java"或"ps -ef | grep java",jps并不使用应用程序名来查找jvm实例。因此,它查找所有的java应用程序,包括即使没有使用java执行体的那种(例如,定制的启动器)。另外,jps仅查找当前用户的java进程,而不是当前系统中的所有进程。
(三) jstat
jstat显示一个测量(instrumented)java hotspot虚拟机的性能统计信息(请参考《jstat-java virtual machine statistics monitoring tool》)。有关于性能计数器的更详细的信息请参考《code sample-jvmstat 3.0》。
(四) jstatd
jstatd是一个java远程方法调用(rmi)服务器应用程序-它监控测量java hotspot虚拟机的创建和终止并且提供一个接口来允许远程监控工具依附到运行于本地主机的jvm(请参考《jstatd-virtual machine jstat daemon》)。
在应用系统中的使用
jps及其它jvmstat实用程序都使用极为轻量级的观察机制。由jvm分配一小部分共享内存,而性能计数器也是从这部分内存中分配的。jvm子系统基于其感兴趣的事件更新性能计数器。客户端工具仅仅负责异步地从共享内存段中进行读取。因此,总的来说,使用jvmstat进行监控的效果是很小的。 四、 java se 6平台中针对于postmortem的可观察性工具
java se 6支持postmortem可观察性工具-它能够从挂起的java进程或java核心复制中获得信息。这些工具(除了jhat外)都使用solaris libproc库来依附到和读取被观察的程序。在观察期间,目标程序被挂起。当java进程被挂起或当从一个java进程中发生一个核心复制时,可以使用这些工具。在任何可能的情况下,请考虑使用gcore来捕获系统的核心复制的一个快照并且使用任何下列工具"离线"分析核心复制。
(一) jinfo
jinfo打印一个给定的java进程或核心文件或一个远程调试服务器的java配置信息。配置信息包括java系统属性和jvm命令行标志(更多信息,请参考《jinfo-configuration info》)。
(二) jmap
jmap:如果这个工具不使用任何选项(除了pid或core选项)运行,那么它显示类似于solaris的pmap工具所输出的信息。这个工具支持针对java堆可观察性的若干其它选项。
在java se 6平台中,新加入了一个-dump选项。这样可以使jmap能够把java堆信息复制到一个文件中,然后我们可以使用新的jhat命令(见下面一节)来分析它。
jmap -dump选项并不使用solaris libproc来实现实时处理;而是,它运行当前正运行的jvm中的一小段代码,由此来实现堆复制。既然这种堆复制代码运行于jvm内部,那么其速度是比较快的。堆复制的效果大致相当于实现一次"完全的gc"(对整个堆的垃圾收集),再加上把该堆的内容写入到文件中。实现堆复制的另外一种可能的思路是使用gcore来进行核心复制并且运行"jmap -dump"(这与以"离线"方式运行的核心复制形成对照)。
(三) jstack
jstack等价于solaris的pstack工具。jstack打印所有的java线程的堆栈跟踪信息(可选地包括本机帧信息),请参考《jstack-堆栈跟踪》。关于锁和死锁的信息也可以被打印,请参考java.util.concurrent locks。
(四) jsadebugd
jsadebugd依附到一个java进程或核心文件并且担当一个调试服务器的作用。远程客户,例如jstack、jmap和jinfo,都能够通过java rmi依附到该服务器。
(五) jhat
jhat是一个java堆复制浏览器。这个工具分析java堆复制文件(例如,由上面的"jmap -dump"所产生的)。jhat启动一个允许堆中的对象在web浏览器中进行分析的web服务器。这个工具并不是想用于应用系统中而是用于"离线"分析。"jhat工具是平*立的",其意思是,它可以被用来观察在任何平台上所产生的堆复制。例如,我们有可能在linux系统上使用jhat来观察一个在solaris os上所产生的堆复制。