2016年03月30日 Java Coding 浏览(67) 收藏

JVM 虚拟机性能优化入门指南

前言

入门JVM垃圾回收机制后,接下来可以学习性能调优了。主要有两部分内容:

  • JDK工具的使用。

  • 调优策略。

兵器谱

jps

列出正在运行的虚拟机进程,用法如下:

jps [-option] [hostid]

 


选项作用
q只输出LVMID,省略主类的名称
m输出main method的参数
l输出完全的包名,应用主类名,jar的完全路径名
v输出jvm参数

jstat

监视虚拟机运行状态信息,使用方式:

jstat -<option> <pid> [interval[s|ms]]

 


选项作用
gc输出每个堆区域的当前可用空间以及已用空间,GC执行的总次数,GC操作累计所花费的时间。
gccapactiy输出每个堆区域的最小空间限制(ms)/最大空间限制(mx),当前大小,每个区域之上执行GC的次数。(不输出当前已用空间以及GC执行时间)。
gccause输出-gcutil提供的信息以及最后一次执行GC的发生原因和当前所执行的GC的发生原因。
gcnew输出新生代空间的GC性能数据。
gcnewcapacity输出新生代空间的大小的统计数据。
gcold输出老年代空间的GC性能数据。
gcoldcapacity输出老年代空间的大小的统计数据。
gcpermcapacity输出持久带空间的大小的统计数据。
gcutil输出每个堆区域使用占比,以及GC执行的总次数和GC操作所花费的事件。

比如:

jstat -gc 28389 1s

 


每隔1秒输出一次JVM运行信息:

S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
52416.0 52416.0 4744.9  0.0   419456.0 28180.6  2621440.0   439372.6  131072.0 33564.8 160472 1760.603  61      2.731 1763.334

 


说明jstat参数
S0CSurvivor0空间的大小。单位KB。-gc -gccapacity -gcnew -gcnewcapacity
S1CSurvivor1空间的大小。单位KB。-gc -gccapacity -gcnew -gcnewcapacity
S0USurvivor0已用空间的大小。单位KB。-gc -gcnew
S1USurvivor1已用空间的大小。单位KB。-gc -gcnew
ECEden空间的大小。单位KB。-gc -gccapacity -gcnew -gcnewcapacity
EUEden已用空间的大小。单位KB。-gc-gcnew
OC老年代空间的大小。单位KB。-gc -gccapacity -gcold -gcoldcapacity
OU老年代已用空间的大小。单位KB。-gc -gcold
PC持久代空间的大小。单位KB。-gc -gccapacity -gcold -gcoldcapacity -gcpermcapacity
PU持久代已用空间的大小。单位KB。-gc -gcold
YGC新生代空间GC时间发生的次数。-gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
YGCT新生代GC处理花费的时间。-gc-gcnew-gcutil-gccause
FGCfull GC发生的次数。-gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
FGCTfull GC操作花费的时间。-gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
GCTGC操作花费的总时间。-gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
NGCMN新生代最小空间容量,单位KB。-gccapacity -gcnewcapacity
NGCMX新生代最大空间容量,单位KB。-gccapacity -gcnewcapacity
NGC新生代当前空间容量,单位KB。-gccapacity -gcnewcapacity
OGCMN老年代最小空间容量,单位KB。-gccapacity-gcoldcapacity
OGCMX老年代最大空间容量,单位KB。-gccapacity-gcoldcapacity
OGC老年代当前空间容量制,单位KB。-gccapacity -gcoldcapacity
PGCMN持久代最小空间容量,单位KB。-gccapacity -gcpermcapacity
PGCMX持久代最大空间容量,单位KB。-gccapacity -gcpermcapacity
PGC持久代当前空间容量,单位KB。-gccapacity -gcpermcapacity
PC持久代当前空间大小,单位KB。-gccapacity-gcpermcapacity
PU持久代当前已用空间大小,单位KB。-gc -gcold
LGCC最后一次GC发生的原因。-gccause
GCC当前GC发生的原因。-gccause
TT老年化阈值。被移动到老年代之前,在新生代空存活的次数。-gcnew
MTT最大老年化阈值。被移动到老年代之前,在新生代空存活的次数。-gcnew
DSS幸存者区所需空间大小,单位KB。-gcnew

jmap

生成堆存储快照,使用方式:

jmap [ -option ] <pid>

 


选项作用
dump生成堆存储快照,格式为:-dump:[live, ]format=b, file=<filename>,live说明是否只dump出存活的对象。
heap显示java堆详细信息,如使用那种回收器、参数配置、分代状况等。
histo显示堆中对象统计信息,包括类、实例数量、合计容量。

jstack

生成虚拟机当前时刻的线程快照,帮助定位线程出现长时间停顿的原因,用法:

jstack <pid>

 


Monitor

Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:

JVM性能优化入门指南-图片-1

进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁,但并未得到。

拥有者(The Owner):表示线程成功竞争到对象锁。

等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。

线程状态

  • NEW,未启动的。不会出现在Dump中。

  • RUNNABLE,在虚拟机内执行的。

  • BLOCKED,等待获得监视器锁。

  • WATING,无限期等待另一个线程执行特定操作。

  • TIMED_WATING,有时限的等待另一个线程的特定操作。

  • TERMINATED,已退出的。

举个例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
* Hello world!
* 
*/
public class App {

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        Thread t1 = new Thread(task);
        t1.setName("t1");
        Thread t2 = new Thread(task);
            t2.setName("t2");
            t1.start();
            t2.start();
   }

}

class MyTask implements Runnable {

    private Integer mutex;

    public MyTask() {
        mutex = 1;
    }

    @Override
    public void run() {
        synchronized (mutex) {
            while(true) {
                System.out.println(Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
             }
         }
    }

}

 


线程状态:

"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000]
java.lang.Thread.State: BLOCKED (on object monitor)
 at com.jiuyan.mountain.test.MyTask.run(App.java:35)
 - waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer)
 at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
 at java.lang.Thread.sleep(Native Method)

 


t1没有抢到锁,所以显示BLOCKED。t2抢到了锁,但是处于睡眠中,所以显示TIMED_WAITING,有限等待某个条件来唤醒。
把睡眠的代码去掉,线程状态变成了:

"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000]
java.lang.Thread.State: BLOCKED (on object monitor)
 at com.jiuyan.mountain.test.MyTask.run(App.java:35)
 - waiting to lock <0x0000000784206650> (a java.lang.Integer)
 at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000]
java.lang.Thread.State: RUNNABLE
 at java.io.FileOutputStream.writeBytes(Native Method)

 


t1显示RUNNABLE,说明正在运行,这里需要额外说明一下,如果这个线程正在查询数据库,但是数据库发生死锁,虽然线程显示在运行,实际上并没有工作,对于IO型的线程别只用线程状态来判断工作是否正常。
把MyTask的代码小改一下,线程拿到锁之后执行wait,释放锁,进入等待区。

public void run() {
    synchronized (mutex) {
        if(mutex == 1) {
            try {
                mutex.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
     }
 }

 


线程状态如下:

"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000]
java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)

"t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000]
java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)

 


两个线程都显示WAITING,这次是无限期的,需要重新获得锁,所以后面跟了on object monitor

再来个死锁的例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
* Hello world!
* 
*/
public class App {

     public static void main(String[] args) throws InterruptedException {
           MyTask task1 = new MyTask(true);
           MyTask task2 = new MyTask(false);
           Thread t1 = new Thread(task1);
           t1.setName("t1");
           Thread t2 = new Thread(task2);
           t2.setName("t2");
           t1.start();
           t2.start();
     }

}

class MyTask implements Runnable {

     private boolean flag;

     public MyTask(boolean flag) {
           this.flag = flag;
     }

     @Override
     public void run() {
           if(flag) {
                 synchronized (Mutex.mutex1) {
                       try {
                             TimeUnit.SECONDS.sleep(1);
                       } catch (InterruptedException e) {
                             // TODO Auto-generated catch block
                             e.printStackTrace();
                       }
                       synchronized (Mutex.mutex2) {
                             System.out.println("ok");
                       }
                 }
           } else {
                 synchronized (Mutex.mutex2) {
                       try {
                             TimeUnit.SECONDS.sleep(1);
                       } catch (InterruptedException e) {
                             // TODO Auto-generated catch block
                             e.printStackTrace();
                       }
                       synchronized (Mutex.mutex1) {
                             System.out.println("ok");
                       }
                 }
           }
     }

}

class Mutex {
    public static Integer mutex1 = 1;
    public static Integer mutex2 = 2;
}

 


线程状态:

"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000]
java.lang.Thread.State: BLOCKED (on object monitor)
 at com.jiuyan.mountain.test.MyTask.run(App.java:55)
 - waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer)
 - locked <0x00000007d6c45be8> (a java.lang.Integer)
 at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000]
java.lang.Thread.State: BLOCKED (on object monitor)
 at com.jiuyan.mountain.test.MyTask.run(App.java:43)
 - waiting to lock <0x00000007d6c45be8> (a java.lang.Integer)
 - locked <0x00000007d6c45bd8> (a java.lang.Integer)
 at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer),
which is held by "t2"

 


这个有点像哲学家就餐问题,每个线程都持有对方需要的锁,那就运行不下去了。

调优策略

两个基本原则:

  • 将转移到老年代的对象数量降到最少。

  • 减少Full GC的执行时间。目标是Minor GC时间在100ms以内,Full GC时间在1s以内。

主要调优参数:

设定堆内存大小,这是最基本的。

  1. -Xms:启动JVM时的堆内存空间。

  2. -Xmx:堆内存最大限制。

设定新生代大小。

新生代不宜太小,否则会有大量对象涌入老年代。

  1. -XX:NewRatio:新生代和老年代的占比。

  2. -XX:NewSize:新生代空间。

  3. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。

  4. -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。

设定垃圾回收器

年轻代:-XX:+UseParNewGC。

老年代:-XX:+UseConcMarkSweepGC。

CMS可以将STW时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要比Parallel GC长很多。

G1采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

赞一下 0 人赞了本文