首页 > 系统相关 >常见的内存泄漏及其解决方案

常见的内存泄漏及其解决方案

时间:2024-08-02 11:24:15浏览次数:13  
标签:泄漏 解决方案 void static 内存 new public

内存泄漏是Java开发中一个常见且令人头疼的问题,即使在使用垃圾回收机制的Java中,也无法完全避免内存泄漏的出现。当对象不再需要时却仍然占据着内存,导致内存使用量不断增加,最终可能导致 OutOfMemoryError。本文将深入探讨Java中常见的内存泄漏及其解决方案,附带详细的代码示例,帮助你更好地理解和解决内存泄漏问题。

1. 常见的内存泄漏场景
  1. 静态集合类引起的内存泄漏
  2. 未关闭的IO资源
  3. 监听器和回调的非预期持有
  4. ThreadLocal引起的内存泄漏
  5. 自定义类加载器引起的内存泄漏
2. 静态集合类引起的内存泄漏

静态集合类(如 HashMap, ArrayList 等)在应用程序生命周期内是静态的,如果没有适当地移除不再需要的对象,会导致这些对象无法被垃圾回收,从而引起内存泄漏。

示例代码:

import java.util.HashMap;
import java.util.Map;

public class StaticCollectionLeak {
    private static final Map<Integer, String> cache = new HashMap<>();

    public void addToCache(int id, String value) {
        cache.put(id, value);
    }

    public static void main(String[] args) {
        StaticCollectionLeak leak = new StaticCollectionLeak();
        for (int i = 0; i < 100000; i++) {
            leak.addToCache(i, "value" + i);
        }
        // 内存使用量会不断增加
    }
}

解决方案:

确保及时移除不再需要的对象,或者使用 WeakHashMap 替代 HashMap

import java.util.WeakHashMap;
import java.util.Map;

public class StaticCollectionSolution {
    private static final Map<Integer, String> cache = new WeakHashMap<>();

    public void addToCache(int id, String value) {
        cache.put(id, value);
    }

    public static void main(String[] args) {
        StaticCollectionSolution solution = new StaticCollectionSolution();
        for (int i = 0; i < 100000; i++) {
            solution.addToCache(i, "value" + i);
        }
        // 内存使用量不会持续增加
    }
}
3. 未关闭的IO资源

未关闭的 InputStreamOutputStream 等IO资源,会导致内存泄漏。

示例代码:

import java.io.FileInputStream;
import java.io.IOException;

public class UnclosedIOLeak {

    public void readFile(String filePath) throws IOException {
        FileInputStream fis = new FileInputStream(filePath);
        // Do something with fis
        // 未关闭FileInputStream
    }

    public static void main(String[] args) {
        UnclosedIOLeak leak = new UnclosedIOLeak();
        try {
            leak.readFile("somefile.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

解决方案:

使用 try-with-resources 确保IO资源被自动关闭。

import java.io.FileInputStream;
import java.io.IOException;

public class ClosedIOSolution {

    public void readFile(String filePath) throws IOException {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            // Do something with fis
        } // FileInputStream将在这里被自动关闭
    }

    public static void main(String[] args) {
        ClosedIOSolution solution = new ClosedIOSolution();
        try {
            solution.readFile("somefile.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
4. 监听器和回调的非预期持有

注册的监听器或回调在不再需要时如果未被删除,会导致内存泄漏。

示例代码:

import java.util.ArrayList;
import java.util.List;

public class ListenerLeak {

    private final List<Runnable> listeners = new ArrayList<>();

    public void registerListener(Runnable listener) {
        listeners.add(listener);
    }

    // 没有方法来移除监听器

    public static void main(String[] args) {
        ListenerLeak leak = new ListenerLeak();
        leak.registerListener(() -> System.out.println("Listener 1"));
        leak.registerListener(() -> System.out.println("Listener 2"));
    }
}

解决方案:

提供移除监听器的方法,并在不需要时及时移除。

import java.util.ArrayList;
import java.util.List;

public class ListenerSolution {

    private final List<Runnable> listeners = new ArrayList<>();

    public void registerListener(Runnable listener) {
        listeners.add(listener);
    }

    public void unregisterListener(Runnable listener) {
        listeners.remove(listener);
    }

    public static void main(String[] args) {
        ListenerSolution solution = new ListenerSolution();
        Runnable listener1 = () -> System.out.println("Listener 1");
        Runnable listener2 = () -> System.out.println("Listener 2");

        solution.registerListener(listener1);
        solution.registerListener(listener2);

        // 移除监听器,避免内存泄漏
        solution.unregisterListener(listener1);
        solution.unregisterListener(listener2);
    }
}
5. ThreadLocal引起的内存泄漏

ThreadLocal 对象如果不及时移除,会导致内存泄漏,尤其是在使用线程池的情况下。

示例代码:

public class ThreadLocalLeak {

    private static final ThreadLocal<byte[]> threadLocal = ThreadLocal.withInitial(() -> new byte[1024 * 1024]);

    public static void main(String[] args) {
        threadLocal.get(); // 分配1MB内存
        // 未调用remove方法,导致内存泄漏
    }
}

解决方案:

在不需要时调用 ThreadLocal.remove() 方法移除对象。

public class ThreadLocalSolution {

    private static final ThreadLocal<byte[]> threadLocal = ThreadLocal.withInitial(() -> new byte[1024 * 1024]);

    public static void main(String[] args) {
        try {
            threadLocal.get(); // 分配1MB内存
        } finally {
            threadLocal.remove(); // 在使用后移除,避免内存泄漏
        }
    }
}
6. 自定义类加载器引起的内存泄漏

自定义类加载器如果未能正确卸载类,会导致内存泄漏。

示例代码:

public class CustomClassLoaderLeak {

    public static void main(String[] args) throws Exception {
        while (true) {
            CustomClassLoader loader = new CustomClassLoader();
            Class<?> clazz = loader.loadClass("LeakClass");
            Object instance = clazz.getDeclaredConstructor().newInstance();
            // 每次循环都会创建新的类加载器,但旧的类加载器未被释放
        }
    }

    static class CustomClassLoader extends ClassLoader {
        // 自定义类加载器实现
    }
}

解决方案:

确保自定义类加载器不再使用时,可以被垃圾回收器回收。

public class CustomClassLoaderSolution {

    public static void main(String[] args) throws Exception {
        while (true) {
            CustomClassLoader loader = new CustomClassLoader();
            Class<?> clazz = loader.loadClass("LeakClass");
            Object instance = clazz.getDeclaredConstructor().newInstance();
            // 使loader对象可以被回收
            loader = null;
            System.gc(); // 提示GC进行垃圾回收
        }
    }

    static class CustomClassLoader extends ClassLoader {
        // 自定义类加载器实现
    }
}
结论

Java中的内存泄漏虽然不如C/C++那样常见,但仍然是需要关注的问题。通过识别常见的内存泄漏场景并采取适当的解决方案,可以有效地减少和避免内存泄漏的发生。希望本文提供的示例和解决方案能够帮助你在实际开发中更好地处理内存泄漏问题。

标签:泄漏,解决方案,void,static,内存,new,public
From: https://blog.csdn.net/weixin_53840353/article/details/140866668

相关文章

  • maven 常见问题及解决方案
    1.resolutionwillnotbereattempteduntiltheupdateintervalofnexus强制更新mvncleaninstall-U2.Couldnotfindartifact如果可以通过其他途径获取到相关的jar包,可以把jar包安装到本地仓库:示例:demo.jar包上传后,项目中设置的依赖为<dependency><gr......
  • JVM内存结构的划分
    JVM内存结构的划分目录JVM内存结构的划分JVM内存区域1.栈(Stack)2.堆(Heap)3.方法区(MethodArea)4.程序计数器(ProgramCounterRegister)5.本地方法栈(NativeMethodStack)堆和栈的主要区别示例Java虚拟机(JVM)的内存模型是Java程序运行的基础之一,理解JVM内存结构对于深入学习Java编......
  • Linux内存管理与监控
    1、物理内存与虛拟内存物理内存就是系统硬件提供的内存大小,是真正的内存,相对于物理内存,在linux下还有一个虛拟内存:的概念,虚拟内存就是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(SwapSpace)linux的内存管......
  • Instrospect 推出全球首个 GDDR7 显存测试系统测试解决方案
    固态技术协会JEDEC于3月6日正式发布JESD239GDDR7显存标准,JESD239GDDR7提供的带宽是GDDR6的两倍,每台设备最高可达192GB/s。JESD239GDDR7是第一个使用脉幅调制(PulseAmplitudeModulation,PAM)接口进行高频操作的JEDEC标准DRAM。其PAM3接口提高了高频操......
  • 第三章 内存管理
    第三章内存管理3.1无存储器抽象最简单的存储器抽象就是根本没有抽象。早期大型计算机(20世纪60年代之前)、小型计算机(20世纪70年代之前)和个人计算机(20世纪80年代之前)都没有存储器抽象。每一个程序都直接访问物理内存。当一个程序执行如下指令:MOVREGISTER1,1000......
  • 【Linux应急响应—下 】一文解明Linux应急响应(hw蓝队兄弟看这里):主机资源异常如何排查?C
    Linux应急响应重要声明linux应急响应各项资源异常CPU排查内存网络带宽网络连接关闭进程Linux系统日志排查登入验证日志登入失败次数登入成功统计攻击者IP个数攻击次数排列,由高到低中间件日志nginxapachetomcat分析维度:上篇文章在此处:【Linux应急响应—上】一文......
  • 【问题解决方案】npm install报错问题:npm ERR! - 多种解决方案,总有一种可以解决
    @[toc]1.问题重述安装package.json里面的包,使用npminstall但是报错2.解决方案方案1.确认根目录正确确认自己的目录是根目录(也就是处于./package.json可以找到的位置)例如--根目录----package.json----其他文件----其他文件方案2.确认文件名正确确认自己的pack......
  • Java堆栈详解:内存管理与优化
    Java堆栈详解:内存管理与优化大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!Java的内存管理系统由堆(Heap)和栈(Stack)两部分组成,这些部分负责管理Java程序运行时的数据。理解Java堆栈的内存管理以及如何优化这些资源对于开发高效的Java应用至关重要。本文将......
  • 检测Linux服务器CPU、内存、负载、IO读写、机房带宽和服务器类型的脚本
    脚本内容:#!/usr/bin/envbash####RED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[0;33m'SKYBLUE='\033[0;36m'PLAIN='\033[0m'about(){ echo"" echo"=============================......
  • STM32的内存映射机制详解
    目录前言一、基本概念二、内存映射的组成三、典型布局四、常用映射前言刚开始学习STM32的同学可能只知道按案例配置RAM、Flash的起始地址和容量,但是这个地址是怎么来的,可能并不清楚,接下来让我来给大家介绍一下STM32的内存映射机制。STM32的内存映射机制是一种将不同......