当前位置: 首页 > article >正文

【jvm】内存泄漏的8种情况

目录

          • 1. 说明
          • 2. 静态集合类持有对象引用
          • 3. 单例模式
          • 4. 内部类持有外部类
          • 5. 未关闭的连接
          • 6. 变量不合理的作用域
          • 7. 改变对象的哈希值
          • 8. 缓存Cache泄漏
          • 9. 监听器和回调

1. 说明
  • 1.内存泄漏(Memory Leak)指的是程序中动态分配的内存由于某种原因没有被释放或无法被回收,最终导致系统内存耗尽。
  • 2.尽管Java有垃圾回收机制(Garbage Collection,GC),但某些编程不善仍然可能导致内存泄漏。
2. 静态集合类持有对象引用
  • 1.静态变量在程序生命周期内一直存在,如果静态集合类(如 HashMap、ArrayList 等)持有大量对象引用且未清理,这些对象将不会被垃圾回收。
  • 2.如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
  • 3.长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但因为长生命周期对象持有它的引用而导致不能被回收。
  • 4.举个例子
public class MemoryLeakDemo {
    private static final List<Object> staticList = new ArrayList<>();

    public void addToList(Object obj) {
        // 静态集合类持有对象引用,obj是短生命周期的对象
        staticList.add(obj); 
    }
}

3. 单例模式
  • 1.和静态集合导致内存泄漏的原因类似,由于单例的静态特性,其生命周期和JVM的生命周期一样长。
  • 2.如果单例对象持有外部对象的引用,那么这个外部对象也不会被回收,从而造成内存泄漏。
  • 3.举个例子
public class Singleton {
    private List<Object> shortLivedObjects = new ArrayList<>();
	
	// 单例对象
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

    public void addShortLivedObject(Object obj) {
        // 单例对象的shortLivedObjects持有短生命周期obj的引用
        shortLivedObjects.add(obj);
    }
}

4. 内部类持有外部类
  • 1.如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,外部类对象也不会被垃圾回收,这也会造成内存泄漏。
  • 2.代码示例
public class OuterClass {
    private String outerField;

    public OuterClass(String outerField) {
        this.outerField = outerField;
    }

    public class InnerClass {
        public void printOuterField() {
            // 内部类持有外部类的隐式引用,可以访问外部类的成员
            System.out.println("Outer field: " + outerField);
        }
    }

    // 示例方法,创建内部类的实例并调用其方法
    public void createInnerClassInstance() {
        InnerClass innerClass = new InnerClass();
        innerClass.printOuterField();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass("Hello, World!");
        outer.createInnerClassInstance();

        // 模拟内存泄漏的情况
        List<Object> leakList = new ArrayList<>();
        while (true) {
            leakList.add(new OuterClass("Leak").new InnerClass());
        }
    }
}

5. 未关闭的连接
  • 1.各种连接如数据库连接、网络连接和IO连接等,在不再使用时需要调用close方法来释放连接。
  • 2.只有连接被关闭后,垃圾回收器才会回收对应对象。
  • 3.如果在访问数据库或网络的过程中,对Connection、Statement、ResultSet或Socket等不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
  • 4.举个例子
public class ResourceLeak {
    public void readFile(String filePath) throws IOException {
        FileReader fileReader = new FileReader(filePath);
        // 未关闭 FileReader
    }

    public void getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection("jdbc:database_url");
        // 未关闭 Connection
    }
}

6. 变量不合理的作用域
  • 1.一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。
  • 2.如果没有及时地把对象设置为null,也很有可能导致内存泄漏的发生。
  • 3.变量的作用域不合理是指变量的生命周期超出了其实际需要的范围,从而导致内存资源无法及时释放,进而可能引发内存泄漏等问题。
  • 4.变量作用域不合理通常是指变量被定义在一个更大的(通常是全局的)作用域中,而实际上它们只在较小的作用域中被使用,会导致变量在不再需要时无法被垃圾回收。
  • 5.举个例子
public class FileProcessor {
    // 不合理的作用域:fileContent 应该是方法级别变量,而不是类级别变量
    private String fileContent;

    public void readFile(String filePath) {
        // 读取文件内容并存储在类级别变量中
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            StringBuilder content = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append(System.lineSeparator());
            }
            fileContent = content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void processFile(String filePath) {
        // 读取文件内容
        readFile(filePath);

        // 处理文件内容
        if (fileContent != null) {
            System.out.println("Processing file content...");
            // 处理文件内容逻辑
        }
    }
}

7. 改变对象的哈希值
  • 1.在使用HashSet等基于哈希值的数据结构时,如果对象的哈希值在添加到集合后被改变(例如,修改了对象的某个字段,该字段参与哈希值的计算),那么将导致无法从集合中正确删除该对象,从而造成内存泄漏。
  • 2.当对象作为键存储在哈希集合(如 HashMap 或 HashSet)中,如果其哈希值在存储后发生变化,该对象可能会变得无法访问,从而导致集合中的一些数据无法被正常回收,间接造成内存泄漏。
  • 3.代码示例
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Person {
    private String name;
    private int id;

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public static void main(String[] args) {
        // 创建一个新的 Person 对象
        Person person = new Person("Alice", 123);

        // 创建一个 HashMap,将 Person 对象作为键
        Map<Person, String> map = new HashMap<>();
        map.put(person, "Person 1");

        // 打印初始哈希值和映射
        System.out.println("Initial hashCode: " + person.hashCode());
        System.out.println("Initial map: " + map);

        // 修改 Person 对象的 name 属性,从而改变其哈希值
        person.setName("Bob");

        // 打印修改后的哈希值和映射
        System.out.println("Modified hashCode: " + person.hashCode());
        System.out.println("Modified map: " + map);

        // 尝试使用修改后的 Person 对象获取值
        String value = map.get(person);
        System.out.println("Value from map with modified key: " + value);

        // 尝试移除修改后的 Person 对象
        map.remove(person);
        System.out.println("Map after removing modified key: " + map);
    }
}

8. 缓存Cache泄漏
  • 1.缓存对象未及时清理或没有设置合理的缓存策略,可能会导致内存泄漏。
  • 2.举个例子
public class Cache {
    private static final Map<String, Object> cache = new HashMap<>();

    public void addToCache(String key, Object value) {
        cache.put(key, value);
    }
}
9. 监听器和回调
  • 1.注册的事件监听器或回调未取消注册,导致对象无法被垃圾回收
  • 2.举个例子
public class EventSource {
    private List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    // 未提供 removeListener 方法
}


http://www.kler.cn/a/449116.html

相关文章:

  • 智能座舱进阶-应用框架层-Jetpack主要组件
  • 网络管理 详细讲解
  • SSD目标检测算法
  • MyBatis通过注解配置执行SQL语句原理源码分析
  • 网关的国际化改造
  • 基于蜂鸟视图的智慧可视化巡检管理系统研究
  • 前端面经每日一题Day19
  • 电子应用设计方案68:智能晾衣架系统设计
  • 每日一题 341. 扁平化嵌套列表迭代器
  • Linux嵌入式系统利用套接字编程(Socket Programming)实现网络通信的基础知识并附对一个简单实例的分析
  • 【Spring】控制反转(IoC)与依赖注入(DI)—IoC的概念与优点
  • 【YashanDB知识库】YMP迁移过程中报错YAS-02143或YAS-02193
  • 如何在K8S集群中查看和操作Pod内的文件?
  • 基于Spring Boot的远程教育网站
  • IPC协议获取签名信息
  • [计算机图形学] 【Unity Shader】【图形渲染】Shader数学基础6-逆矩阵与正交矩阵
  • leetcode hot100 合并区间
  • net_device结构
  • golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
  • 【蓝桥杯每日一题】 蜗牛——动态规划
  • Redisson分布式锁的源码解读
  • panddleocr-文本检测+文本方向分类+文本识别整体流程
  • JavaAgent技术应用和原理:JVM持久化监控
  • ubuntu18.04连接不上网络问题
  • Spring Boot与Django对比:哪个更适合做为Web服务器框架?
  • 32岁前端干了8年,是继续做前端开发,还是转其它工作