jdk提供了jmap的方式获取jvm堆的对象,可以说极大的解决了oom的问题分析,下面我们说说这个方式的问题以及优化方式。
常规解决方式
直接用jmap获取或者在jvm参数里加上dump参数。我们可以获取堆的dump。进行分析的时候我们可以看到什么对象占用多大的空间,以此可以分析出我们是堆空间不够导致的问题还是说是内存泄漏。
在java中内存不够或者内存泄漏,是一个逻辑问题,例如我的缓存过大导致oome,那我本来预期可以缓存5g的数据,但是其中一个对象不断的膨胀,导致进程oom。那只能分析对象膨胀的原因,如果是正常的,而且是可预期的合理停止增长,那么这个问题就是内存设置不够,我们的结局方案就是加大内存。如果说这个增大不合理,那么是程序的问题,需要进行合理的淘汰。如果自己写代码的时候压根认为自己的设置的缓存只能是5g。是没有释放资源导致了oome。那这个就是一个内存泄漏的典型。
所谓泄漏,是必须合理的认知才能辨别的。否则直接问我一个集合已经有5g了,这个有问题吗?这个实在是光凭这个信息无法判断。
dump的问题
dump解决的方式可以说是没什么问题,只是随着机器越来越好,项目越来越大,原来的堆一般都是5g,10g的设置,现在部署一个hbase,es可能都是64g,100g的内存,dump的操作对小内存没什么问题,但是对大内存,可能就是个灾难,直接一个dump操作,进程先gc,然后获取堆的数据。这个进程基本是几十秒甚至几分钟是无法进行工作的。这样的诊断成本直接导致了进程不可用,尤其是有心跳的程序,一个dump操作直接导致了进程的心跳超时,类似hbase的regionserver。直接就是自杀操作。
大堆的对象例如10g,那么你需要10g+的机器才能进行分析,如果100g呢,用100g的机器分析。。。直接就是大型物理机器分析数据了,这样的环境不好准备。
线上的机器安全性要求很高,dump的文件需要专门的运维进程获取,等待数据的周期过长。
尝试解决
对于获取难的方式,我们可以考虑做一个文件捕获转存系统,部署在每个机器,dump的触发,以及获取都可以通过这个系统来满足,这样就保证了安全性的问题,也保证了获取数据的方式的快捷。也降低了技术成本,我们自己dump的时候其实不会记忆那个jmap的参数的,每次都是靠提示。如果可以直接的操作,那么就减少了学习的成本。
对于大堆不可用的问题,这个可能得使用采样的方式去查看,查看每次的变化。如果说是一个典型的内存泄漏的话,我们一定是可以捕捉到他对象的分配。缓慢分配的就比较麻烦,需要可视化方案来做,例如每天泄漏1m,一年不停机才泄漏365m,可能这个泄漏速度基本忽略是没有泄漏的,毕竟没有系统会真的一年都不停一次(毕竟还是需要更新迭代的,一年都不更新一次,这个项目也是毫无bug,且不值得更新)。
如何才能进行对象分配的采集
可以使用jfr这种商业的功能,效果肯定不是一般的好。而且有对应的展示。只不过就是得花点钱。
也可以使用jvmti来进行分析,内存泄漏的场景其实相对没那么多,主要都是集合的操作,利用这个特点,我们可以通过字节码注入的能力,对集合对象进行注入。最终获取到一个数据的信息。这个也是有几个缺陷。
- 需要注入的集合太多。光jdk的集合就很多了,线程安全的,线程不安全的。阻塞的,非阻塞的,样式繁多。都注入一次,明显会影响jvm的启动。
- 第三方的集合无法操作,我们不可能穷举所有的集合类型,总有第三方的,那些是我们不知道的,也是不可期待的部分。
总结
针对以上的问题,做一套系统方便获取堆dump是比较现实的,其他的采集技术,我们需要针对特定的场景进行分析。
实在不行,我们就把实例下线处理,这样就不用担心什么影响了。只不过是需要找个好点的机器去分析。