JVM调优

3/24/2022 JVM调优

# JVM配置参数-X与-XX的区别

启动JVM时通过指定配置参数来指导虚拟机按照我们的要求提供服务,这一点对大多数的Java程序员来说已经是司空见惯。

在指定配置参数时,会有-X和-XX两种形式,那么它们两者有什么区别呢,今天我想借这篇文章总结一下。

下面是我们的某个Java项目在正式环境上启动JVM时的一个典型命令,在该命令中指定了各种启动参数:

java -Xmx15G \
-Xms10G \
-Xmn3G \
-Xss512k \
-XX:MaxPermSize=512M \
-XX:PermSize=512M \
-XX:+PrintFlagsFinal \
-XX:MaxTenuringThreshold=1 \
-XX:SurvivorRatio=23 \
-XX:TargetSurvivorRatio=80 \
-Xnoclassgc \
-XX:+UseParNewGC \
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=80 \
-XX:ParallelGCThreads=24 \
-XX:ConcGCThreads=24 \
-XX:+CMSParallelRemarkEnabled \
-XX:+CMSScavengeBeforeRemark \
-XX:+ExplicitGCInvokesConcurrent \
-XX:+UseTLAB \
-XX:TLABSize=64K, -verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-XX:+PrintGCApplicationStoppedTime \
-Xloggc:./gc.log

Java HotSpot VM的官方文档中将启动参数分为如下两类:

配置参数 类型 说明 举例
-X non-standard 非标准参数。<br/><br/>这些参数不是虚拟机规范规定的。因此,不是所有VM的实现(如:HotSpot,JRockit,J9等)都支持这些配置参数。 -Xmx、-Xms、-Xmn、-Xss
-XX not-stable 不稳定参数。<br/><br/>这些参数是虚拟机规范中规定的。这些参数指定虚拟机实例在运行时的各种行为,从而对虚拟机的运行时性能有很大影响。 -XX:SurvivorRatio、-XX:+UseParNewGc

补充: -X和-XX两种参数都可能随着JDK版本的变更而发生变化,有些参数可以能会被废弃掉,有些参数的功能会发生改变,但是JDK官方不会通知开发者这些变化,需要使用者注意。

-XX参数被称为不稳定参数,是因为这类参数的设置会引起JVM运行时性能上的差异,配置得当可以提高JVM性能,配置不当则会使JVM出现各种问题, 甚至造成JVM崩溃。

国外有个哥们从HotSpot VM的源码里发现了934个此类型的配置参数,因此能对JVM做出很多组合配置,对JVM的调优也没有统一的标准,需要我们在实践中不断总结经验,并结合实际业务来进行操作,最终找到最适合当前业务的那些配置。

# 一些有用的-XX配置

对于-XX类型的配置选项,虚拟机规范有一些惯例,针对不同的平台虚拟机也会提供不同的默认值。

  • 对于布尔(Boolean)类型的配置选项,通过-XX:+<option>来开启,通过-XX:-<option>来关闭。

  • 对于数字(Numberic)类型的配置选项,通过-XX:<option>=<number>来配置。<number>后面可以携带单位字母,比如: 'k'或者'K'代表千字节,'m'或者'M'代表兆字节,'g'或者'G'代表千兆字节。

  • 对于字符串(String)类型的配置选项,通过-XX:<option>=<string>来配置。这种配置通过用来指定文件,路径或者命令列表。

# 参考

<http://www.nituchao.com/jvm-tuning/8.html>

# java -X命令

C:\Users\wale>java -X
    -Xmixed           混合模式执行(默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:\<; 分隔的目录和 zip/jar 文件>
                      设置引导类和资源的搜索路径
    -Xbootclasspath/a:\<; 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:\<; 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc        禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:\<file>    将 GC 状态记录在文件中(带时间戳)
    -Xbatch           禁用后台编译
    -Xms\<size>        设置初始 Java 堆大小
    -Xmx\<size>        设置最大 Java 堆大小
    -Xss\<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 分析数据
    -Xfuture          启用最严格的检查,预计会成为将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用(请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据(默认)
    -Xshare:on        要求使用共享类数据,否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:system
                      (仅限 Linux)显示系统或容器
                      配置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项。如有更改,恕不另行通知。

# 堆栈相关参数

  • -Xms

堆最小值

  • -Xmx

堆最大堆值。-Xms与-Xmx 的单位默认字节都是以k、m做单位的。

通常这两个配置参数相等,避免每次空间不足,动态扩容带来的影响。

  • -Xss

每个线程池的堆栈大小。在jdk5以上的版本,每个线程堆栈大小为1m,jdk5以前的版本是每个线程池大小为256k。一般在相同物理内存下,如果减少-xss值会产生更大的线程数,但不同的操作系统对进程内线程数是有限制的,是不能无限生成。

  • -Xmn

新生代大小

  • -XX:NewRatio

设置新生代与老年代比值,-XX:NewRatio=4 表示新生代与老年代所占比例为1:4 ,新生代占比整个堆的五分之一。如果设置了-Xmn的情况下,该参数是不需要在设置的。

  • -XX:PermSize

设置永久代初始值,默认是物理内存的六十四分之一

  • -XX:MaxPermSize

设置永久代最大值,默认是物理内存的四分之一

  • -XX:MaxTenuringThreshold

新生代中对象存活次数,默认15。(若对象在eden区,经历一次MinorGC后还活着,则被移动到Survior区,年龄加1。以后,对象每次经历MinorGC,年龄都加1。达到阀值,则移入老年代)

  • -XX:SurvivorRatio

Eden区与Subrvivor区大小的比值,如果设置为8,两个Subrvivor区与一个Eden区的比值为2:8,一个Survivor区占整个新生代的十分之一

  • -XX:+UseFastAccessorMethods

原始类型快速优化

  • -XX:+AggressiveOpts

编译速度加快

  • -XX:PretenureSizeThreshold

对象超过多大值时直接在老年代中分配

# 垃圾回收器的JVM参数

  • -XX:+UseSerialGC

串行垃圾回收,现在基本很少使用。

  • -XX:+UseParNewGC

新生代使用并行,老年代使用串行;

  • -XX:+UseConcMarkSweepGC

新生代使用并行,老年代使用CMS(一般都是使用这种方式),CMS是Concurrent Mark Sweep的缩写,并发标记清除,一看就是老年代的算法,所以,它可以作为老年代的垃圾回收器。CMS不是独占式的,它关注停顿时间

  • -XX:ParallelGCThreads

指定并行的垃圾回收线程的数量,最好等于CPU数量

  • -XX:+DisableExplicitGC

禁用System.gc(),因为它会触发Full GC,这是很浪费性能的,JVM会在需要GC的时候自己触发GC。

  • -XX:CMSFullGCsBeforeCompaction

在多少次GC后进行内存压缩,这个是因为并行收集器不对内存空间进行压缩的,所以运行一段时间后会产生很多碎片,使得运行效率降低。

  • -XX:+CMSParallelRemarkEnabled

降低标记停顿

  • -XX:+UseCMSCompactAtFullCollection

在每一次Full GC时对老年代区域碎片整理,因为CMS是不会移动内存的,因此会非常容易出现碎片导致内存不够用的

  • -XX:+UseCmsInitiatingOccupancyOnly

使用手动触发或者自定义触发cms 收集,同时也会禁止hostspot 自行触发CMS GC

  • -XX:CMSInitiatingOccupancyFraction

使用CMS作为垃圾回收,使用70%后开始CMS收集

  • -XX:CMSInitiatingPermOccupancyFraction

设置perm gen使用达到多少%比时触发垃圾回收,默认是92%

  • -XX:+CMSIncrementalMode

设置为增量模式

  • -XX:+CmsClassUnloadingEnabled

CMS是不会默认对永久代进行垃圾回收的,设置此参数则是开启

  • -XX:+PrintGCDetails

开启详细GC日志模式,日志的格式是和所使用的算法有关

  • -XX:+PrintGCDateStamps

将时间和日期也加入到GC日志中

# JVM调优

# JVM调优有哪些工具?

# jstat-jvm状态信息

jstat可以打印出当前JVM运行的各种状态信息,例如新生代内存使用情况,老年代内存使用情况,以及垃圾回收的时间。Minor GC发生总次数,总耗时,Full GC发生总次数,总耗时。(jmap -heap命令也可以打印出堆中各个分区的内存使用情况,但是不能定时监测,持续打印。例如每1s打印当前的堆中各个分区的内存使用情况,一直打印100次。)

//5828是java进程id,1000是打印间隔,每1000毫秒打印一次,100是总共打印100次
jstat -gc 5828 1000 100

打印结果如下:

img

各个参数的含义如下:

  • S0C 新生代中第一个survivor(幸存区)的总容量 (字节)

  • S1C新生代中第二个survivor(幸存区)的总容量 (字节)

  • S0U 新生代中第一个survivor(幸存区)目前已使用空间 (字节)

  • S1U 新生代中第二个survivor(幸存区)目前已使用空间 (字节)

  • EC 新生代中Eden区的总容量 (字节)

  • EU 新生代中Eden区目前已使用空间 (字节)

  • OC 老年代的总容量 (字节)

  • OU 老年代代目前已使用空间 (字节)

  • YGC 目前新生代垃圾回收总次数

  • YGCT 目前新生代垃圾回收总消耗时间

  • FGC 目前full gc次数总次数

  • FGCT 目前full gc次数总耗时,单位是秒

  • GCT 垃圾回收总耗时

一般还可以使用jstat -gcutil \<pid>:统计gc信息,这样打印出来的结果是百分比,而不是实际使用的空间,例如jstat -gcutil 1 1000 100

例如,S0代表 新生代中第一个survivor区的空间使用了73.19%,E代表新生代Eden区使用了51%,O代表老年代食堂了98%

img

参数 描述
S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
s1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E 年轻代中Eden已使用的占当前容量百分比
O old代已使用的占当前容量百分比
M 元空间(MetaspaceSize)已使用的占当前容量百分比
CCS 压缩使用比例
YGC 年轻代垃圾回收次数
FGC 老年代垃圾回收次数
FGCT 老年代垃圾回收消耗时间
GCT 垃圾回收消耗总时间

img

# jstack-jvm的线程快照

jstack可以生成当前JVM的线程快照,也就是当前每个线程当前的状态及正在执行的方法,锁相关的信息。jstack -l 进程id,-l代表除了堆栈信息外,还会打印锁的附加信息。jstack还会检测出死锁信息。一般可以用于定位线程长时间停顿,线程间死锁等问题。

例如在下面的例子中,第一个线程获取到lock1,再去获取lock2,第二个线程先获取到lock2,然后再去获取lock1。每个线程都只获得了一个锁,同时在获取另外一个锁,就会进入死锁状态。

public static void main(String[] args) {
        final Integer lock1 = new Integer(1);
        final String  lock2 = new String();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println("线程1获得了lock1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1休眠结束");
                    System.out.println("线程1开始尝试获取lock2");
                    synchronized (lock2) {
                        System.out.println("线程1获得了lock2");
                    }
                }
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                synchronized (lock2) {
                    System.out.println("线程2获得了lock2");
                    System.out.println("线程2开始尝试获取lock1");
                    synchronized (lock1) {
                        System.out.println("线程2获得了lock2");
                    }
                }
            }
        });
    }

使用jstack -l 进程id就可以打印出当前的线程信息

img

以及各个线程的状态,执行的方法(pool-1-thread-1和pool-1-thread-2分别代表线程池的第一个线程和第二个线程):

img

# jmap

# jmap -heap-堆栈快照

这个命令可以生成当前堆栈快照。使用 jmap -heap 进程id可以打印出当前堆各分区内存使用情况的情况,新生代(Eden区,To Survivor区,From Survivor区),老年代区的内存使用情况。

使用jmap -heap查看内存使用情况的案例

img

# jmap -histo

jmap -histo 进程id 打印出当前堆中的对象统计信息,包括类名,每个类的实例数量,总占用内存大小。

instances列:表示当前类有多少个实例。
bytes列:说明当前类的实例总共占用了多少个字节
class name列:表示的就是当前类的名称,class name 对于基本数据类型,使用的是缩写。解读:B代表byte ,C代表char ,D代表double, F代表float,I代表int,J代表long,Z代表boolean 
前边有[代表数组,[I 就相当于int[] 
对象数组用`[L+类名`表示

img

# jmap -dump

使用jmap -dump:format=b,file=dump.hprof 进程id可以生成当前的堆栈快照,堆快照和对象统计信息,对生成的堆快照进行分析,可以分析堆中对象所占用内存的情况,检查大对象等。执行jvisualvm命令打开使用Java自带的工具Java VisualVM来打开堆栈快照文件,进行分析。可以用于排查内存溢出,内存泄露问题。在Java VisualVM里面可以看到每个类的实例对象占用的内存大小,以及持有这个对象的实例所在的类等等信息。

也可以配置启动时的JVM参数,让发送内存溢出时,自动生成堆栈快照文件。

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/liuke/jvmlogs/

使用jmap -dump:format=b,file=/存放路径/heapdump.hprof 进程id就可以得到堆转储文件,然后执行jvisualvm命令就可以打开JDK自带的jvisualvm软件。

例如在这个例子中会造成OOM问题,通过生成heapdump.hprof文件,可以使用jvisualvm查看造成OOM问题的具体代码位置。

public class Test018 {

    ArrayList\<TestObject> arrayList = new ArrayList\<TestObject>();

    public static void main(String[] args) {
        Test018 test018 =new Test018();
        Random random = new Random();
        for (int i = 0; i \< 10000000; i++) {
            TestObject testObject = new TestObject();
            test018.arrayList.add(testObject);
        }
    }
    private static class TestObject {
        public byte[] placeholder = new byte[64 * 1024];
    }
}

-Xms20m -Xmx20m -verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/存放路径/heapdump.hprof

造成OOM问题的代码位置:

img

堆内对象列表

img

占用内存最多的实例对象就是这个placeholder对象

img

# MAT

MAT主要可以用于分析内存泄露,可以查询dump堆转储文件中的对象列表,以及潜在的内存泄露的对象。

通过导入hprof文件,主页会展示潜在的内存泄露问题,比如下面这个例子中

public class Test018 {
    static ArrayList\<TestObject> arrayList = new ArrayList\<TestObject>();
    public static void main(String[] args) {
        Random random = new Random();
        for (int i = 0; i \< 10000000; i++) {
            TestObject testObject = new TestObject();
            Test018.arrayList.add(testObject);
        }
    }
    private static class TestObject {
        public byte[] placeholder = new byte[64 * 1024];
    }
}

在详情页面Shortest Paths To the Accumulation Point表示GC root对象到内存消耗聚集点的最短路径,内存聚集点的意思就是占用了大量内存的对象,也就是可能发生; 内存泄露的对象。

img

然后在主页点击Histogram,进入Histogram页面可以看到对象列表,with incomming references 也就是可以查看所有对这个对象的引用(思路一般优先看占用内存最大对象;其次看数量最多的对象。)。我们这个例子中主要是byte[]数组分配了占用了大量的内存空间,而byte[]主要来自于Test018类的静态变量arrayList的每个TestObject类型的元素的placeholder属性。

img

img

同时可以点击 内存快照对比 功能对两个dump文件进行对比,判断两个dump文件生成间隔期间,各个对象的数量变化,以此来判断内存泄露问题。

img

img

Last Updated: 3/28/2022, 9:29:49 PM