Java内存泄漏最全详解(6大原因及解决方案)
liuian 2025-05-27 15:54 4 浏览
大家好,我是mikechen。
内存泄漏是经常出现的线上故障,也是大厂面试经常考察的,下面我就全面来详解内存泄漏以及解决方案@mikechen
本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》里面。
什么是内存泄漏
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。
随着垃圾回收器活动的增加以及内存占用的不断增加,程序性能会逐渐表现出来下降,极端情况下,会引发OutOfMemoryError导致程序崩溃。
内存泄漏的原因
JVM 虚拟机是使用引用计数法和可达性分析来判断对象是否可回收,本质是判断一个对象是否还被引用,如果没有引用则回收。
在开发的过程中,由于代码的实现不同就会出现很多种内存泄漏问题,让gc 系统误以为此对象还在引用中,无法回收,造成内存泄漏。
内存泄漏的危害
- 长时间运行,程序变卡,性能严重下降
- 程序莫名其妙挂掉
- OutOfMemoryError错误
- 乱七八糟的错误,还不易排查
内存泄漏有哪些情况
内存泄漏原因很多,因此这里给出最常见的几种。
1.资源未关闭造成的内存泄漏
各种连接,比如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。
只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏,因此最好按照下面的做法处理资源类,伪代码如下:
publicvoidhandleResource {
try {
// open connection
// handle business
} catch (Throwable t) {
// log stack
} finally {
// close connection
}}
2.静态集合类
如HashMap、LinkedList等等,如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
生命周期长的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
3.ThreadLocal的误用
ThreadLocal一定要列在Java内存泄露的榜首,总能在不知不觉中将内存泄露掉,一个常见的例子是:
publicvoidtestThreadLocalMemoryLeaks {
ThreadLocal<List<Integer>> localCache = new ThreadLocal<>;
List<Integer> cacheInstance = new ArrayList<>(10000);
localCache.set(cacheInstance);localCache = new ThreadLocal<>;
}
当localCache的值被重置之后cacheInstance被ThreadLocalMap中的value引用,无法被GC。
但是其key对ThreadLocal实例的引用是一个弱引用,本来ThreadLocal的实例被localCache和ThreadLocalMap的key同时引用,但是当localCache的引用被重置之后。
则ThreadLocal的实例只有ThreadLocalMap的key这样一个弱引用了,此时这个实例在GC的时候能够被清理。
上面这张图详细地揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1)Thread中有一个map,就是ThreadLocalMap
2)ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3)ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。
那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
4.变量不合理的作用域
一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
public class UsingRandom {
private String msg;
public void receiveMsg(){
readFromNet();// 从网络中接受数据保存到msg中
saveDB();// 把msg保存到数据库中
}
}
如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。
实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。
5.内部类持有外部类
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
6.堆外内存无法回收
堆外内存不受gc的管理,可能因为第三方的bug出现内存泄漏
内存泄漏的解决办法
1.少使用静态变量
尽量减少使用静态变量,或者使用完及时 赋值为 null;
2.明确对象有效作用域
明确内存对象的有效作用域,尽量缩小对象的作用域,能用局部变量处理的不用成员变量,因为局部变量弹栈会自动回收;
3.注意声明周期引用
减少长生命周期的对象持有短生命周期的引用;
4.注意Sting的使用
使用StringBuilder和StringBuffer进行字符串连接,Sting和StringBuilder以及StringBuffer等都可以代表字符串,其中String字符串代表的是不可变的字符串,后两者表示可变的字符串。
如果使用多个String对象进行字符串连接运算,在运行时可能产生大量临时字符串,这些字符串会保存在内存中从而导致程序性能下降。
5.不需要对象手动设置Null
对于不需要使用的对象手动设置null值,不管GC何时会开始清理,我们都应及时的将无用的对象标记为可被清理的对象;
6.及时关闭各种链接
各种连接(数据库连接,网络连接,IO连接)操作,务必显示调用close关闭。
以上
本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集》里面。
相关推荐
- 那些Java架构师必知必会的技术
-
Java基础#Java对象的内存布局MapStruct解了对象映射的毒周末我把HashMap源码又过了一遍Java7和Java8中的ConcurrentHashMap原理解析Java中自定...
- Java内存泄漏最全详解(6大原因及解决方案)
-
大家好,我是mikechen。内存泄漏是经常出现的线上故障,也是大厂面试经常考察的,下面我就全面来详解内存泄漏以及解决方案@mikechen本篇已收于mikechen原创超30万字《阿里架构师进阶专题...
- 都说Feign是RPC,没有侵入性,为什么我的代码越来越像 C++
-
1.概览随着SpringCloud的流行性,Feign已经成为RPC的事实标准,由于其构建与Http协议之上,对请求和返回值缺少规范约束,在日常开发过程中经常由于设计不当对系统造成一...
- 面试题系列-java后端面试题List 和 Set 的区别
-
List和Set的区别List,Set都是继承自Collection接口List特点:元素有放入顺序,元素可重复,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(...
- 一直不理解为什么在重写equals方法时都要重写hashCode方法
-
为什么在重写equals方法时都要重写hashCode方法呢?首先jdk的要求是这样的,equals与hashcode间的关系:1、如果两个对象相同(即用equals比较返回true),那么它们的ha...
- 翻了ConcurrentHashMap1.7 和1.8的源码,我总结了它们的主要区别
-
ConcurrentHashMap思考:HashTable是线程安全的,为什么不推荐使用?HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次...
- 10 个经典的 Java 集合面试题,看你能否答得上来?
-
来自:evget.com/article/2014/11/27/21869.html这里有10个经典的Java面试题,也为大家列出了答案。这是Java开发人员面试经常容易遇到的问题,相信你了解和掌握之...
- MyBatis3.5.11-从入门到高阶
-
一.课程介绍MyBatis概述MyBatis基础应用MyBatis高级MyBatis进阶二.MyBatis概述1、为什么需要MyBatis在我们程序中,运行时期产生的数据都是存放在内存中的,那么在内存...
- 灵魂拷问:如何检查 Java 数组中是否包含某个值?
-
作者|沉默王二责编|Elle在逛programcreek的时候,我发现了一些专注细节但价值连城的主题。比如说:如何检查Java数组中是否包含某个值?像这类灵魂拷问的主题,非常值得深入地研...
- Java后端学习路线是什么?
-
关于Java后端(SpringBoot为主)学习路线:一、Java基础阶段编程语言基础掌握基本数据类型(如int、double、char等)、变量、常量的定义和使用。理解运算符(算术、关...
- Mybatis配置文件XML全貌详解,再不懂我也没招了
-
一、为什么要使用配置文件试想,如果没有配置文件,我们的应用程序将只能沿着固定的姿态运行,几乎不能做任何动态的调整,那么这不是一套完美的设计,因为我们希望拥有更宽更灵活的操作空间和更多的兼容度,同时也能...
- 软件性能调优全攻略:从瓶颈定位到工具应用
-
性能调优是软件测试中的重要环节,旨在提高系统的响应时间、吞吐量、并发能力、资源利用率,并降低系统崩溃或卡顿的风险。通常,性能调优涉及发现性能瓶颈、分析问题根因、优化代码和系统配置等步骤,调优之前需要先...
- 你还在使用Guava的Lists.newArrayList()吗
-
Guava说起Guava,做Java开发的应该没人不知道吧,毕竟“google出品,必属精品”。虽然应该没有Spring那样让Javaer无法避开,但是其中很多工具类的封装还是让人欲罢不能。而我们今天...
- JDK成长记7:3张图搞懂HashMap底层原理
-
HashMap基本原理和优缺点HashMap基本原理和优缺点一句话讲,HashMap底层数据结构,JDK1.7数组+单向链表、JDK1.8数组+单向链表+红黑树。HashMap的3个底层原理Hash...
- 如何深度理解mybatis?
-
深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
python使用fitz模块提取pdf中的图片
-
《人人译客》如何规划你的移动电商网站(2)
-
Jupyterhub安装教程 jupyter怎么安装包
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- uniapp textarea (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)