【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 staticList = new ArrayList<>();

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

    }

    3. 单例模式
    • 1.和静态集合导致内存泄漏的原因类似,由于单例的静态特性,其生命周期和JVM的生命周期一样长。

    • 2.如果单例对象持有外部对象的引用,那么这个外部对象也不会被回收,从而造成内存泄漏。

    • 3.举个例子

      public class Singleton {
      private List 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 listeners = new ArrayList<>();

        复制代码
          public void addListener(EventListener listener) {
              listeners.add(listener);
          }
        
          // 未提供 removeListener 方法

        }

      相关推荐
      南极企鹅15 小时前
      JVM-编译执行过程
      jvm
      苏克贝塔19 小时前
      .NET开发之.net framework对比.net core
      jvm
      cfm_291420 小时前
      JVM垃圾收集算法与收集器深度解析
      jvm·测试工具·算法·性能优化
      自律懒人1 天前
      AI Agent 工作流编排实战:从单 Agent 到多 Agent,手搭一套能跑通的协作系统
      jvm
      石一峰6991 天前
      SQLite 与 db_manager 集成关键概念详解
      jvm·数据库·sqlite
      布朗克1682 天前
      34 JVM深入理解
      java·jvm
      eggrall2 天前
      Linux线程:并发编程的双刃剑
      jvm
      程序员晨曦2 天前
      深入浅出JVM内存结构
      jvm·面试·职场和发展
      cfm_29142 天前
      JVM对象创建与内存分配机制深度解析
      jvm
      wuminyu2 天前
      Java锁膨胀机制之偏向锁到轻量级锁源码剖析
      java·linux·c语言·jvm·c++