1、了解虚拟机内存结构
在进行内存调优之前,我们需要先了java的内存结构,见下图或查看该遍文章:Java内存导图
下面我们对关键几个部分进行说明:
- 虚拟机栈:是线程私有的。存储方法执行时相关信息,每个方法在调用时都会在虚拟机栈中创建一个方法帧,帧中包含了局部变量、参数、运行中间结果等信息。帧数超过限制(-Xss)就会出现StackOverFlow错误。在运行过程中如果超过线程分配的内存大小,也会报OOM错误。
- 本地方法栈:线程私有的。存放的是native方法帧(基本上同虚拟机栈)。
- 堆:所有线程共享。存放new出来的数组和对象数据,以及类的静态变量和常量池。
- 元数据区(MateSpaces),它不存在jvm虚拟机中,而是本地内存,默认情况下大小只与系统内存有关。
2、了解GC的过程
在jvm虚拟机中,GC主要还是在Heap上进行。下面我们换结合上图说明一下GC的过程。
1、新new的对象都将放在Eden区;
2、第一次:Eden区满或者将满时,会进行一次Minor GC,不被引用的对象将会被清除,被引用但年龄较大的对象将移动到S0区;
3、第二次:Eden区快满时,会再次进行Minor GC,Eden和S0区中不被引用的对象将会被清除,同时这两个区的年龄较大的对象将被移动到S1区(注意:S0和S1在原理上都是保持有一个区是空的)
4、第三次:Eden区快满时,会进行Minor GC,注意这里是将对象移动到S0( S1是空的)
5、直到Eden快满,S0或S1都快满时,这时把两个区中年纪较大的对象移动到Old区;
6、按上持续循环,直到Old区也快满时,Eden已快满时,S0或S1也快满时,就会对整个区内区进行Full GC。
注意:元空间也会触发Full GC执行的
元空间的初始大小是21M,当空间使用超过这个值时,就会进行回收操作。
3、内存参数指南
我们大概了解了内存的结构,也清楚GC的主要触发点,那我们怎么对这些内存结构进行调整?
-server #启用JVM的server模式,理好的发挥服务器性能
-Xmx2g #最大Heap可用内存
-Xms2g #初始Heap内存大小(建议生产环境同Xmx一致)
-Xss128k #每个线程Stack内存大小,理论上来说,相同物理内存,值小则能生产更多的线程(当然也要受系统限制的)
-xx:NewRatio=4 #设置新生代与老年代的比值,4表示新生代:老年代=1:4,默认=2(新生代与老年代占比值为1:2,新生代占堆内存1/3)
-xx:SurvivorRatio=4 #设置2个Survivor区和Eden的比值,4表示两个Survivor:eden=2:4.默认值=8
-Xmn1024m #设置Young Generation占用的Head的大小为1g,官方推荐配置为整个堆的3/8
-XX:NewSize=1g #设置新生代大小
-XX:MaxNewSize=1g #设置新生代最大大小
对新代年设置我们注意到有几个参数可以配置,那他们的优先级是怎么样的呢?
1、最优先:-xx:NewSize -xx:MaxNewSize
2、次之: -Xmn 推荐使用该参数
3、最低: -XX:NewRatio
-XX:OldSize=2g #设置老年代的大小
-XX:MetaspaceSize=128M #初始元空间的大小,默认值为21MB
-XX:MaxMatespacesSize=256M #最大元空间大小
4、调优原则
1、目标:避免频繁的Full GC执行
2、Java应用一般都留有JAVA_OPTS或JAVA_OPT_EXT变量,如RocketMQ指定堆大小
rmqserver -e "JAVA_OPT_EXT=-server -Xms128m -Xmx128m -Xmn128m"
3、明确指定Xmx的大小,因为不指定的情况下,系统会根据程序运行需要动态调整,当需要调整时,会进行一次Full GC。
4、设置Xms=Xmx,避免GC时的动态调整
5、元空间调整,设置-xx:MetaspaceSize,因为该值默认为21M,超过这个值,会导致较频繁的Full GC
6、记录GC日志,提供数据分析:-XX:+printGCTimeStamps -XX:+PrintGCDetails -Xloggc:/opt/gc/aa.log
7、官方建议堆空间分配规则
空间 | 命令参数 | 建议 |
Heap | -Xms -Xmx | 3-4倍FullGC后老年代空间占比 |
新生代 | -Xmn | 1-1.5倍FullGC之后的老年代空间占比 |
老年代 | 2-3倍FullGC后的老年代空间占比 |