Java应用调优指南之-工具篇

Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 Java应用调优指南之-前菜

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

1.1 Heap Dump

jmap -dump:live,format=b,file=heap.hprof pid

从安全点的日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(写到Page Cache,或许触发background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

  • 本身大小(Shallow Size):对象本来的大小。
  • 保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,”jstack pid” 或”jstack -l pid” ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差一百倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),慎用再慎用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

  • RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
  • BLOCKED:被某个锁(synchronizers)給block住了。
  • WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
  • TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

  • 内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
  • 内存Tab:GC Tab的GC详细情况,以及分配Tab中TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
  • 代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
  • 线程Tab:热点线程,再换个姿势来看热点方法和调用树。
  • 线程Tab:争用,等待时间,锁定实例等。

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder ,JDK8则不需要。
如果是远程服务器,要开JMX:

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

  • 加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
  • 方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
  • Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

by:calvin