JVM性能调优入门

Java程序员进阶三条必经之路:数据库、虚拟机、异步通信。

前言

入门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运行信息:

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参数
S0C Survivor0空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S1C Survivor1空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
S0U Survivor0已用空间的大小。单位KB。 -gc -gcnew
S1U Survivor1已用空间的大小。单位KB。 -gc -gcnew
EC Eden空间的大小。单位KB。 -gc -gccapacity -gcnew -gcnewcapacity
EU Eden已用空间的大小。单位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
FGC full GC发生的次数。 -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
FGCT full GC操作花费的时间。 -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause
GCT GC操作花费的总时间。 -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>
  1. Monitor

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

Paste_Image.png

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

  1. 线程状态

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

  3. RUNNABLE,在虚拟机内执行的。

  4. BLOCKED,等待获得监视器锁。

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

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

  7. 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. 设定堆内存大小,这是最基本的。
  2. -Xms:启动JVM时的堆内存空间。
  3. -Xmx:堆内存最大限制。
  4. 设定新生代大小。
    新生代不宜太小,否则会有大量对象涌入老年代。
  5. -XX:NewRatio:新生代和老年代的占比。
  6. -XX:NewSize:新生代空间。
  7. -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。
  8. -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。
  9. 设定垃圾回收器
    年轻代:-XX:+UseParNewGC。
    老年代:-XX:+UseConcMarkSweepGC。
    CMS可以将STW时间降到最低,但是不对内存进行压缩,有可能出现“并行模式失败”。比如老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。这时候会触发压缩处理,但是CMS GC模式下的压缩处理时间要比Parallel GC长很多。
    G1采用”标记-整理“算法,解决了内存碎片问题,建立了可预测的停顿时间类型,能让使用者指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容