JVM 性能监控调优


参考:http://www.cnblogs.com/java-zhao/category/776216.html(万分感谢,学了好多东西)

1. JVM性能监控

1、定位系统问题

  • 依据
    • GC日志
    • 堆转储快照(heapdump/hprof文件)
    • 线程快照(threaddump/javacore文件)
    • 运行日志
    • 异常堆栈
  • 分析依据的工具
    • jps:显示指定系统内的所有JVM进程
    • jstat:收集JVM各方面的运行数据
    • jinfo:显示JVM配置信息
    • jmap:形成堆转储快照(heapdump文件)
    • jhat:分析heapdump文件
    • jstack:显示JVM的线程快照
    • jconsole
    • visualVM

说明:后边两种是具有图形化界面的。

 

2、jps(是其他所有命令的基础)

作用:列出所有的JVM虚拟机进程。

格式:jps -l

 

3、jstat(是没有GUI界面的情况下,在运行期定位JVM性能问题的首选

作用:查看gc数据和类加载卸载数据

格式:jstat option PID interval count

意义:每隔interval毫秒做一次option,一共做count次

说明:S0(from区)使用了41.74%;S1(to区)使用了0;E(Eden区)使用了54.35%;O(Old,年老代)使用了62.41%;P(Perment,永久代)使用了99.63%;YGC(Young GC)了32次,YGCT(Young GC Time)花销0.132秒;FGC(Full GC)了1次,FGCT(Full GC Time)花销0.102秒;GCT(GC Time)总花销0.234秒。

分析:其实上边这个查询结果可以直接看出,我们需要加大P(永久代大小)

说明:加载了3683个类,总共占有4355.3字节;卸载了0个类,卸载的类的字节数为0,类的加载与卸载共花销3.16秒

更多的jstat的使用,参看http://my.oschina.net/skyline520/blog/304805

 

4、jinfo

作用:查看和运行期修改JVM的配置参数

格式:jinfo -flag parameter PID

说明:查看3732进程下的MaxTenuringThreshold参数值。

说明:修改3732进程下的MaxTenuringThreshold参数值,但是windows下失败。

 

5、jmap

作用:生成堆转储快照和查看最占内存的元素,用于分析内存泄露问题

格式(生成堆转储快照):jmap -dump:format=b,file=文件名 PID

说明:生成了3732进程的堆转储文件myfile

格式(查看最占内存的元素):jmap -histo PID

说明:结果自己去看,太多了

 

6、jhat

作用:分析堆转储快照(与jmap配合)

格式:jhat 文件名

说明:执行上述命令后,打开localhost:7000,找到如下红框部分打开,这里才是我们最关注的东西

注意:该工具是万不得已才用的。

推出命令使用”ctrl+c”

 

7、jstack

作用:生成线程快照,定位线程长时间卡顿的原因(线程间死锁、死循环、请求外部资源导致的长时间等待)

格式:jstack -l PID

说明:查看3732进程中的所有线程的堆栈信息

《深入理解Java虚拟机》的作者提供了一个工具jsp页面,使得我们可以在程序运行时,随时运行该jsp页面,来查看线程堆栈信息,代码如下:

复制代码
 1 <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8" import="java.util.Map"%>
 3 <!DOCTYPE html>
 4 <html>
 5 <head>
 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 7 <title>jstack</title>
 8 </head>
 9 <body>
10 <% 
11     for(Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStackTraces().entrySet()){
12         Thread thread = (Thread)stackTrace.getKey();
13         StackTraceElement[] elements = (StackTraceElement[])stackTrace.getValue();
14         
15         /* if(thread.equals(Thread.currentThread())){
16             continue;
17         } */
18         out.println("\n线程:"+thread.getName()+"\n");
19         for(StackTraceElement ele : elements){
20             out.println("\t"+ele+"\n");
21         }
22     }
23 %>
24 </body>
25 </html>
复制代码

注意:代码中我注释掉一段,是因为想也查出当前线程的堆栈信息,作者并没有这个注释。

 

总结:

  • JVM性能相关的6个常用的JDK命令
    • jps:查询JVM中的所有进程,找出将要操作的PID,是所有命令的基础
    • jstat:查看相应JVM进程的gc、类加载卸载信息,是没有GUI界面查看JVM运行数据的首选
    • jinfo:查看和在运行期动态修改JVM配置参数
    • jmap:生成堆转储快照和比较占内存的对象
    • jhat:配合jmap分析堆转储日志,除非没有其他工具可做这个事儿,否则就不用该工具
    • jstack:生成线程快照,定位线程长时间卡顿的原因(线程间死锁、死循环、请求外部资源导致的长时间等待)
  • 输出gc信息到控制台
    • -XX:+PrintGCDetails:输出GC的详细信息
    • -XX:+PrintGCTimeStamps:输出GC的时间信息
    • -XX:+PrintGCApplicatonStoppedTime:GC造成的应用暂停的时间
  • 输出gc信息到文件
    • 以上三个参数在这里依旧适用
    • -Xloggc:文件路径/gc.log:输出到文件


图形化的JVM性能监控


1、图像化的故障处理工具

  • Jconsole
  • visualVM

2、Jconsole

进入”E:\Java\jdk1.6\bin”,双击”jconsole.exe”,弹出如下框:

说明:这里列出了所有的JVM进程,一个Jconsole进程,一个eclipse(PID:4684),这相当于jps命令。

选中其中一个PID,假设选中了eclipse,双击,出现下图:(注:之后的各个叶签,都是每4秒刷新一次

“内存”:相当于jstat -gc,在上图中的详细信息部分,该部分对应的信息就是头部图表部分所写的参数(这里是”整个堆”的情况),同时对应的也是右下角部分柱状图所选中的柱子(这里是”堆”),对于”非堆”指的就是”方法区”(或者称为”永久代”)。当然,这里也可以选择时间范围来查看相应的信息。

“类”:相当于jstat -class,列出了装载类和卸载类的相关信息。

“线程”:相当于jstack,折线图显示了线程数目的变化情况,包括峰值线程数量、活动线程数量;左下角展示了所有线程名称。双击相应的线程名称

“VM摘要”:相当于jinfo

最后,测试一下线程死锁的现象。代码如下:

 View Code

执行main()方法,之后去查看”线程”标签,点击”检测死锁”,如下:

发现线程Thread-95和Thread-106死锁(彼此拥有对方想要的锁)

分析:

1)Integer缓存机制

Integer.valueOf(int xxx),该方法为了减少对象的创建,节省内存,会将xxx转化成的Integer对象缓存起来,之后只要是相同的xxx,那么这个方法都会直接从缓存中取出对象来。假设代码中的Integer.valueOf(1)生成的对象是java.lang.Integer@987197,而Integer.valueOf(2)生成的对象是java.lang.Integer@15e293a,那么之后无论调用多少次Integer.valueOf(1),也无论是哪一个线程去调用该方法,返回的都只是同一个对象java.lang.Integer@987197。也就是说上边的这段代码中的Integer.valueOf(i)只会生成两个不同的对象,就是java.lang.Integer@987197和java.lang.Integer@15e293a,而这两个对象也就是我们的锁对象。

2)死锁发生的时机

假设线程”Thread-95”执行到其第一个synchronized块中时(假设刚刚获取了锁对象java.lang.Integer@987197),这时候CPU时间片切换给了线程”Thread-106”,而”Thread-106”执行其第一个synchronized块(获取了锁对象java.lang.Integer@15e293a),之后”Thread-106”要执行第二个synchronized块儿来获取锁对象java.lang.Integer@987197,这时候就获取不到了,因为这个锁对象正被”Thread-95”所持有,于是”Thread-106”就阻塞在java.lang.Integer@987197这个锁对象上,这时,假设CPU时间片又切换给了”Thread-95”,该线程要执行第二个synchronized块来获取java.lang.Integer@15e293a,就获取不到了,因为该锁对象已被”Thread-106”所持有

3)结果

“Thread-95”持有锁对象java.lang.Integer@987197,阻塞在锁对象java.lang.Integer@15e293a;

“Thread-106”持有锁对象java.lang.Integer@15e293a,阻塞在锁对象java.lang.Integer@987197

 

3、visualVM

是一块更加全面的GUI监视工具,包含很多插件(需要自己下载),具体的见《深入理解Java虚拟机(第二版)》


JVM调优

1、JVM的调优主要是内存的调优,主要调两个方面:

  • 各个代的大小
  • 垃圾收集器选择

2、各个代的大小

  • 常用的调节参数
    • -Xmx
    • -Xms
    • -Xmn
    • -XX:SurvivorRatio
    • -XX:MaxTenuringThreshold
    • -XX:PermSize
    • -XX:MaxPermSize

  • 原则
    • -Xmx==-Xms:防止堆内存频繁进行调整,调整的时机见《第一章 JVM内存结构
    • -Xmn:通常设为-Xmx/4(这是我在企业中实习时的设置方式,系统运行正常、平稳、速度也快),林昊推荐的是-Xmx/3,所以-Xmn==-Xmx/4~-Xmx/3
      • 调节时机:minor GC太频繁
      • -Xmn过小:minor GC太频繁;小对象可能也会直接进入年老代,提前导致Full GC
      • -Xmn过大:年轻代大了,minor GC的时间变长了;年老代变小了,Full GC会频繁
      • 调节策略:若-Xmx可调大,则调大,且保持-Xmn==-Xmx/4~-Xmx/3;若-Xmx不可调大,在保持-Xmn==-Xmx/4~-Xmx/3的范围内增大-Xmn,若-Xmn也不可调了,则试着调大-XX:SurvivorRatio来看看情况

    • -XX:SurvivorRatio:默认8
      • -XX:SurvivorRatio过大:Eden变大,Survivor变小,minor GC可能减少,但是由于suvivor减小了,所以如果minor GC存活下来的对象大于suvivor,则会直接进入年老代
      • -XX:SurvivorRatio过小:Eden变小,Survivor变大,minor GC可能增多,但是由于suvivor变大了,能够存储更多存活下来的对象,进入年老代的对象可能会减少

    • -XX:MaxTenuringThreshold:默认为15
      • -XX:SurvivorRatio过大:对象在年轻代的存活时间变长,可能在年轻代就被回收掉而不必进入年老代,但是相应的复制的时候survivor区就会被占用更多的空间。
      • -XX:SurvivorRatio过大:对象在年轻代的存活时间变短,可能会早早进入年老代而失去在年轻代被回收的机会,但是相应的复制的时候survivor区也就有更多内存了,这样可能会避免部分大对象直接进入年老代

    • -XX:MaxPermSize==-XX:PermSize
      • 在实际开发中,前台不要使用jsp,使用velocity等模板引擎技术
      • 不要引入无关的jar


3、垃圾收集器选择

企业中最常用的两个组合:(这里由于大部分大型企业用的还是JDK1.6,所以G1不说)

关于下边两组垃圾收集器的详细原理见:第五章 JVM垃圾收集器(1)

  • Parallel Scavenge/Parallel Old
    • 注重吞吐量(吞吐量越大,说明CPU利用率越高)
    • 主要用于处理很多的CPU计算任务而用户交互任务较少的情况
    • 用于让JVM自动调优而我们袖手旁观的情况(-XX:+UseParallelOldGC,-XX:GCTimeRatio,-Xmx,-XX:+UseAdaptiveSizePolicy)
    • -XX:+UseParallelOldGC:指定使用该组合

  • ParNew/CMS
    • 注重STW的缩短(该时间越短,用户体验越好,而且会减少部分请求的请求超时问题
    • -XX:+UseConcMarkSweepGC:指定使用该组合
    • -XX:CMSInitiatingOccupancyFraction:来指定当年老代空间满了多少后(百分比)进行垃圾回收

  • 关于上边两种组合的说明
    • 一般而言,在企业中,机器的CPU数量都比较多,且CPU的计算能力也不会成为瓶颈,所以对于CMS的并发标记与并发清除阶段,会占用CPU资源的问题,其实不是大事儿;而对于Parallel的注重吞吐量的问题也就不是什么大事儿了,毕竟CPU是强大的
    • 所以,ParNew/CMS是首选(在G1不能用的情况下),Parallel Scavenge/Parallel Old只在想让JVM自动管理内存的情况下使用

注意:在实际调优过程中,可以使用jstat、jconsole、visualVM或GC日志的检测数据来调,具体的示例见《深入理解java虚拟机(第二版)》p142对eclipse运行速度的调优。

关于GC日志的参数含义与每一种垃圾收集器的GC日志的格式,查看《深入理解Java虚拟机(第二版)》的P89和《深入分析Java web技术内幕(修订版)》的P224


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章