内存泄露
Java虚拟机是使用引用计数法和可达性分析来判断对象是否可回收,本质是判断一个对象是否还被引用,如果没有引用则回收。内存泄露是指,程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。简单来说,就是应该被垃圾回收的对象没有回收掉,导致占用的内存越来越多,最终导致内存溢出。
内存泄露主要原因:
- 在内存中加载过大的数据,例如从数据库取出过多数据;
- 资源未关闭造成的内存泄漏;
- 变量不合理的作用域,使用完毕,如果没有及时的赋值为
null,则会造成内存泄露; - 长生命周期的对象中引用短生命周期对象,很可能会出现内存泄露;
举个例子,创建的连接不再使用时,需要调用close方法关闭连接,只有连接被关闭后,GC才会回收对应的对象。忘记关闭这些资源会导致持续占有内存,无法被GC回收。这样就会导致内存泄露,最终导致内存溢出。
public class MemoryLeak {
public static void main(String[] args) {
try{
Connection conn =null;
Class.forName("com.mysql.jdbc.Driver");
conn =DriverManager.getConnection("url","","");
Statement stmt =conn.createStatement();
ResultSet rs =stmt.executeQuery("....");
} catch(Exception e){//异常日志
} finally {
// 1.关闭结果集 Statement
// 2.关闭声明的对象 ResultSet
// 3.关闭连接 Connection
}
}
}
排查过程
根据运维之前收集到的内存数据、GC日志尝试判断哪里出现了问题。结果发现老年代的内存使用就算是发生GC也一直居高不下,而且随着时间推移也越来越高。

使用jstat -gc <vmid> 查看GC垃圾回收统计信息,看Full GC后堆空间使用内存还持续增长,且有增长到Xmx设定值的趋势,基本可以肯定存在内存泄露。如果当前完全垃圾回收后内存增长到一个值之后,又能回落,总体上处于一个动态平衡,那么内存泄漏基本可以排除;也可以隔断时间抽取老年代占用内存情况,如果老年代占用情况持续上升也很有可能存在内存泄露的情况。
解决方案
- 内存泄漏的主要表象就是内存不足,所以首先要看一下JVM启动参数中内存空间分配是否过小,如果是这种问题调整该参数即可;
- 确定是否新部署或有新变更,首先需要确认是否在最近进行了新的部署或有其他相关的变更,例如代码更新、配置修改等。这些变更可能导致应用出现性能问题,特别是在高负载情况下;
- 内存泄漏解决方案,最经典的就是用MAT工具分析
dump文件,但如果dump文件巨大就不建议这样,可以使用其他方案,例如:重启、本地复现、jmap -histo:live <pid>在线进行分析等其他方案解决。
使用MAT定位内存泄漏思路:
-
打开MAT中
histogram,找到堆内存中占用最大的对象,内存泄漏很有可能就是由大对象导致的;

-
由大对象找被哪些线程引用,查看内存占用最大的线程;


-
从线程中的堆栈信息找到项目中自定义的包和对象,从而可定位到具体的代码;


本文详细探讨了内存泄露的定义、问题影响,以及常见的内存泄露原因,包括变量作用域不合理、向静态集合添加数据、内部类引用、数据结构操作和连接未释放。同时提供了相应的解决策略,强调了在编程中避免内存泄露的重要性。
1万+

被折叠的 条评论
为什么被折叠?



