10个java常见内存泄露场景的模拟和解决方案
yund56 2025-07-28 23:31 4 浏览
模拟内存泄漏的场景是理解和排查内存泄漏问题的一个重要手段,今天就给大家贴一些常见内存泄漏的实例模拟吧!
了解了以下这些示例,在自己写代码和做review时,轻松拿捏内存泄露问题。
实例1:静态集合类导致的内存泄漏
【java】
import java.util.ArrayList;
import java.util.List;
public class StaticCollectionLeak {
// 使用静态集合类持有对象引用
private static List<Object> staticList = new ArrayList<>();
public static void main(String[] args) {
// 不断向静态集合中添加对象
while (true) {
staticList.add(new Object());
// 模拟任务执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 打印当前静态集合的大小(可选)
System.out.println("Static list size: " + staticList.size());
}
}
}
说明:静态集合类(如ArrayList)在应用程序的整个生命周期内存在,如果它们持有大量对象的引用,这些对象即使不再需要也无法被垃圾回收,从而导致内存泄漏。
如何避免:
1.及时清理集合:
当确定集合中的对象不再需要时呢,要及时从集合中移除它们哦,这样垃圾回收器才能顺利回收那些不再使用的对象。
2.使用弱引用:
可以考虑使用像 WeakHashMap 这样的弱引用集合呀,这样当对象没有其他强引用时,垃圾回收器就能自动回收它们
3.避免长时间持有对象引用:
尽量不要让静态集合长时间持有大量对象的引用呀,要合理地管理对象的生命周期~
实例2:未关闭的资源导致的内存泄漏
【java】
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
public class UnclosedResourceLeak {
public static void main(String[] args) {
// 不断打开网络连接但不关闭
while (true) {
try {
URL url = new URL("http://example.com");
URLConnection conn = url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
// 处理读取的数据(这里只是简单读取并未关闭流)
}
// 注意:这里没有关闭BufferedReader和URLConnection,导致资源泄漏
} catch (IOException e) {
e.printStackTrace();
}
// 模拟任务执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
说明:未关闭的资源(如文件流、网络连接等)会占用系统资源,如果这些资源没有及时释放,就可能导致内存泄漏。在这个例子中,我们不断打开网络连接但不关闭它们,从而模拟内存泄漏。
如何避免:
1.使用 try-with-resources 语句:
在 Java 里呀,可以用这个语句来自动管理资源呢,它会在代码块执行完毕后自动关闭资源。
2.显式关闭资源:
如果没用那个语句的话,也要记得在用完资源后,显式地调用它们的关闭方法呀,就像关上门一样,要把资源也关好哟。
3.使用资源池:
对于一些经常需要使用的资源呀,可以考虑使用资源池来管理呢,这样资源的复用率会更高,也能减少内存泄漏的风险。
实例3:长生命周期对象持有短生命周期对象导致的内存泄漏
【java】
import java.util.LinkedHashMap;
import java.util.Map;
public class CacheLeak {
// 使用LinkedHashMap作为缓存
private Map<Integer, Object> cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Object> eldest) {
// 这里只是简单示例,并未实现真正的缓存淘汰策略
return false;
}
};
public static void main(String[] args) {
CacheLeak demo = new CacheLeak();
demo.start();
}
private void start() {
// 不断向缓存中添加对象
while (true) {
cache.put((int) (Math.random() * 1000), new Object());
// 模拟任务执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 打印当前缓存的大小(可选)
System.out.println("Cache size: " + cache.size());
}
}
}
说明:长生命周期对象(如缓存)如果持有短生命周期对象的引用,并且没有实现合适的缓存淘汰策略,就可能导致内存泄漏。
如何避免:
1.实现合适的缓存淘汰策略:
要给缓存设置一个合适的淘汰策略呀,比如 LRU(最近最少使用)或者 TTL(生存时间)之类的呢,这样那些不再需要的短生命周期对象就能被及时淘汰啦,就不会一直占用内存
也可以考虑使用弱引用来持有那些短生命周期对象的引用呀,这样当它们没有其他强引用时,垃圾回收器就能把它们回收掉啦,就不会造成内存泄漏。
要定期地对缓存进行清理呀,把那些不再需要的对象从缓存中移除掉呢,这样也能避免内存泄漏~
实例4:单例模式中的内存泄漏
【java】
public class Singleton {
private static Singleton instance;
private Context context; // Context 可能是 Activity 或者其他长生命周期的对象
private Singleton(Context context) {
this.context = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
// 其他方法...
}
说明:在单例模式中,如果单例对象持有一个长生命周期对象的引用(如 Activity),并且这个长生命周期对象在不需要时没有被正确清理,就可能导致内存泄漏。因为单例对象的生命周期和应用程序一样长,所以它会一直持有那个长生命周期对象的引用,不让它被垃圾回收。
如何避免:
1.不要直接持有长生命周期对象的强引用:
可以让单例对象不直接持有那个长生命周期对象的强引用,比如可以使用弱引用或者软引用之类。
2.在适当的时机清理引用:
如果单例对象确实需要持有那个长生命周期对象的引用,那也要在适当的时机,比如那个长生命周期对象不再需要时,及时清理掉这个引用。
3.使用生命周期管理工具:
比如Spring框架中,可以作为bean,利用容器来管理长生命周期对象的生命周期,这样就能更好地避免内存泄漏啦。
实例5:非静态内部类中的内存泄漏
【java】
public class OuterClass {
private class InnerClass {
// 内部类代码...
}
public void start() {
InnerClass inner = new InnerClass();
// 内部类对象被创建并持有外部类对象的隐式引用
}
}
说明:非静态内部类会隐式持有外部类对象的引用。如果外部类对象不再需要,但由于内部类对象仍然存在(比如被其他对象持有),那么外部类对象也无法被垃圾回收,从而导致内存泄漏。
如何避免:
1.及时清理内部类对象的引用:
当确定内部类对象不再需要时,要及时清理掉它的引用,可以设置为null,这样垃圾回收器就能顺利回收外部类对象。
2.使用静态内部类:
可以考虑将非静态内部类改为静态内部类哦。静态内部类不会隐式持有外部类对象的引用,这样就能避免这个问题。
实例6:ThreadLocal 中的内存泄漏
【java】
public class ThreadLocalLeak {
private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public static void set(byte[] data) {
threadLocal.set(data);
}
// 通常情况下,应该有一个 clear 方法来清理 ThreadLocal 中的数据
// 但如果忘记调用 clear 方法,就可能导致内存泄漏
public static void clear() {
threadLocal.remove();
}
}
说明:ThreadLocal用于为每个线程提供变量的独立副本。但如果ThreadLocal被设置为一个长生命周期的对象(此处的字节数组),并且在线程结束后没有调用ThreadLocal的remove方法来清理这些对象,就可能导致内存泄漏。
因为ThreadLocalMap的entry的键是弱引用,但值是强引用,所以即使线程结束了,只要ThreadLocalMap中的值还被强引用着,就无法被垃圾回收。
如何避免:
1. 在使用完ThreadLocal后,一定要调用其remove()方法:
这样可以清除当前线程对该变量的引用,防止内存泄漏。你可以把remove()方法放在finally块中,确保即使在发生异常时也能正确清理。
2. 避免将ThreadLocal变量声明为static:
因为static变量的生命周期会与类加载器一样长,如果不及时清理,很容易导致内存泄漏。
3. 使用InheritableThreadLocal时要谨慎:
虽然InheritableThreadLocal可以在父子线程之间传递ThreadLocal变量,但也可能导致内存泄漏,所以在使用时需要特别小心。
4. 设置合适的初始值和清理逻辑:
为ThreadLocal变量设置合适的初始值和清理逻辑,确保在不需要时能够正确释放资源。
5. 避免过度使用ThreadLocal:
虽然ThreadLocal提供了线程隔离数据的便利,但过度使用也可能导致内存泄漏和其他问题。在设计系统时,要权衡使用ThreadLocal的利弊,并考虑其他可能的解决方案。
实例7:重写equals和hashCode方法时的内存泄漏
【java】
public class CustomObject {
private String id;
private byte[] data;
public CustomObject(String id, byte[] data) {
this.id = id;
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomObject that = (CustomObject) o;
return id.equals(that.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
// 其他方法...
}
说明:在使用HashSet或HashMap等集合时,如果自定义对象的equals和hashCode方法实现不当,就可能导致内存泄漏。因为当从集合中移除对象时,如果hashCode方法返回的值不正确,就可能导致对象无法被正确找到和移除,从而一直留在集合中占用内存。
如何避免:
1. 正确实现equals和hashCode方法:
equals方法要满足自反性、对称性、传递性、一致性和对于任何非null值的x,x.equals(null)必须返回false这些特性哦。hashCode方法呢,只要对象的equals方法所比较的信息没有被修改,那么对这同一个对象调用多次hashCode方法必须始终如一地返回同一个整数呢。如果两个对象是相等的,那么它们的hashCode值一定要相同。
2. 不要使用可变字段来计算hashCode:
如果用来计算hashCode的字段在对象生命周期中可能会被修改,那就不要用这些字段来计算,不然可能会导致hashCode值发生变化,从而使得对象在集合中的位置也发生变化,这样就很难正确找到和移除对象。
3. 避免使用复杂的对象作为键:
如果可能的话,尽量避免使用复杂的自定义对象作为HashMap或HashSet的键哦,可以使用一些简单的、不可变的类型,比如String、Integer这些。
4. 使用合适的集合类型:
选择合适的集合类型也能避免一些问题呢。比如,如果你只需要判断对象是否存在而不需要关心对象的顺序,那可以使用LinkedHashSet呀,它既能保证元素的唯一性,又能保持插入的顺序。
5. 定期清理集合:
虽然这不是直接解决equals和hashCode问题的方法,但定期清理集合中的无用元素也是一种避免内存泄漏的好办法哦。可以通过遍历集合,然后移除那些不再需要的元素。
实例8:动态代理导致的内存泄漏
【java】
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ConcurrentHashMap;
public class DynamicProxyLeak {
private static ConcurrentHashMap<String, Object> proxyMap = new ConcurrentHashMap<>();
public static void main(String[] args) {
while (true) {
createProxy();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Proxy count: " + proxyMap.size());
}
}
private static void createProxy() {
Object proxyInstance = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[]{MyInterface.class},
new MyInvocationHandler()
);
proxyMap.put(System.nanoTime() + "", proxyInstance);
}
interface MyInterface {
void doSomething();
}
static class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理方法调用
return null;
}
}
}
说明:
在这个例子中,我们使用
Proxy.newProxyInstance创建了大量的动态代理对象,并将它们存储在一个ConcurrentHashMap中。由于动态代理对象会持有其InvocationHandler的引用,如果InvocationHandler持有外部对象的引用(比如在这个例子中,MyInvocationHandler可能持有其他长生命周期对象的引用),并且这些动态代理对象没有被及时清理,就可能导致内存泄漏。
如何避免:
1.及时清理持有的动态代理对象:
要定期检查和清理不再需要的动态代理对象。可以使用一些策略,比如基于时间的清理策略,或者当某个条件满足时就移除对应的动态代理对象。这样可以减少无用的动态代理对象对内存的占用。
2.避免InvocationHandler持有长生命周期对象的引用:
如果可能的话,尽量不要让InvocationHandler持有长生命周期对象的引用。如果确实需要持有,那要考虑在使用完后及时清理这些引用,比如将引用设置为null,或者采用其他方式来管理这些引用的生命周期。
实例9:ThreadLocal 与线程池结合使用时的内存泄漏
【java】
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
public class ThreadLocalWithThreadPoolLeak {
private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
byte[] data = new byte[ThreadLocalRandom.current().nextInt(1024, 1024 * 1024)]; // 分配大内存
threadLocal.set(data);
try {
// 模拟任务执行
Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 忘记清理 ThreadLocal 中的数据
// threadLocal.remove();
}
});
}
// 关闭线程池(在实际应用中应该等待所有任务执行完毕再关闭)
executor.shutdown();
}
}
说明:在这个例子中,我们使用ThreadLocal来为每个线程存储数据,并且与线程池结合使用。由于线程池中的线程是复用的,如果ThreadLocal中的数据在任务执行完毕后没有被及时清理(即没有调用threadLocal.remove()方法),就可能导致内存泄漏。因为ThreadLocal中的数据会一直占用内存,直到线程池中的线程被销毁。
如何避免:
1.使用try-finally块确保清理:
在每个使用ThreadLocal的任务中,都可以用try-finally块来确保在任务执行完毕后调用threadLocal.remove()方法清理数据。
2.自定义线程池,覆盖线程的run方法:
可以自定义一个线程池,然后覆盖线程的run方法,在这个方法中添加对ThreadLocal数据的清理逻辑。不过这种方法需要对线程池的实现有一定了解,而且可能会影响到线程池的其他行为。3.使用线程池的钩子函数:
有些线程池实现提供了钩子函数,可以在线程执行前后执行一些自定义的逻辑。可以利用这些钩子函数来清理ThreadLocal数据。
4.使用弱引用的ThreadLocal:
虽然Java原生的ThreadLocal不支持弱引用,但可以通过一些第三方库或者自己实现一个弱引用的ThreadLocal。这样,即使数据没有被及时清理,也不会导致严重的内存泄漏问题,因为弱引用的对象在垃圾回收时更容易被回收。
5.定期清理ThreadLocal数据:
虽然这种方法不是很推荐,因为它可能会引入额外的复杂性和开销,但在某些情况下,可以通过定期遍历线程池中的所有线程,并清理它们持有的ThreadLocal数据来避免内存泄漏。
实例10:缓存中的内存泄漏
【java】
import java.util.concurrent.ConcurrentHashMap;
public class CacheLeak {
private static ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public static void main(String[] args) {
while (true) {
String key = String.valueOf(System.nanoTime());
Object value = new Object();
cache.put(key, value);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Cache size: " + cache.size());
}
}
}
说明:在这个例子中,我们使用ConcurrentHashMap来实现一个简单的缓存。然而,由于我们没有实现任何缓存淘汰策略(比如 LRU、LFU 等),并且不断向缓存中添加新的键值对,就可能导致内存泄漏。因为缓存中的对象会一直占用内存,直到程序结束。
在实际应用中,我们应该根据业务需求实现合适的缓存淘汰策略来避免内存泄漏。
以上就是今天整理的各种泄露场景了,觉得有用的话,记得点赞关注哦!
相关推荐
- 在这款15年老端游的手机版中,正在上演着“萌新拯救计划”
-
以往我们判断一款刚公测的新手游到底火不火,不是瞅苹果的免费榜畅销榜,就是看各家数据网站的预估流水。不过如今这个法子放在《剑网3无界》身上似乎就不那么适用了。作为一款与原端游完全数据互通的手游,点卡制收...
- 708090后集体回忆!88款经典街机游戏,你通关过几部?
-
街机厅的霓虹灯在夜色中闪烁,投币口“叮当”的声响此起彼伏,摇杆与按键的碰撞声混合着玩家的欢呼与叹息,构成了那个年代独有的电子交响乐。对于70后、80后、90后来说,街机不仅是游戏,更是一段无法复制的...
- 爷青回!这10款童年小游戏,玩过5个以上的都当爸妈了吧?
-
当手机游戏被3A画质与开放世界统治的今天,那些藏在像素点阵里的童年记忆,才是真正刻进DNA的快乐密码!我们翻遍全网玩家回忆录,结合抖音、Steam等平台数据,为你揭开这代人的集体记忆封印一、经典益智三...
- 怀旧时刻:PS2十大经典动作游戏盘点,老玩家不可错过的青春回忆
-
说起PS2,那可是游戏史上最火的主机之一,上面好游戏多得数不清,给咱们带来过不少欢乐时光。今天,小核桃就带大家回忆一下PS2上那些超经典的动作游戏,一起重温那些热血沸腾的日子吧!当年在电玩店看到《战神...
- 又是一年仲夏,三十年前的暑假,你还记得在玩什么游戏吗?
-
今年山东的夏天似乎比往年都热,夜晚繁星点点,本该轻柔的晚风却没有丝毫凉意,伴随着远处草丛里此起彼伏的虫鸣声,听的让人心里愈加烦躁,翻来覆去睡不着的笔者,无聊且乏困地坐在院子里的老槐树下,思绪却不由自主...
- 十六年前的首款安卓1.0手机,内置物理全键盘,如今二手45元
-
周末聊点轻松的话题,说起智能手机系统之争,安卓和iOS绝对是两大“宿敌”。2007年苹果在乔布斯带领下发布了初代iPhone,也凭借iOS系统掀起了智能手机的新时代。短短一年后,谷歌联合HTC推出了...
- HTC巅峰时期的安卓手机,自带全键盘,居然很多人用过
-
上次写三部最经典的侧滑盖全键盘手机,居然很多人报出了DesireZ的大名,这让我很吃惊。因为这部手机没有行货只有水货,你们咋都用过?那今天好好聊聊它。十多年前,HTC是安卓手机领域绝对的霸主,当年只...
- 十年前的 iPhone 6s 还在 “服役”:一部手机的 “超长待机” 启示录
-
当iPhone16系列已经开始预热,有人却还握着2015年发布的iPhone6s刷着微信、看视频——这部诞生已近十年的手机,至今仍在不少人的生活里扮演着重要角色。它的“超长寿命”...
- SFC黄金时代10款动作RPG神作,每一款都是回忆满满的经典游戏!
-
任天堂的16位主机SFC可是游戏史上的一个高峰,它用像素画面打造出无数经典作品,其中结合动作和冒险的ARPG特别受欢迎。最近几年复古风又火起来,大厂们忙着移植或重制老游戏,模拟器也让玩家轻松重温旧梦。...
- 揭秘十年前真正的游戏手机:索尼爱立信R800魔改系统超乎想象!
-
对于游戏手机的起源,众说纷纭,有人认为是黑鲨问世,有人说是红魔领路,还有人坚称自从iPhone问世起,游戏手机就已然存在。然而,如果从更宏观的角度审视,这些所谓的游戏手机,其本质上仍旧是多功能的智能手...
- 专属于八零,九零后的插卡游戏,你还记得吗
-
1.魂斗罗这是我玩过的第一款插卡游戏,永远记得上上下下左左右右,BABA开始,这个可以有三十条命的“魔法。”也是第一次体验双打游戏的那行配合的责任感使命感。2.忍者神龟四只神龟的名字都是意大利著名的画...
- Java编程的那些屎山代码分析之二(java编程神器)
-
以下是个人总结的一些代码习惯问题和优化,单独一个也许不起眼,但堆积起来,就让一个项目代码变成一座屎山。1.滥用`public`修饰符o重要性:滥用`public`修饰符可能导致类的成员变量或方法被不...
- 六种java的多线程设计模式详解和代码举例
-
java的多线程处理,有哪些模式可以使用呢,如何使用呢。本文列举了六种多线程设计模式供大家参考。1.生产者-消费者模式设计理念:生产者-消费者模式通过协调两个线程(生产者和消费者)来处理数据,生产者生...
- java的四种引用(java 中都有哪些引用类型)
-
java中的引用分为4种1.强引用引用存在就不会被GC*2.软引用heapmemory(堆内存)满了就会被GC掉*3.弱引用每次GC就会回收掉(应用有:ThreadLocal)*4....
- @程序员 2020了看不懂这些动图,你可能是个假的程序员
-
点击上方Java编程技术乐园,轻松关注!及时获取有趣有料的技术文章文章很有趣,开心一下,如果有收获,记得点赞和关注哦~「1」外包产品交付,给客户演示时「2」与领导斗智斗勇,躲猫猫「3」领导总是能识破程...
- 一周热门
- 最近发表
-
- 在这款15年老端游的手机版中,正在上演着“萌新拯救计划”
- 708090后集体回忆!88款经典街机游戏,你通关过几部?
- 爷青回!这10款童年小游戏,玩过5个以上的都当爸妈了吧?
- 怀旧时刻:PS2十大经典动作游戏盘点,老玩家不可错过的青春回忆
- 又是一年仲夏,三十年前的暑假,你还记得在玩什么游戏吗?
- 十六年前的首款安卓1.0手机,内置物理全键盘,如今二手45元
- HTC巅峰时期的安卓手机,自带全键盘,居然很多人用过
- 十年前的 iPhone 6s 还在 “服役”:一部手机的 “超长待机” 启示录
- SFC黄金时代10款动作RPG神作,每一款都是回忆满满的经典游戏!
- 揭秘十年前真正的游戏手机:索尼爱立信R800魔改系统超乎想象!
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 亲测源码 (36)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)
- 表格乘法的公式怎么设置 (34)
- sumif函数的例子 (34)
- 图片素材 (36)