Java 工具

jps: JVM Process Status Tool, 显示指定系统内所有的 HotSpot 虚拟机进程。

jstat: jstat (JVM statistics Monitoring) 是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。

jmap: jmap (JVM Memory Map) 命令用于生成 heap dump 文件,如果不使用这个命令,还阔以使用-XX:+HeapDumpOnOutOfMemoryError 参数来让虚拟机出现 OOM 的时候·自动生成 dump 文件。
jmap 不仅能生成 dump 文件,还阔以查询 finalize 执行队列、Java 堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

jhat: jhat (VM Heap Analysis Tool) 命令是与 jmap 搭配使用,用来分析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为 jhat 是一个耗时并且耗费硬件资源的过程,一般把服务器生成的 dump 文件复制到本地或其他机器上进行分析。

jstack: jstack 用于生成 java 虚拟机当前时刻的线程快照。jstack 来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。如果 java 程序崩溃生成 core 文件,jstack 工具可以用来获得 core 文件的 java stack 和 native stack 的信息,从而可以轻松地知道 java 程序是如何崩溃和在程序何处发生问题。

关于 JVM 调优的一些方面

所有线程共享数据区大小=新生代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为 64m。所以 java 堆中增大年轻代后,将会减小年老代大小(因为老年代的清理是使用 fullgc,所以老年代过小的话反而是会增多 fullgc 的)。此值对系统性能影响较大,Sun 官方推荐配置为 java 堆的 3/8。

调整最大堆内存和最小堆内存

-Xmx –Xms :指定 java 堆最大值(默认值是物理内存的 1/4(<1GB))和初始 java 堆最小值(默认值是物理内存的 1/64(<1GB))

默认(MinHeapFreeRatio 参数可以调整)空余堆内存小于 40%时,JVM 就会增大堆直到-Xmx 的最大限制.,默认(MaxHeapFreeRatio 参数可以调整)空余堆内存大于 70%时,JVM 会减少堆直到 -Xms 的最小限制。简单点来说,你不停地往堆内存里面丢数据,等它剩余大小小于 40%了,JVM 就会动态申请内存空间不过会小于-Xmx,如果剩余大小大于 70%,又会动态缩小不过不会小于–Xms。就这么简单。

开发过程中,通常会将 -Xms 与 -Xmx 两个参数配置成相同的值,其目的是为了能够在 java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。

我们执行下面的代码

System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");    //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");  //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");  //当前可用的总空间

注意:此处设置的是 Java 堆大小,也就是新生代大小 + 老年代大小

设置一个 VM options 的参数

-Xmx20m -Xms5m -XX:+PrintGCDetails

再次启动 main 方法

这里 GC 弹出了一个 Allocation Failure 分配失败,这个事情发生在 PSYoungGen,也就是年轻代中

这时候申请到的内存为 18M,空闲内存为 4.214195251464844M

我们此时创建一个字节数组看看,执行下面的代码

byte[] b = new byte[1 * 1024 * 1024];
System.out.println("分配了1M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");  //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");  //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");

此时 free memory 就又缩水了,不过 total memory 是没有变化的。Java 会尽可能将 total mem 的值维持在最小堆内存大小

byte[] b = new byte[10 * 1024 * 1024];
System.out.println("分配了10M空间给数组");
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");  //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");  //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");  //当前可用的总空间

这时候我们创建了一个 10M 的字节数据,这时候最小堆内存是顶不住的。我们会发现现在的 total memory 已经变成了 15M,这就是已经申请了一次内存的结果。

此时我们再跑一下这个代码

System.gc();
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");    //系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");  //系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");  //当前可用的总空间

此时我们手动执行了一次 fullgc,此时 total memory 的内存空间又变回 5.5M 了,此时又是把申请的内存释放掉的结果。

调整新生代和老年代的比值

-XX:NewRatio --- 新生代(eden+2*Survivor)和老年代(不包含永久区)的比值

例如:-XX:NewRatio=4,表示新生代: 老年代=1:4,即新生代占整个堆的 1/5。在 Xms=Xmx 并且设置了 Xmn 的情况下,该参数不需要进行设置。

调整 Survivor 区和 Eden 区的比值

-XX:SurvivorRatio (幸存代)--- 设置两个 Survivor 区和 eden 的比值

例如:8,表示两个 Survivor:eden=2:8,即一个 Survivor 占年轻代的 1/10

设置新生代的大小

-XX:NewSize=N --- 设置年轻代大小
-XX:MaxNewSize=N --- 设置年轻代最大值
-Xmn256m --- 设置新生代 256MB 内存(newsize=maxnewsize)

可以通过设置不同参数来测试不同的情况,反正最优解当然就是官方的 Eden 和 Survivor 的占比为 8 :1: 1,然后在刚刚介绍这些参数的时候都已经附带了一些说明,感兴趣的也可以看看。反正最大堆内存和最小堆内存如果数值不同会导致多次的 gc,需要注意。

小总结

根据实际事情调整新生代和幸存代的大小,官方推荐新生代占 java 堆的 3/8,幸存代占新生代的 1/10

在 OOM 时,记得 Dump 出堆,确保可以排查现场问题,通过下面命令你可以输出一个.dump 文件,这个文件可以使用 VisualVM 或者 Java 自带的 Java VisualVM 工具。

-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志路径

一般我们也可以通过编写脚本的方式来让 OOM 出现时给我们报个信,可以通过发送邮件或者重启程序等来解决。

永久区的设置

-XX:PermSize=N -XX:MaxPermSize=N

初始空间(默认为物理内存的 1/64)和最大空间(默认为物理内存的 1/4)。也就是说,jvm 启动时,永久区一开始就占用了 PermSize 大小的空间,如果空间还不够,可以继续扩展,但是不能超过 MaxPermSize,否则会 OutOfMemoryError。

tips:如果堆空间没有用完也抛出了 OOM,有可能是永久区导致的。堆空间实际占用非常少,但是永久区溢出一样抛出 OOM。

元空间的设置

使用元空间后,若没有指定元空间的大小,那么随着类的创建,虚拟机会耗尽所有可用的系统内存。

-XX:MetaspaceSize=N -XX:MaxMetaspaceSize=N

垃圾回收参数

-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParNewGC
-XX:+UseG1GC

GC 记录

-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=< number of log files >
-XX:GCLogFileSize=< file size >[ unit ]
-Xloggc:/path/to/gc.log

JVM 的栈参数调优

调整每个线程栈空间的大小

可以通过 -Xss :调整每个线程栈空间的大小

JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。在相同物理内存下, 减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右

设置线程栈的大小

-XXThreadStackSize:设置线程栈的大小(0 means use default stack size)

这些参数都是可以通过自己编写程序去简单测试的,这里碍于篇幅问题就不再提供 demo 了

(可以直接跳过了)JVM 其他参数介绍

形形色色的参数很多,就不会说把所有都扯个遍了,因为大家其实也不会说一定要去深究到底。

设置内存页的大小

-XXThreadStackSize:
    设置内存页的大小,不可设置过大,会影响 Perm 的大小

设置原始类型的快速优化

-XX:+UseFastAccessorMethods:
    设置原始类型的快速优化

设置关闭手动 GC

-XX:+DisableExplicitGC:
    设置关闭 System.gc()(这个参数需要严格的测试)

设置垃圾最大年龄

-XX:MaxTenuringThreshold
    设置垃圾最大年龄。如果设置为 0 的话, 则年轻代对象不经过 Survivor 区, 直接进入年老代.
    对于年老代比较多的应用, 可以提高效率。如果将此值设置为一个较大值,
    则年轻代对象会在 Survivor 区进行多次复制, 这样可以增加对象再年轻代的存活时间,
    增加在年轻代即被回收的概率。该参数只有在串行 GC 时才有效.

加快编译速度

-XX:+AggressiveOpts

加快编译速度

改善锁机制性能

-XX:+UseBiasedLocking

禁用垃圾回收

-Xnoclassgc

设置堆空间存活时间

-XX:SoftRefLRUPolicyMSPerMB
    设置每兆堆空闲空间中 SoftReference 的存活时间,默认值是 1s。

设置对象直接分配在老年代

-XX:PretenureSizeThreshold
    设置对象超过多大时直接在老年代分配,默认值是 0。

设置 TLAB 占 eden 区的比例

-XX:TLABWasteTargetPercent
    设置 TLAB 占 eden 区的百分比,默认值是 1% 。

4.8.11 设置是否优先 YGC

-XX:+CollectGen0First
    设置 FullGC 时是否先 YGC,默认值是 false。

JVM 的常用参数

参数名称含义默认值说明
-Xms初始堆大小物理内存的 1/64(<1GB)默认(MinHeapFreeRatio 参数可以调整)空余堆内存小于 40%时,JVM 就会增大堆直到-Xmx 的最大限制.
-Xmx最大堆大小物理内存的 1/4(<1GB)默认(MaxHeapFreeRatio 参数可以调整)空余堆内存大于 70%时,JVM 会减少堆直到 -Xms 的最小限制
-Xmn年轻代大小(1.4or lator)注意:此处的大小是(eden+ 2 survivor space). 与 jmap -heap 中显示的 New gen 是不同的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小. 增大年轻代后, 将会减小年老代大小. 此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8
-XX:NewSize设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize年轻代最大值(for 1.3/1.4)
-XX:PermSize设置持久代(perm gen)初始值物理内存的 1/64
-XX:MaxPermSize设置持久代最大值物理内存的 1/4
-Xss每个线程的堆栈大小JDK5.0 以后每个线程堆栈大小为 1M, 以前每个线程堆栈大小为 256K. 根据应用的线程所需内存大小进行调整. 在相同物理内存下, 减小这个值能生成更多的线程. 但是操作系统对一个进程内的线程数还是有限制的, 不能无限生成, 经验值在 3000~5000 左右一般小的应用,如果栈不是很深,应该是 128k 够用的大的应用建议使用 256k。这个选项对性能影响比较大,需要严格的测试。(校长)和 threadstacksize 选项解释很类似, 官方文档似乎没有解释, 在论坛中有这样一句话:-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了
-XX:NewRatio年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)-XX:NewRatio=4 表示年轻代与年老代所占比值为 1:4, 年轻代占整个堆栈的 1/5Xms=Xmx 并且设置了 Xmn 的情况下,该参数不需要进行设置。
-XX:SurvivorRatioEden 区与 Survivor 区的大小比值设置为 8, 则两个 Survivor 区与一个 Eden 区的比值为 2:8, 一个 Survivor 区占整个年轻代的 1/10
-XX:+DisableExplicitGC关闭 System.gc()这个参数需要严格的测试
-XX:PretenureSizeThreshold对象超过多大是直接在旧生代分配0单位字节新生代采用 Parallel ScavengeGC 时无效另一种直接在旧生代分配的情况是大的数组对象, 且数组中无外部引用对象.
-XX:ParallelGCThreads并行收集器的线程数此值最好配置与处理器数目相等同样适用于 CMS
-XX:MaxGCPauseMillis每次年轻代垃圾回收的最长时间(最大暂停时间)如果无法满足此时间,JVM 会自动调整年轻代大小, 以满足此值.

其实还有一些打印及 CMS 方面的参数,这里就不以一一列举了

推荐阅读