由一个CUP占用率过高的问题去理解Java垃圾回收机制

最近我们的高并发平台遇到用户高峰运行时总会出现CPU占用率过高的问题,经过一段时间的排查及查阅相关的JVM资料,发现在程序中有这样一块代码引起了注意:

由一个CUP占用率过高的问题去理解Java垃圾回收机制_第1张图片

首先我们需要了解一下JVM的垃圾收集算法:
1、标记-清除算法
最基础的收集算法是“标记-清除”算法,顾名思义,算法分为“标记”和“清除”两个阶段:首先标记出所有要回收的对象,在标记完成之后统一回收所有被标记的对象。
缺点:一是时间问题,标记和清除两个过程效率都不高。二是空间问题,标记清除之后会产生大量不连续的内存碎片。
2、复制算法
该算法的思想是将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块的内存用完了,则将还存活的对象复制到另外一块内存上去,然后再把刚使用过的内存空间一次清理掉。从而达到了每次垃圾回收时只是针对其中一块,避免了产生内存碎片等情况。
缺点:该算法的代价是只是使用了其中一半的内存,代价未免太高。
IBM研究曾表明,98%的新生代对象都是存活周期很短。比如在HotSpot虚拟机中,新生代中Eden和Survivor的大小比例为8::1,因为新生代的对象需要回收的概率大(对象的生命周期短,存活率低),所以内存的可用率达到了90%(新生代分为:Eden和两块Survivor)。
3、标记-整理算法
标记过程仍然和“标记-清除”算法一样,但后续步骤不是直接对可回收的对象进行清理,而是让所有存活的对象移动到移动,然后直接清理掉端边界以外的内存。
4、分代收集算法
当代商业虚拟机都使用“分代收集”算法,它是根据对象存活周期不同将内存划分几块。一般是把java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大量对象死亡,只有少量才存活,就可采用复制算法。而老年代中,对象存活率高,就必须使用“标记-清除”或者“标记-整理”算法。
大概了解JVM的垃圾收集算法之后,还需要知道一个Java大对象的概念。
大对象是指需要大量连续内存空间的Java对象,最典型的就是大对象就是那些很长的字符串和数组,例如byte[]数组就是典型的大对象。说到这里,我觉得大部分同学应该就能发现上面的程序中存在严重大对象的问题,没错,在高并发的情况下这块程序会产生一大波短命大对象,这对java垃圾收集简直是噩梦,这回不停的触发垃圾收集在新年代进行内存复制,从而导致严重消耗CUP资源。
问题找到了,那我们接着就要找对应的解决方案
1、针对请求的信息,我们发现不需要那么大的分配空间,byte[100]就可以满足要求。
2、JVM提供一个-XX:PretenureSizeThreshold参数,另大于这个对象的参数直接在老年代进行分配,避免在Eden和连个Survivor之间进行大量的内存复制。
经过优化设置之后,系统的CUP占用率过高问题得到了明显的改善。
通过这次实际遇到的问题,让我对Java垃圾回收机制有了更深的理解,以后底层程序优化处理,jVM垃圾回收是一个必须考虑的问题。

你可能感兴趣的:(垃圾收集,CPU占用率过高)