相信很多java工程师在工作中都会接触到jvm,在面试中也会被问到跟java虚拟机有关的问题。我们为了把工作做好、把项目维护好、在系统出现fullgc等问题的时候,能够像老司机一样准确定位问题,很有必要把java虚拟机知识学习一遍。
然而,Java虚拟机的知识量很大,买一本书回来慢慢学习会比较消耗时间(例如《深入理解Java虚拟机》,其实这本书也只算"凑合")。况且jvm优质学习资源较少,只有官方文档较好,但是官方文档内容又太多了,不适合作为学习教材,而且还是英文的。
即使你下定决心,准备把Java虚拟机的所有知识彻底学一遍,也很有可能遇到这些问题:1)看到后面忘记前面;2)看的时候,没有练习机会,导致在实际要用的时候,又不知道怎么办;3)看完之后,当时虽然记得,但是过了一段时间之后,又都忘掉了。
针对上面这些问题,我们应该从实践出发,带着目的学习。这样不但记得牢、而且一开始就面向实践,理解得也要比单纯的看资料深入。在这篇文章中,就以我们公司的某个后端服务的java启动选项为例,介绍这些jvm选项,逐个解释这些选项的作用,及相关的JVM内存管理、GC或类加载等知识。
我们公司某一个大流量的后端服务配置了这些jvm选项(这些选项也是目前国内互联网公司经常用到的)
1) -Xms4G解释
这个配置项对应的JVM选项是 -Xms<size>,其中,4G是参数值。
我们知道java命令是用来启动java虚拟机执行java代码的。java命令支持很多选项,在这些选项当中,以"-X"开头的,都是Java虚拟机选项;不以"-X"开头的选项,不是传给Java虚拟机的,例如"java -jar filename.jar"中的"-jar"不是传给JVM的。
所以,-Xms4G中的-X代表这是一个jvm选项,m代表memory,对于jvm而言memory就是堆;s是smallest,最小的意思。-Xms4G代表把JVM的堆的最小值设为4G。
jvm堆随着java程序的运行不断增大,因此,这里的堆最小值也是jvm初始堆大小。当jvm运行一段时间后,堆大小超过初始值,这里配置的值其实没什么用了。
所以,最小堆大小或初始堆大小只影响JVM启动阶段,对后续jvm运行没什么影响。
那么,在实际运用中,-Xms<size>应该配置成多少呢?假如服务器是8核16G,先设置成10G,即物理内存的一半再多加一些(建议跟最大堆大小设置成相同值,这样可以减少刚部署阶段的fullgc次数),然后运行一段时间再看容器的监控,看容器还剩多少内存。如果还剩很多,再调大一些,例如设置成12G,直到充分利用容器物理内存为止。
2) -Xmx4G解释
对应的JVM选项是 -Xmx<size>,它跟前面-Xms<size>类似,配置方法完全一样。
-Xmx4G中,m代表memory,x是maximum,最大的意思。-Xmx4G就是把JVM的堆的最大值设为4G。
前面说过,随着java程序运行,堆会从初始值开始稳步增长,当达到最大值以后就不再增长,以后主要靠GC来回收内存。所以,堆最大值的设置要比最小值谨慎,配置小了,程序内存不够用,频繁GC;配置得太大,每次GC时间比较长,程序有停顿现象(也跟垃圾回收器的选择有关)。对后端服务而言,堆最大值一般与堆最小值配置成一样的即可。
实际工作中,-Xmx<size>的值怎么选?按照前面-Xms<size>的配置方法操作即可。如果每次GC时间较长,说明堆配置值的大了,适当减小堆最大值。
3) -Xmn1G解释
这项配置对应的jvm选项是-Xmn<size>,即把young generation(新生代)设置为1G。
-Xmn<size>中,m代表memory(如前所述),字母n代表什么呢?它代表nursery(托儿所,存放新出生的婴儿,即新创建的对象)。-Xmn1G就是把新生代固定设置成1G。另外,新生代也可以像前面一样,最大、最小值分开设置,用-XX:NewSize来设置新生代的初始大小(最小值),用-XX:MaxNewSize设置新生代的最大值,这里-Xmn<size>相当于同时指定-XX:NewSize和-XX:MaxNewSize。
jvm运行一段时间以后,新生代的初始大小,即-XX:NewSize其实没什么作用了,起作用的仅仅是-XX:MaxNewSize,所以我们往往把两者设置成一样的值,而且用-Xmn<size>这种合并的写法,这样比较方便。
JVM内存知识:我们知道,java中新创建的对象都是放在新生代(young generation)中的,尤其是刚创建出来的对象,更是放在了新生代中的eden区。经历过少数几次GC的新对象往往都是放在survivor区。对于java这样的"一切都是对象"的语言来说,程序运行一旦运行起来,就不断有大量的对象被新建和释放(java一开始甚至因为运行得太慢,差点被淘汰)。因此,新生代的GC非常活跃,如果你们公司有实时监控新生代内存的系统,就可以看到,新生代几乎一直在GC。新生代的GC虽然频繁,但是所产生的停顿非常非常小,几乎可以忽略不计,不会产生多少服务延迟。新生代GC也就是我们所说的Young GC。
那么,-Xmn<size>怎么设置呢?假如设置得太小,那么Yong GC就会发生得更加频繁,所产生的的效果就是,它会比较多的消耗CPU资源;而如果设置的太大,那么新对象存活的时间就会比较长,导致它们被不恰当的放入老年代(old generation),从而导致产生太多full gc,而full gc就对服务有明显影响了。Oracle官方文档建议我们把新生代的大小设置为堆的总大小的1/2到1/4之间。但是,Oracle不了解我们的业务特点,我们还是需要根据业务监控,来看新生代具体要设置为多大。
那具体要怎么调整?就是前面说的,如果Yong GC太多了或GC导致的CPU计算量太高,就调大新生代;而如果业务服务出现了full gc,那考虑调大新生代。不过,据我多年经验总结,full gc很少是由JVM参数配置不当造成的,它往往是业务代码写得不合理导致的。如果你的某一个业务服务平时运行得很好,突然有一天full gc,就看看最近上线改了什么。另外,如果full gc是因为服务连续、长时间运行导致,那就看你代码里面有没有不当使用内存,有没有内存泄漏,长期引用不需要的对象。
到这里才发现,还有这些JVM参数没有讲呢。下一篇文章继续分析,感兴趣的朋友请关注哦。
-XX:+AlwaysPreTouch -XX:PermSize=256M -XX:MaxPermSize=256M
-XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80
-Dlog4j.configurationFile=/data/webapps/dealservice_bizer/current/conf/log4j2.xml