哈喽哈喽大家猴,我是把代码写成bug的大头菜。公众号:大头菜技术(bigheadit)。原创不易,但欢送转载。
这周的技术周报次要内容:JVM相干知识点
JVM相干知识点
这周,还是次要钻研JVM的相干常识,次要钻研一些实在的JVM生产案例。尝试通过案例去总结一些优化JVM的方法论:
比方:
频繁产生FULL GC,应该如何剖析?
首先可能起因有:
- survivor空间太小,不足以包容所有存活对象,从而导致对象过早进入老年代。使得老年代很快没内存,导致频繁产生FULL GC。
- survivor空间太小,存活对象的空间大小超过survivor空间的一半,触发动静年龄判断,使得对象过早进入老年代,使得老年代很快没内存,导致频繁产生FULL GC。
- 产生内存泄露,有大对象始终在老年代,得不到回收。当每一次young gc后,都会有大量对象进入老年代,从而触发FULL GC
- 代码中显示调用System.gc(),在高并发时,会频繁触发FULL GC
- 频繁产生young gc,存活对象的大小大于进入老年代对象的均匀大小,且没开启空间担保调配,从而触发FULL GCC
- 在元数据区,一直有新的类被加载到元数据区,从而触发FULL GC
下面这些是一些产生FULL GC可能的起因。
咱们能够通过jstat命令来查看堆的状况来进行判断。如果有监控视图,也能够通过监控视图来判断。
定位到具体起因后,进行对应的优化即可。
总体的外围思路就是:尽可能让对象在young gc中被回收,避免对象过快进入老年代,进行一些不必要的FULL GC。说白了,就是缩小FULL GC的频率。个别把FULL GC产生的次数优化到一天一次,或者几天一次即可。
大家也不要被网上的一些文章观点误导:说老年代是新生代空间的1.5倍。
哪有这样子通用的优化模板的,不同的业务,必定须要制订不同的优化策略的。只有不变应万变,不变就是升高FULL GC的频率,变动的就是各种内存的调配策略+垃圾回收器的抉择+垃圾回收器参数配置。
比方:一些要求延时低的零碎,能够采纳CMS+ParNew策略;或者采纳G1策略。
如果对应一些须要计算大量数据的零碎,这时须要大内存,此时抉择G1会比拟适合。
尝试解答一些问题:
问题:产生young gc时,存活对象的空间大小大于survivor空间,此时存活对象会去哪?
- 说法一:所有存活对象会全副进入老年代。
- 说法二:局部对象进入survivor区,局部对象进入老年代。
先说论断:说法一和说法二都正确。理由如下:
- 如果产生full gc,会将新生代存活的对象都放入老年代。
- 如果产生young gc,会将新生代存活的对象一部分放survivor区,一部分放老年代
那如何来证实呢?
我间接上代码:
先运行代码,拿到日志文件:
Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for bsd-amd64 JRE (1.8.0_281-b09), built on Dec 9 2020 12:44:49 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5) Memory: 4k page, physical 16777216k(126540k free) /proc/meminfo: CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 0.115: [GC (Allocation Failure) 0.115: [ParNew (promotion failed): 6943K->7407K(9216K), 0.0040643 secs]0.119: [CMS: 8194K->6562K(10240K), 0.0016383 secs] 11039K->6562K(19456K), [Metaspace: 2706K->2706K(1056768K)], 0.0061605 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] Heap par new generation total 9216K, used 2130K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 26% used [0x00000007bec00000, 0x00000007bee14930, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000,<a style="color:transparent">来源gao*daima.com搞@代#码网</a> 0x00000007bf500000) concurrent mark-sweep generation total 10240K, used 6562K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) Metaspace used 2713K, capacity 4486K, committed 4864K, reserved 1056768K class space used 291K, capacity 386K, committed 512K, reserved 1048576K
首先,咱们间接看from区和to区,这两个区,使用率都为零。依据这里,能够失去证实,存活对象没去survivor区。
[ParNew (promotion failed): 6943K->7407K(9216K)
首先看,eden区,GC前6943K,GC后7407K。好家伙,不降反升。
因为下面这几个对象都被援用着,无奈回收。
当执行代码: byte[] array6 = new byte[2*_1MB];
此时eden区有存活对象有3个2M的对象和1个128K的对象。
因为eden区没有足够的空间,此时会产生young gc。
新生代6M+128K大于老年代的可用间断内存空间,
然而老年代可用间断内存空间大于历年新生代进入老年代对象的均匀大小0。
所以,空间调配担保胜利,不须要触发full gc。
然而因为6m+128k对象都存活,且因为survivor空间只有1m空间
导致存活对象间接进入老年代
看一下日志:
[CMS: 8194K->6562K(10240K)
年老代现将2个2m对象,放入老年代,此时老年代有1个4m+2个2m的对象。大概8194k
此时还须要放入1个2m对象+1个128k对象。
但老年代空间有余,此时须要触发full gc。
回收4m对象,而后再把1个2m对象+1个128k对象放入老年代。即6562K。
这里产生了full gc。联合from 区和to区的使用率都是0
可见,产生full gc时,全副存活对象,都会进入老年代。
那如何证实:
如果产生young gc,会将新生代存活的对象一部分放survivor区,一部分放老年代
让它只产生young gc即可。
代码如下:
运行一下代码,日志如下:
Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for bsd-amd64 JRE (1.8.0_281-b09), built on Dec 9 2020 12:44:49 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5) Memory: 4k page, physical 16777216k(125452k free) /proc/meminfo: CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 0.115: [GC (Allocation Failure) 0.116: [ParNew: 6943K->462K(9216K), 0.0040400 secs] 6943K->6608K(19456K), 0.0044398 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap par new generation total 9216K, used 2592K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 26% used [0x00000007bec00000, 0x00000007bee14930, 0x00000007bf400000) from space 1024K, 45% used [0x00000007bf500000, 0x00000007bf573ab0, 0x00000007bf600000) to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) concurrent mark-sweep generation total 10240K, used 6146K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) Metaspace used 2713K, capacity 4486K, committed 4864K, reserved 1056768K class space used 291K, capacity 386K, committed 512K, reserved 1048576K
咱们把焦点放在:from space 1024K, 45%
此时,from 区被应用了45%,能够证实有存活对象
再看,concurrent mark-sweep generation total 10240K, used 6146K
这里是有6M(6*1024=6144k),就是6146。必定不包含array5的128K对象。
过程我就不形容了。
看到这里,你应该懂了吧。如果你还看不懂,欢送加我微信和我交换探讨。
这周的技术日报就到这吧。。。。
下周见。。。。
相干代码和JVM配置参数:公众号回复:对象去哪里 获取相应代码