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虚拟机)。

相关推荐
VX:Fegn089517 分钟前
计算机毕业设计|基于ssm + vue超市管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
徐徐同学34 分钟前
cpolar为IT-Tools 解锁公网访问,远程开发再也不卡壳
java·开发语言·分布式
Mr.朱鹏2 小时前
Nginx路由转发案例实战
java·运维·spring boot·nginx·spring·intellij-idea·jetty
白露与泡影3 小时前
2026版Java架构师面试题及答案整理汇总
java·开发语言
历程里程碑3 小时前
滑动窗口---- 无重复字符的最长子串
java·数据结构·c++·python·算法·leetcode·django
qq_229058014 小时前
docker中检测进程的内存使用量
java·docker·容器
我真的是大笨蛋4 小时前
InnoDB行级锁解析
java·数据库·sql·mysql·性能优化·数据库开发
钦拆大仁4 小时前
Java设计模式-单例模式
java·单例模式·设计模式
小手cool4 小时前
在保持数组中对应元素(包括负数和正数)各自组内顺序不变的情况下,交换数组中对应的负数和正数元素
java
笨手笨脚の4 小时前
深入理解 Java 虚拟机-04 垃圾收集器
java·jvm·垃圾收集器·垃圾回收