java深度调试【第三章内存分析和堆内存设置】

前言

我是[提前退休的java猿],一名7年java开发经验的开发组长,分享工作中的各种问题!(抖音、公众号同号)

🔈PS: 最近多了一个新的写作方向,就是会把出版社寄来的【java深度调试技术】按照书的骨架提取其中干货进行丰盈写成博客,大家可以关注我的新专栏

内存泄露分析和堆内存设置

这一章节主要介绍了垃圾回收器的基础知识,然后再举出内存泄漏的案例让我们能够轻松的理解内存泄露的原理。 接着又介绍了内存泄露的症状以及定位和分析的手段,然后就是介绍了内存泄露的方法。

最后也是重点讲解了 java内存和垃圾回收的设置原则和关键参数。

内存泄露

首先肯定要明白的是什么是内存泄露?

在Java中,内存泄露指的是程序在运行过程中,已经不再需要的对象无法被垃圾回收器正常回收,导致这些对象持续占用内存空间的现象。随着时间推移,泄露的内存不断累积,最终可能导致内存耗尽和程序崩溃。

回收机制

Java的垃圾回收(GC)采用可达性分析算法,通过GC Roots对象作为起点,构建引用链。只有与GC Roots存在引用链的对象才会被认为是存活对象,其余对象则被标记为可回收。

GC Roots包括:

  • 虚拟机栈中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

重要注意事项:将对象设置为null并不能避免内存泄露

这是一个常见的误解。将引用设置为null只是解除了一个引用关系,如果对象仍然被其他活跃引用持有,它依然无法被GC回收。

java 复制代码
public class NullAssignmentMyth {
    private static Map<String, Object> globalCache = new HashMap<>();
    
    public void commonMistake() {
        Object expensiveObject = new byte[10 * 1024 * 1024]; // 10MB对象
        globalCache.put("key", expensiveObject);
        
        // 错误认知:认为设置为null就能释放内存
        expensiveObject = null;
        
        // 实际上,对象仍然被globalCache引用,无法被GC回收
    }
    
    public void correctApproach() {
        Object expensiveObject = new byte[10 * 1024 * 1024];
        globalCache.put("key", expensiveObject);
        
        // 正确的释放方式
        globalCache.remove("key");  // 先从集合中移除引用
        expensiveObject = null;     // 再设置为null
    }
}

如何定位内存泄露

首先要知道内存泄露的特征有哪些,在发生崩溃之前提前定位原因。

内存泄露的特征

系统越来越慢,并伴随着内存的使用率过高,因为内存不足 GC频率增高(Full GC频率增加,回收前后可能内存五无明显变化),GC操作是CPU密集型操作,所以存在内存泄露的场合最后必然CPU使用率100%。或者出现OutOfMemory了。简单概括就下面几点:

  • Minor GC/Full GC越来越频繁
  • 每次GC后堆内存使用率不下降或持续上升
  • GC暂停时间逐渐变长
  • 老年代使用率持续增长
  • 最后就系统崩溃OOM

定位内存泄漏

通过内存泄漏的特征也可以看到,GC回收的频率和效果 是内存泄漏的重要特征,也是关乎系统性能的重要指标(如果GC频率高,效果差 对系统性能的影响是非常大的,特别是FULL GC)。

不管是定位内存泄漏问题,还是保障系统的稳定运行这两个步都是必不可少的。第一步 GC日志的打印及分析,第二步 导出堆转储文件。

打印GC日志:命令各垃圾回收均有点区别,jdk 17 G1 GC日志打印 -Xlog:gc*,g1*:file=/path/to/gc.log:time,level,tags,输出结果如下:

js 复制代码
[2025-10-25T21:35:17.927+0800][info] GC(3838) Pause Young (Normal) (G1 Evacuation Pause) 4043M->4043M(4044M) 0.445ms
//-- FULL GC 回收前4043 回收后 4043 总内存4044M ,回收时间为12.924ms
[2025-10-25T21:35:17.940+0800][info] GC(3839) Pause Full (G1 Compaction Pause) 4043M->4043M(4044M) 12.924ms
[2025-10-25T21:35:17.956+0800][info] GC(3840) Pause Full (G1 Compaction Pause) 4043M->4043M(4044M) 15.300ms
[2025-10-25T21:35:17.957+0800][info] GC(3841) Pause Young (Concurrent Start) (G1 Evacuation Pause) 4043M->4043M(4044M) 0.647ms
[2025-10-25T21:35:17.957+0800][info] GC(3843) Concurrent Mark Cycle
[2025-10-25T21:35:17.974+0800][info] GC(3842) Pause Full (G1 Compaction Pause) 4043M->4043M(4044M) 16.871ms
[2025-10-25T21:35:17.991+0800][info] GC(3844) Pause Full (G1 Compaction Pause) 4043M->4043M(4044M) 17.143ms
[2025-10-25T21:35:17.991+0800][info] GC(3843) Concurrent Mark Cycle 34.722ms

通过上面的GC日志发现,FULL GC 前后的内存空间没有发生变化,因此多半是内存泄露。

导出堆转储文件,用工具打开就行了,下图也是之前解决OOM时候记录JProfiler打开的图片,很清晰的能看到各对象的大小,以及对象的GC ROOT 我们去排查各大对象对应的代码就行了:

分享一个OOM 和 FULL GC 的案例:

上午系统崩溃(OOM),下午就喜提大礼包
CTO 是如何排查FullGC异常的

总结

java 深度调试技术 第3章 【内存泄露分析和堆内存设置】讲了还是很多基础的理论,比如GC回收的机制,详细解释了内存泄露的原理,内存和垃圾回收设置等。本篇文章只是把其中最核心的知识提取出来。比如内存泄露的特征,GC日志的打印分析,堆文件的打印分析这些基础掌握之后就在实际开发中解决问题了。但是对于java堆内存模型,垃圾回收算法这些本篇文章就没过多的描述了(推荐深入理解java虚拟机)。

相关推荐
007php0073 小时前
京东面试题解析:同步方法、线程池、Spring、Dubbo、消息队列、Redis等
开发语言·后端·百度·面试·职场和发展·架构·1024程序员节
爬山算法3 小时前
Redis(84)如何解决Redis的缓存击穿问题?
java·redis·缓存
程序定小飞3 小时前
基于springboot的电影评论网站系统设计与实现
java·spring boot·后端
码事漫谈3 小时前
高性能推理引擎的基石:C++与硬件加速的完美融合
后端
码事漫谈3 小时前
C++与边缘AI:在资源荒漠中部署智能的工程艺术
后端
一 乐4 小时前
汽车销售|汽车推荐|基于SprinBoot+vue的新能源汽车个性化推荐系统(源码+数据库+文档)
java·数据库·vue.js·汽车·毕设·汽车个性化推荐
绝无仅有4 小时前
腾讯面试文章解析:MySQL慢查询,存储引擎,事务,结构算法等总结与实战
后端·面试·github
虾说羊4 小时前
最细Maven教程以及Maven私服搭建
java·数据库·maven
杯莫停丶4 小时前
设计模式之:装饰器模式
java·设计模式·装饰器模式