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

相关推荐
西西学代码18 小时前
Flutter---Stream
java·服务器·flutter
Blossom.11820 小时前
移动端部署噩梦终结者:动态稀疏视觉Transformer的量化实战
java·人工智能·python·深度学习·算法·机器学习·transformer
静若繁花_jingjing21 小时前
IDEA下载
java·ide·intellij-idea
代码丰21 小时前
函数式接口+default接口+springAi 中的ducumentReader去理解为什么存在default接口的形式
java
果汁华1 天前
java学习连续打卡30天(1)
java
q***92511 天前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
武子康1 天前
Java-171 Neo4j 备份与恢复 + 预热与执行计划实战
java·开发语言·数据库·性能优化·系统架构·nosql·neo4j
m0_639817151 天前
基于springboot火锅店管理系统【带源码和文档】
java·spring boot·后端
会编程的林俊杰1 天前
SpringBoot项目启动时的依赖处理
java·spring boot·后端
码事漫谈1 天前
C++循环结构探微:深入理解while与do...while
后端