67

如何降低 young gc 时间

 5 years ago
source link: https://mp.weixin.qq.com/s/FeHquGVIRiF6oC9BEVOWCg?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

基础知识

young gc 主要采用的是copying GC算法;copying GC算法主要有以下两个步骤:

  1. Root Scanning

  2. Object Copy

copying Gc的执行过程大概是从 Gc roots开始扫描其引用,扫描到的就是认为是存活的对象,其他的就是不需要的对象,然后把存放对象进行移动就OK了。

r2yqu2F.jpg!web

young gc 的耗时也基本上都在这两个步骤上。要想减少一次young gc的时间,必须想办法减少上面两步耗时。

根据官方文档可以知道, GC roots 包含以下引用:

  1. 所有java线程以及线程栈帧里指向GC堆里的对象的引用

  2. JNI Local & Global

  3. 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的

  4. stack local Java方法的local变量或参数

  5. 其他,包含monitor & finalizable & native stack 等吧

Copying GC算法最主要的特征就是它的gc 时间只跟活对象的多少有关系,而跟它所管理的堆空间的大小没关系。

如何降低每次young gc 的时间呢

从上面的分析可以知道只要减少GC roots集合大小以及降低每次gc 之后的存活对象就可以了。

在GC roots中 跟业务方最相关的就是j ava线程 ,那要是把线程数减少是不是能降低 Root Scanning,进而降低整个young gc 时间呢。

笔者负责的项目大部分项目都是采用Hystrix线程池作为超时熔断降级,因为依赖的下游接口很多很多并且很多时候需要分批,导致线程数特别多,高达4000+,young gc 时root  scanning 占用了 15ms左右,young gc 日志如下:

Vv2Yja3.jpg!web

我采用了 Hystrix信号量+RPC异步化去改造项目,减少线程池数目。改造之后线程数在700左右,young gc时 root  scanning 占用的的时间 < 5ms。

YZ7R3qZ.jpg!web

降低young gc的总时间

调整 Eden区域大小对应用产生的可能影响分析

相同的应用一般来说 gc roots 应该是保持不变的,可以简单认为 Root Scanning相等(其实live object会影响到扫描时间,但是影响和object copy相比很小)

我们来看看Object Copy可能受到的影响( 假设 Survivor区域足够大,不会因为copy过程中Survivor不够大直接晋升到old区域

先看第一部分, Eden移动到 Survivor情况

假设机器2 Eden区域是 机器1 的两倍大,其他条件都保持不变;

就一般情况来说( Survivor区域 中存活的对象比Eden少很多,比如1%),那么机器1 young gc的频率是 机器2 young gc 频率的2倍;那么假设机器1在T时间内GC一次,在GC之后由Eden区域晋升到 Survivor的大小为10M(即age=1),那么机器2在2T时间之后发生GC,1T-2T之间生成的对象和机器1类似,GC之后有10M进入Survivor区域,但是0T-1T内最多会剩余10M内存可能会进入到Survivor,但是在经历1T-2T时间之后也有可能导致object已经不存活,如何判断这部分对象有没有存活呢,在机器1在2T的时间点要又要进行一次young gc,那么在0T-1T之前存活的对象也就是age=1的对象将会再次会经历一次young gc,便是了age=2,所有看age=2的年龄段剩余多少就可以了。机器2一次GC之后,由 Eden区域进入到 Survivor区域中的大约等于10M+机器1中Survivor中(age=2)也就是机器1:age1+age2中的object对象。

总结  机器2由 Eden区域移动到 Survivor的量就是机器1 age1 + age2的量。

第二部分的分析逻辑和第一部分的差不多,逻辑自己推。

结论:如果age1 远大于 age2中的值,那么调大 Eden区域对减少young gc 次数会很明显,并且每次young gc time时间变化不大,能明显降低young gc总体时间。

为了验证上面的理论分析,笔者找了一个young gc 之后 age1>>>age2的项目,young gc 日志如下:

yEniayv.jpg!web

笔者把dx17的young 区域调大 (调整之后 Eden为 1677824K),dx14的 Eden为 921600K,调整前后的gc 时间如下:

jANNny7.jpg!web

ZvauEzu.jpg!web

MRrQVzR.jpg!web

从上面3张图可以看出 整体每分钟 young gc 时间由 125ms --->70ms,young gc 次数由 每分钟12.7 ---> 7,每次young gc的时间仍旧是9.4左右。用awk做了一下统计发现每次young gc 之后的live object的大小由2.85M增加到了3.3M。

减少对象生成 以到达降低young gc 次数

尽量使用小对象,并且在方法内和线程内分配对象,利用JIT在优化时对象在 栈上分配,减少在堆上分配内存,可以参考 浅谈HotSpot逃逸分析,但是笔者在关闭逃逸分析的时候( -XX:-DoEscapeAnalysis ),对线上机器,对GC请求没啥影响,但是自己写测试确实有比较大的影响,没有明白为什么。

使用对象池,减少对象产生。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK