(JVM调优)内存调优
内存调优
什么是内存泄露
Java中如果一个对象不再被使用,但他仍在GC ROOT的引用链上,根据之前提到的回收办法,他就不会被垃圾回收器回收,这就造成了内存泄露。内存泄漏往往发生在堆区。
下面列举几个内存泄露的常见场景
- 在Java后端程序中处理完用户请求后未及时将用户数据删除,随着用户数据不断累积,造成堆内存溢出
- 进行分布式调度时被调度的应用在调度结束时出现了内存泄漏,多次调度后内存溢出
内存泄露的解决方案
对于内存泄露,常常按照四步来解决
发现问题阶段
常使用阿里开发的Arthas工具来对业务状态进行监看
而Prometheus+Grafana是企业中运维常用的监控方案
在使用上面的工具对堆内存状况进行监看,我们可以发现出现内存泄漏时,堆内存会出现明显异常
诊断阶段
原因1: 内存中的代码泄露
equals()和hashCode()的不正确使用,导致同一数据被保存多次
ThreadLocal的使用过程中未回收线程池中的线程;
线程方法执行完毕后,一定要调用ThreadLocal中的remove方法来清理对象
通过静态字段来保存对象,造成大量数据在静态变量中被引用,但又不被使用
尽量减少将对象长时间保存在静态变量中; 使用单例模式时,尽量使用懒加载; SpringBoot的Bean中不要长期存放大对象,若临时使用,设定过期时间定期失效
内部类引用外部类;非静态的内部类会默认持有外部类,虽然代码上没有使用外部类,但如果在别的地方使用了该静态内部类,会导致外部类也被引用,让他无法被回收,造成内存泄漏
String的intern方法被大量调用并保存导致内存泄漏
资源没有正常关闭;
在finally块中关闭不在使用的资源
原因2: 并发请求问题
用户在短时间内发来大量并发请求,导致处理数据的时间激增,导致大量数据堆积在内存中,造成内存溢出。
为了解决高并发请求造成的内存溢出,我们可以使用Jmeter进行并发测试,定位问题根源
内存快照
当内存溢出时,为了进行时候的分析与复盘,会将内存溢出时的整个堆内存保存下来,生成内存快照(Heap Profile)。我们可以使用MAT工具打开快照文件,并且选择内存泄漏检测,就可以自动分析数据定位内存泄漏根源
生成内存快照的JVM参数
1 | -XX:+HeapDumpOnOutOfMemoryError:发生OutOfMemoryError错误时,自动生成hprof内存快照文件。 |
MAT内存泄漏检测的原理 :
MAT就是根据支配树(如果所有指向B的路径都经过A,就认为A支配B,按这个判定方法生成支配树),从叶子节点向根节点遍历,如果发现深堆的大小超过整个堆内存的一定比例阈值,就会将其标记成内存泄漏的“嫌疑对象”。
修复阶段
上面介绍了几种引发内存泄露的来源,对于他们,我们需要逐一分析
- 对于代码中的内存泄露;在上一阶段中已经介绍并且提供了解决方案,这里不再赘述
- 对于因为参数不当而在高并发引起的内存溢出;需要调整参数,在下一章节GC调优中会详细介绍
- 对于因为设计不当而在高并发引起的内存溢出(比如拉取过大的数据库数据,线程池设计不当,消费者消费性能不足等);需要对设计方案进行优化
验证阶段
对引发内存泄漏的问题进行修复后,我们就可以使用压测工具对之前引发内存泄露的场景进行复现,验证修复的可靠性