JVM GC优化初探

JVM内存模型

jvm_memory_structure

堆内存&非堆内存

JVM内存分为堆内存(Heap Memory)和非堆内存(Non-Heap Memory)。
官方的解释为: Java虚拟机具有一个堆,堆是运行时的数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的,JVM中堆之外的内存称为非堆内存。简单来说堆是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

年轻代&老年代&永久代&元空间

①年轻代(Young Generation): 大多数对象创建于年轻代中,其中大部分对象生命周期较短。
年轻代分三个区域,一个Eden(新生代)区,两个大小相同的Survivor区。大部分对象于Eden区中生成,当Eden区满时,仍存活的对象将被复制到两个Survivor区(中的一个)。当这个Survivor区满时,此区存活且不满足“晋升”条件的对象将被复制到另外一个Survivor区。对象每经历一次Minor GC(使用复制算法),年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。晋升年龄阈值”通过参数MaxTenuringThreshold设定, 不同回收器默认值不同,一般为15。

②老年代(Old Generation): 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代。老年代的垃圾回收为Major GC( 使用标记-清理算法)。
整堆包括年轻代和老年代的垃圾回收称为Full GC。

③永久代(Permanent Generation): Hotspot虚拟机特有的概念,是方法区的一种实现。用于存放Class和Method的元信息,Class在Load时被放入永久代,Class和存放Instance的Heap区不同, GC不会在主程序运行期间对永久代进行清理。

④元空间(MetaSpace): 与JVM堆不相连的本地内存。jdk1.8之后,废除了永久代,常量池从永久代迁移到了堆内存中,类的元数据如方法区从永久代迁移到了元空间中。GC时不会扫描元空间,减小了full gc的时间。

参数简介

官方文档参考
-Xms: jvm启动时分配的初始内存, 如-Xms512m, 表示初始分配512M内存。

-Xmx: jvm运行过程中分配的最大内存, 如-Xmx512m,表示jvm进程最多只能够占用512M内存。

-Xmn: 分配给年轻代的堆内存,如-Xmn192m. 表示年轻代可使用jvm的最大内存为192M。

-Xss: jvm启动的每个线程分配的内存大小, 如-Xss256k, 表示每个线程分配了256K内存。

-XX:PermSize: 永久代的初始内存,默认值依赖于平台。如-XX:PermSize=32m,表示永久代的初始内存设为32M。jdk1.8后被废除,若仍使用,不影响运行,仅启动报警。

-XX:MaxPermSize: 永久代的最大内存,若无指定,默认无上限(物理内存大小)。如-XX:MaxPermSize=68m,表示永久代最大内存为68M,jdk1.8后被废除。

-XX:MetaspaceSize: 首次触发垃圾回收的元空间大小,jdk1.8后出现。

-XX:MaxMetaspaceSize: 本地内存可分配给元空间的最大值,jdk1.8后出现,如-XX:MaxMetaspaceSize=128M。若无指定,元空间将会根据应用程序在运行时的需求动态设置大小(与可使用的本地内存相关)。

-XX:SurvivorRatio=4: 年轻代中Eden区与Survivor区的比值为4(注意Survivor区有两个)。表示Eden:Survivor=4:2,一个Survivor区占整个年轻代的1/6。

-XX:+PrintGCTimeStamps: 打印每次GC的时间戳,默认不开启。

-XX:+PrintHeapAtGC: 打印GC前后的详细堆栈信息。

-XX:+PrintGCDetails: 打印每次GC的细节信息,默认不打印。

-XX:+UseParNewGC: 支持在年轻代用多线程进行垃圾收集,默认不开启。

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

-XX:ParallelGCThreads=8: 并行GC的线程数设为8,默认值是CPU数。

-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片。

-XX:CMSFullGCsBeforeCompaction=3: 设置每3次GC则对内存空间进行压缩、整理。由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生碎片,使运行效率降低

-XX:+UseConcMarkSweepGC: 设置对老生代使用CMS回收。

-XX:+DisableExplicitGC: 禁止代码中显式调用GC,加上此参数,代码中调用System.gc()没有效果(附:显式调用System.gc(),只是建议JVM进行垃圾回收,但是最终会不会执行垃圾回收是不确定的)。如果应用中使用了java nio中的direct memory,那么使用-XX:+DisableExplicitGC一定要小心,存在潜在的内存泄露风险。

-XX:MaxDirectMemorySize: java nio direct buffer的最大值,若未指定MaxDirectMemorySize,此时MaxDirectMemorySize大小等同-Xmx的值, 建议显式指定大小,如-XX:MaxDirectMemorySize=128M。

-XX:NativeMemoryTracking=summary: Hotspot VM开启本地内存的统计,通过命令jcmd pid VM.native_memory summary查看。jcmd是jdk1.7新增的命令行工具, oracle官方建议使用jcmd代替jmap

-XX:+HeapDumpOnOutOfMemoryError: 当JVM发生OOM时,自动生成DUMP文件。

-XX:HeapDumpPath=$dirName: 指定生成DUMP文件的路径(可包含文件名),如:-XX:HeapDumpPath=/root/javaheapdump.hprof ; 如果不指定文件名,默认为java

-Duser.timezone=GMT+08: 设置java运行环境时区为东8区 。

实例优化

首先确认项目的架构和代码等基本没有优化空间,然后通过GC优化,使性能提升。

方案

GC优化是系统且复杂的工作,可简单参照如下建议。
①JVM初始内存与JVM最大内存的值设置为相等,即-Xms=-Xmx
目的:减少内存自动扩容和收缩带来的性能损失

②SUN官方推荐年轻代配置为整个堆的3/8, 即Xmn为Xmx的3/8,实际根据应用情况调整。如果应用存在大量的短期对象,年轻代适量增大;如果存在相对较多的持久对象,老年代应适当增大。

③若为docker部署,Pod参数设置为:
内存Requested设置与-Xmx大小相同,内存Limited大小为Requested的4/3~3/2, 腾讯云容器(CCS)推荐为2倍。
CPU核数根据实际使用情况配置。
原因:-Xmx仅为Heap大小,除此之外,还有Non-Heap,如GC 初始及运行内存,元空间等。

实际应用

查看资源使用及堆栈信息,如通过以下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# linux下查看资源占用,作用同windows资源管理器
top
# 查看某个进程下各个线程的cpu使用情况
top -Hp pid
# 输出当前jvm进程的全部参数和系统属性
jinfo pid
# 与linux ps功能类似,显示本地的java程序,参数-m:输出传入main方法的参数;-l:输出完全的包名,主类名,jar的完全路径名;-v:输出jvm参数
jps -m -l -v
# 查看jvm线程运行状态,通过线程状态(RUNNABLE,BLOCKED,WAITING)分析是否有死锁等
jstack pid
# 查看堆的使用状况
jmap -heap pid
# 查看堆中对象详细占用
jmap -histo pid
# 导出整个JVM中内存信息
jmap -dump:format=b,file=文件名 pid
# 查看NMT(Native Memory Tracking)报告
jcmd pid VM.native_memory summary
# 用来监控VM的GUI程序, 命令行输入jconsole即可
jconsole
# 1000ms统计一次gc情况统计10次
jstat -gcutil pid 1000 10
# 查看gc的次数及时间,其中最后五项分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间
jstat -gc pid
# 基于Eclipse的内存分析工具,帮助查找内存泄漏和减少内存消耗
MAT(Eclipse Memory Analyzer)
# jvisualvm(jdk自带的可视化监控程序,在jdk/bin目录下), 大部分情况仅需使用此工具即可
双击jvisualvm.exe
查看内存快照:文件->装入->选择dump文件->装入后可查看堆栈快照信息
远程连接:远程->添加远程主机->添加jmx连接->双击建立好的连接可以实时查看当前程序的运行状况和堆栈信息等
# JProfiler,性能分析,参考如下
简介:https://www.cnblogs.com/jpfss/p/8488111.html
IDEA集成:https://blog.csdn.net/wytocsdn/article/details/79258247
IDEA集成使用,需购买,可先试用:https://blog.csdn.net/qq_22194659/article/details/83829891

关于资源分配,查容器监控及Grafana_Metrics监控,资源按实际消耗的2倍配置如下

1
2
3
4
5
6
-Xmx512M
-Xms512M
-Xmn192M
-XX:MaxDirectMemorySize=192M
元空间大小不显式指定,根据应用程序在运行时的需求动态设置
内存Requested设为768M, 内存Limited设为1G, CPU设为0.2-0.5核