GC代码优化之路浅析及抛砖

背景

最近被一些GC问题困扰,处理一些工作中的GC问题,本文也算整体对笔者GC经验的一些梳理。

GC–内存分配率

谈起GC,我觉得首先要提到的应该是 “内存分配率” 或者说是 java对象分配率,简单来说,就是内存分配速率,单位时间内对象分配占用内存的情况,通常可以用 MB/S 作为单位。

对于我们系统而言,过高的内存分配率说明系统存在性能问题,会导致GC的执行频率 和 执行时间过长,更严重的情况导致FullGC增多,甚至OOM (分配过快,回收不及时)。

我们调优本质就是降低内存分配率,减少GC的执行频率和时间,从而提高系统吞吐率。当然,这些是从代码层面分析,还有根据调优GC垃圾收集器,GC的堆大小的调优方式,不在我们这节的讨论中。

关于内存分配率的评估,这里就不过多介绍了,常用的gc日志工具(gcviewer 和 gceasy.io),以及堆内存分析工具(mat / jprofiler)都可以做评估。

常见的GC飙高的情况

  1. 过高的流量:
    针对一些服务,高峰期的突发流量,突然而来的大量消息,定时任务等导致的在一个时间点,大量的业务对象被创建,导致系统GC次数和执行频率飙升,甚至fullgc 和 oom。这种情况,其实建议对自己负责的系统评估,比如做一些压测,评估系统的极限在哪里或者可接受的极限在哪里,根据这些数据做一些服务的限流操作,防止过高的流量直接严重影响系统性能。

  2. 代码bug
    1.说起代码bug的情况,种类其实太多了,比如list 和 map 这种对象数据没有及时释放。
    2.笔者还见过由于代码处理不清晰,明明处理一条数据就可以的情况下,代码却取出了所有满足要求的数据,处理一条后,下一次继续取所有数据,再处理一条,导致list长度大小的list重复创建。
    3.比如一些情况,根据服务上游提供的一些数据,我们需要关联n张表,才能将相关数据取出来,为了减少数据库开销,很少使用join,这种情况下,有的工程师可能先取一张表的这条记录的所有数据,找到关联id 再根据关联id 再取另一张表一条实体的全部数据。。。。最终找到需要的数据,再高流量的情况下,这中间要多产生多少临时对象,这种情况,建议针对性的建立反筛服务,针对请求信息作为key,最终目标对象作为value的redis缓存,减少临时对象产生。
    4.微服务情况下,上下游没有建立防腐层,这种情况怎么理解呢?对于开发出去的服务B,我提供公共服务,那么我吐出的信息基本上大而全的,很多下游比如下游A,为了方便,直接获取B服务接口对象的全部信息,其实可能你只需要一部分或者一两个字段,导致A在整个业务周期中,都用这个大对象,导致性能问题。

GC调优的目标 和 原则

一般来讲,我们将系统的 吞吐量、平均停顿时间、最大停顿时间、FullGC次数、 minorGC次数 作为调优的KPI。
原则我理解只有两个:
一、减少对象的数量
二、减少对象的大小
很明显原则一效果最为显著。原则二需要根据对象头、属性大小去进行估算,这种确实会耗费大量的计算成本,但是在高流量的情况下,确实也起作用,并不是所有情况下都可以减少对象数量的。

抛砖引玉

这些都是工作中遇到的情况,也有悬而未决的情况,如果大家有更好的GC的代码优化建议,建议给我私信留言分享。

猜你喜欢

转载自blog.csdn.net/baoxue2008/article/details/106953870