JVM性能调优入门

入门JVM垃圾回收机制后,接下来可以学习性能调优了。主要有两部分内容: JDK工具的使用。调优策略。兵器谱jps 列出正在运行的虚拟机进程,用法如下: jps [-option] [hostid] q只输出LVMID...

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

  1. JDK工具的使用。
  2. 调优策略。

兵器谱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运行信息:

attachments-2016-09-NpJF6Jfo57ebedd8e665


map

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

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

jstack

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

jstack <pid>

  1.  Monitor

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

attachments-2016-09-vM9s7L0s57ebeeb26e67

  1. Paste_Image,png

    进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁,但并未得到。
    拥有者(The Owner):表示线程成功竞争到对象锁。
    等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。

  2. 线程状态

    1. NEW,未启动的。不会出现在Dump中。
    2. RUNNABLE,在虚拟机内执行的。
    3. BLOCKED,等待获得监视器锁。
    4. WATING,无限期等待另一个线程执行特定操作。
    5. TIMED_WATING,有时限的等待另一个线程的特定操作。
    6. 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"

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

调优策略

两个基本原则:

  1. 将转移到老年代的对象数量降到最少。
  2. 减少Full GC的执行时间。目标是Minor GC时间在100ms以内,Full GC时间在1s以内。

主要调优参数:

  1. 设定堆内存大小,这是最基本的。
    1. -Xms:启动JVM时的堆内存空间。
    2. -Xmx:堆内存最大限制。
  2. 设定新生代大小。
    新生代不宜太小,否则会有大量对象涌入老年代。
    1. -XX:NewRatio:新生代和老年代的占比。
    2. -XX:NewSize:新生代空间。
    3. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。
    4. -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。
  3. 设定垃圾回收器
    年轻代:-XX:+UseParNewGC。
    老年代:-XX:+UseConcMarkSweepGC。
    CMS可以将STW时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要比Parallel GC长很多。
    G1采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
  • 发表于 2016-09-29 00:20
  • 阅读 ( 472 )

0 条评论

请先 登录 后评论
小A
小A

29 篇文章

作家榜 »

  1. shitian 662 文章
  2. 石天 437 文章
  3. 每天惠23 33 文章
  4. 小A 29 文章