前言
在网上查了很多如何配置JVM参数的讲解文章,但是生产环境里JVM参数的值到底配置为多少,却没能得到一个具体的规范;确实,生产环境受到各方面的影响,设置合适的JVM参数实在比较困难,但是本文将会给大家一个相对合理的参数设置指标。
本文主线
①、JVM运行时数据区分析
②、JVM参数设置时的注意点
③、简单的GC垃圾回收过程描述
④、最终JVM参数配置指南
本文为转载文章,原作者: 蓝山牧童, 原文地址: jdk1.8——jvm分析与调优
JVM运行时数据区分析
下面将主要分析下 1.7、1.8 两个不同的JDK版本下的JVM运行时数据区。
JDK1.7及以前
JDK 1.7及以前,Java 类信息、常量池、静态变量都存储在 Perm(永久代)里。类的元数据和静态变量在类加载的时候分配到 Perm,当类被卸载的时候垃圾收集器从 Perm 处理掉。
JDK1.8
JDK 1.8 的对 JVM 架构的改造将类元数据放到 本地内存中 ,另外,将 常量池和静态变量 放到 Java 堆里。HotSopt VM 将会为类的元数据明确分配和释放本地内存。
在这种架构下,类元信息就突破了原来 -XX:MaxPermSize 最大方法区大小参数的限制,所以PermSize的JVM配置参数也是无效的,现在可以使用更多的本地内存。
这样就从一定程度上解决了在运行时使用反射、代理等操作生成的大量 类实例 的问题,从而极大的降低了触发Full GC的问题,以及降低了出现 OutOfMemoryError: PermGen 方法区内存溢出的问题。
JDK1.7的JVM部分运行时数据区展示
干货内容:
可以发现最明显的一个变化是元空间从 虚拟机转移到本地内存 ;默认情况下,元空间的大小仅受本地内存的限制。这意味着以后几乎不再会因为永久代空间不够而抛出OOM异常了。
jdk1.8以前版本的class和jar包数据存储在permGen下面 ,permGen大小是固定的,而且项目之间无法共用公有的class,所以很容易碰到OOM异常。
改成metaSpaces后,各个项目会共享同样的class内存空间,比如多个项目都引用了apache-common包,在metaSpaces中只会存储一份apache-common的class,提高了内存的利用率,垃圾回收更有效率。
JVM参数设置的注意点
1、在jdk1.7及以前,生产环境一般有如下配置:
-XX:PermSize=512M -XX:MaxPermSize=1024M
表示在JVM里存储Java类信息,常量池和静态变量的 永久代(方法区) 区域初始大小为512M,最大为1024M。在项目启动后,这个值是固定的,如果项目 class(类实例) 过多,很可能会导致OutOfMemoryError: PermGen异常。
升级JDK1.8之后,上面的perm配置已经变成:
-XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M
元空间MetaspaceSize如果不做配置,通过jinfo查看默认MetaspaceSize大小(约21M),MaxMetaspaceSize最大元空间则很大很大,前面说过MetaSpace只受本地内存大小限制
jinfo -flag MetaspaceSize 1234 #结果为:-XX:MetaspaceSize=21807104
jinfo -flag MaxMetaspaceSize 1234 #结果为:-XX:MaxMetaspaceSize=18446744073709547520
干货内容:
MetaspaceSize触发FullGC的阈值,默认约为21M,如做了配置,最小阈值为自定义配置大小。空间使用达到阈值,触发FullGC,同时对该值扩大。当然如果元空间实际使用小于阈值,在GC的时候也会对该值缩小。
MaxMetaspaceSize为元空间的最大值,如果设置太小,就会和上面提到的一样,可能会导致频繁FullGC,甚至OOM。
GC 垃圾回收过程
首先贴上一张摘抄自网上的大图,结合此大图可以更加清晰的说明GC的过程:
垃圾回收过程:
下面说的GC流程中,新生代中的对象晋升到年老代中的条件是 年龄阀值 ;除此之外还有很多其它的晋升条件,后面会再细说。
①、新new的对象都放在Eden区(伊甸园嘛,创造的地方)
②、记住新生代的GC算法是 复制算法 。Eden区满或者快满的时候触发GC时(Minor Gc);在GC刚开始的时候,对象只会存在于Eden区和名为 From 的Survivor区,Survivor区 To 是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到 To 区,而在 From 区中,仍存活的对象会根据他们的年龄值来决定去向。
③、年龄达到一定值(年龄阈值,可以通过 -XX:MaxTenuringThreshold 来设置,默认是 15 )的对象会被移动到年老代中,没有达到阈值的对象会被复制到Survivor To 区域。
④、经过这次GC后,Eden区和From区已经被清空。这个时候, From和To 会交换它们的角色,也就是新的 To 就是上次GC前的 From,新的 From 就是上次GC前的 To 。
⑤、不管怎样,都会保证名为 To 的Survivor区域是空的。Minor GC会一直重复这样的过程,直到 To 区被填满, To 区被填满之后,会将所有对象移动到年老代中。
⑥、注意:当在触发Minor GC时,发现堆新生代中还存活的年龄达到阀值的对象的容量,比目前年老代中剩余空间还大的话,说明年老代存放不下,就会直接触发一次Full GC,而不再去触发Minor GC 了。
清理新生代(Eden区和Survivor区)叫Minor GC;清理年老代(Old区)叫Major GC;清理整个堆空间(年轻代和老年代)以及永久代(方法区)叫Full GC。
再贴一张大图配合理解GC的过程:
新生代对象晋升到年老代的条件:
①、如果对象的大小大于Eden的二分之一会直接分配在old年老代,如果old也分配不下,会做一次major GC,如果小于Eden的一半但是没有足够的空间,就进行minor GC也就是新生代GC。
②、minor GC后,Survivor仍然放不下,则放到Old老年代。
③、动态年龄判断 ,大于等于某个年龄的对象超过了Survivor空间一半 ,大于等于某个年龄的对象直接进入老年代。
JVM参数配置指南
前面三个部分对JVM进行了整体的了解,接下来是本文的重点。
-XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M -Xms256m -Xmx256m
文章看下来上面这段配置的意思很简单,设置元空间的初始值和最大值,设置堆空间的初始值和最大值。
为什么MetaspaceSize要设置为128M?为什么堆内存初始值Xms设置为256M而不是512M?
按照Java官方的指导:
①、Java堆大小设置,Xms 和 Xmx设置为老年代存活对象的3-4倍,即Full GC之后的老年代内存占用的3-4倍。
②、永久代 PermSize和MaxPermSize(元空间)设置为老年代存活对象的1.2-1.5倍。
③、年轻代Xmn的设置为老年代存活对象的1-1.5倍。
④、老年代的内存大小设置为老年代存活对象的2-3倍。
使用一个模仿生成环境进行测试运行一段时间后,获取JVM参数数据。然后再进行设置实际的JVM参数;如下用jstat工具查看jvm的情况:
jstat -gc 12345
###
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
13824.0 22528.0 13377.0 0.0 548864.0 535257.2 113152.0 46189.3 73984.0 71119.8 9728.0 9196.2 14 0.259 3 0.287 0.546
OU表示老年代所占用的内存为 46189.3 K(大约45M);那么jvm相应的配置参数应该做如下修改:
-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M -Xms180m -Xmx180m
❤ 关注 + 点赞 + 收藏 + 评论 哟
如果本文对您有帮助的话,请挥动下您爱发财的小手点下赞呀,您的支持就是我不断创作的动力,谢谢!
您可以VX搜索【木子雷】公众号,坚持高质量原创java技术文章,值得您关注!