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

目录

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

    }

相关推荐
东阳马生架构6 小时前
JVM简介—3.JVM的执行子系统
jvm
程序员志哥12 小时前
JVM系列(十三) -常用调优工具介绍
jvm
后台技术汇12 小时前
JavaAgent技术应用和原理:JVM持久化监控
jvm
程序员志哥12 小时前
JVM系列(十二) -常用调优命令汇总
jvm
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭12 小时前
聊聊volatile的实现原理?
java·jvm·redis
_LiuYan_16 小时前
JVM执行引擎JIT深度剖析
java·jvm
工业甲酰苯胺16 小时前
JVM简介—1.Java内存区域
java·jvm·python
yuanbenshidiaos1 天前
c++---------数据类型
java·jvm·c++
java1234_小锋1 天前
JVM对象分配内存如何保证线程安全?
jvm