jvm性能优化入门指南

author author     2022-07-30     261

关键词:

前言

入门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参数
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>

Monitor

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

技术分享

进入区(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毫秒。

 

听说jvm性能优化很难?今天我小试了一把!

...客shuyi.tech,欢迎关注访问。对于Java开发的同学来说,JVM性能优化可以说是比较难掌握的知识点。这不仅因为JVM性能优化需要掌握晦涩难懂的JVM知识,还因为JVM性能优化很难有使用场景。这导致了许多人对JVM性能优化不熟悉,感... 查看详情

jvm性能优化,第2部分:编译器jvm

...家参考学习,如有不足之处,欢迎补充!Java编译器在JVM性能优化系列的第二篇文章中占据中心位置。EvaAndreasson介绍了不同种类的编译器,并比较了客户端,服务器和分层编译的性能结果。最后,她概述了常见的JVM优化,例如消... 查看详情

jvm性能优化

 java应用程序是应用在JVM上的,你们对JVM又有多少了解呢?JVM将内存分为三部分:NEW(年轻代)、Tenured(年老代)、Perm(永久代)。   (1)年轻代:用来存放java分配的新对象。   (2)年老代:经过垃圾... 查看详情

jvm性能调优

  JVM技术图谱 性能调优性能调优包含多个层次,比如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。架构调优和代码调优是JVM调优的基础,其中架构调优是对系统影响最大的。性能调优基本上按照以... 查看详情

性能优化系列三:jvm优化1

一、几个基本概念GCRoots对象都有哪些所有正在运行的线程的栈上的引用变量。所有的全局变量。所有ClassLoader。。。1.SystemClass.2.JNILocal3.JNIGlobal4.ThreadBlock5.BusyMonitor6.JavaLocal7.NativeStack8.Unfinalized9.Unreachable10.JavaStackFrame11.Unknow 查看详情

jvm性能调优入门

...不少应用程序需要对JVM进行额外的配置才能达到其期望的性能要求。现在JVM为了满足各种应用的需要,为程序运行提供了大量的JVM配置选项。不幸的是,针对一个应用程序进行的JVM调优(配置)可能并不适用于另一个应用程序。... 查看详情

spark系列(代码片段)

...《Java并发编程》,《Spring核心知识》《Docker教程》和《JVM性能优化》,都是多年面试总结。欢迎关注【后端精进之路】,轻松阅读全部文章。Java并发编程:Java并发编程系列-(1)并发编程基础Java并发编程系列-(2)线程的并发工具类Ja... 查看详情

第七篇:双管齐下,jvm内部优化与jvm性能调优

目录一、前言二、编译时优化2.1Javac编译器2.2Java语法糖2.2.1泛型和泛型擦除 查看详情

第七篇:双管齐下,jvm内部优化与jvm性能调优

目录一、前言二、编译时优化2.1Javac编译器2.2Java语法糖2.2.1泛型和泛型擦除 查看详情

spark系列(代码片段)

目前已经更新完《Java并发编程》,《JVM性能优化》,《Spring核心知识》《Docker教程》和《Spark基础知识》,都是多年面试总结。欢迎关注【后端精进之路】,轻松阅读全部文章。Java并发编程:Java并发编程系列-(1)并发编程基础Java并... 查看详情

jvm性能调优实战之:一次系统性能瓶颈的寻找过程

玩过性能优化的朋友都清楚,性能优化的关键并不在于怎么进行优化,而在于怎么找到当前系统的性能瓶颈。性能优化分为好几个层次,比如系统层次、算法层次、代码层次...JVM的性能优化被认为是底层优化,门槛较高,精通这... 查看详情

spark性能优化-jvm虚拟机垃圾回收调优

12 34 查看详情

深入jvm内核---jvm性能优化

持久代用来防止类、类的一些常量操作   1.类和接口的全限定名   2、字段的名称和描述符   3、方法和名称和描述符   两个原则   1.一个是将转移到老年代的对象数量降到最少   因为老年代空间上的GC处理会花费更多... 查看详情

jvm优化入门(代码片段)

超详细的Java知识点路线图深入了解JVMJVM的内存模型程序计数器Java虚拟机栈本地方法栈堆区方法区Java对象的内存分配对象回收的算法引用计数算法可达性分析算法堆的分代堆的GC机制JVM参数JVM加载类的过程类加载的具体过程类加... 查看详情

jvm性能优化(代码片段)

1、重新认识JVMJVM的大体物理结构图:如上面架构图所示,JVM分为三个主要子系统:(1)类加载器子系统(ClassLoaderSubsystem);(2)运行时数据区(RuntimeDataArea);࿰ 查看详情

jvm性能参数优化

以上是jdk1.7的默认GC回收器Java的gc回收的类型主要有几种 UseSerialGC,UseConcMarkSweepGC,UseParNewGC,UseParallelGC,UseParallelOldGC,UseG1GC,而这几个参数是如何搭配的,实际上只要看下面的代码就非常清楚 我们把GC分成4种类型1.&nbs... 查看详情

什么是jvm?

...不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是jvm,以及弄清楚jvm的工作原理,个人认为这块还是非常有必要去认真了解及学习的,特别是刚入门或入门不久... 查看详情

java程序设计题库及答案,最全指南

架构筑基大家都知道,性能一直是让程序员比较头疼的问题。当系统架构变得复杂而庞大之后,性能方面就会下降,如果想成为一名优秀的架构师,性能优化就是你必须思考的问题。所以性能优化专题从JVM底层原... 查看详情