JVM学习-内存泄漏

内存泄漏的理解和分类

在这里插入图片描述

  • 可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一上对象是否还被引用,对于这种情况下,由于代码的实现不同就会出现很多内存泄漏问题(让JVM误以为此对象还在引用,无法回收,造成内存泄漏)
内存泄漏(memory leak)
  • 严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,叫内存泄漏
    在这里插入图片描述
    • 对象X引用对象Y,X的生命周期比Y的生命周期长
    • 那么当Y生命周期结束的时候,X依然引用着Y,这时候,垃圾回收期是不会回收对象Y的
    • 如果对象X还引用着生命周期比较短的A,B,C,对象A对引用着对象a,b,c,这样可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出
  • 申请了内存用完了不释放,如一共有1024M内存,分配了512M的内存一直不回收,那么可用内存只有512M,仿佛泄露了一部分,通俗讲内存泄漏就是【占着茅坑不拉shi】
内存溢出(out of memory)
  • 申请内存时,没有足够的内存可以使用
  • 通俗一点讲,一个厕所三个坑,有两个站着茅坑不走(内存泄漏),剩下一个坑,厕所一示接待压力大,这时一下来了两个人,坑位(内存)不够了,内存泄漏变成内存溢出了
  • 内存泄漏和内存溢出的关系,内存泄漏增多,最终导致内存溢出
泄漏分类
  • 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存
  • 偶然发生:在某些特定情况下才会发生
  • 一次性:发生内存泄漏的方法只会执行一次
  • 隐式泄漏:一直占着内存不释放,直到执行结束,严格的说这个不算内存泄漏,因为最终释放掉了,但如果执行时间特别长,会导致内存耗尽
Java内存泄漏的8种情况
静态集合类
  • 静态集合类,如HashMap、LinkedList等,如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中对象在程序结束这前将不能被释放,从而造成内存泄漏,简单而言,长生命周期的对象持有短生命周期对象的引用,尽管生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
public class MemoryLeak {
    static List list = new ArrayList();
    
    public void oomTests() {
        Object obj = new Object();   //局部变量,方法执行完成后不能被回收,因为被list所持有(list是静态变量,生命周期同类)
        list.add(obj);
    }
}
单例模式
  • 单例模式和静态集合导致内存泄漏的原因类似,因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
内部类持有外部类
  • 内部类持有外部类,如果一个外部类的实例对象的方法返回一个内部类的实例对象
  • 这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏
各种连接,如数据库连接,网络连接和IO连接等
  • 各种连接,如数据库连接,网络连接和IO连接等
  • 在对数据库进行操作的过程中,首先要建立与数据库连接,当不再使用时,需要调用close方法来释放与数据库连接,只有连接被关闭后,垃圾回收器才会回收对应的对象
  • 否则,在访问数据库的过程中,对Connection,Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏
public static void main(String[] args) {
  try {
    Connection conn = null;
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverManager.getConnection("url","username","password");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("....")
  } catch(Exception e) {  //异常日志
    
  }  finally {
    //①关闭结果集 Statement
    //②关闭声明的对象 ResultSet
    //③关闭连接 Connection
  }
}
变量不合适作用域
  • 变量不合理的作用域,一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏,另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的定义
public class UsingRandom {
  private String msg;
  public void receiveMsg() {
    msg = readFromNet();  //从网络中接收数据保存到msg中
    saveDB();       //将msg保存到数据库中
  }
}
  • 上面伪代码,通过readFromNet方法把接收到的消息保存到变量msg中,然后调用saveDB方法把msg内存保存到数据库,此时msg已经没用了,由于msg的生命周期与对象的生命周期相同,此时msg不能回收,因此造成内存泄漏
  • 实际msg变量可以放在receiveMsg方法内部 ,当方法用完,那么msg的生命周期就结束了,此时就可以回收了,另一种方法,使用完msg后,将msg=null,此时垃圾回收器也会回收msg内存空间
改变哈希值
  • 当一个对象被存储进HashSet集合中后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了,在这种情况下,即使用在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏
  • 这也是String设置成不可变类型的原因,可以放心把String存入HashSet,或把String当成HashMap的key值
  • 当我们需要把自定义的类保存到散列表时,需要保证对象的hashCode不可变
public class ChangeHashCode1 {
    public static void main(String[] args) {
        HashSet<Point> hs = new HashSet<>();
        Point cc = new Point();
        cc.setX(10);      //hashCode = 41
        hs.add(cc);
        cc.setX(20);      //hashCode = 51
        System.out.println("hs.remove = " + hs.remove(cc));   // false
        hs.add(cc);
        System.out.println("hs.size = " + hs.size());         //2
    }
}
class Point {
    int x;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        Point other = (Point) obj;
        if (x != other.x) return false;
        return true;
    }
}
//执行结果
hs.remove = false
hs.size = 2
缓存泄漏
  • 一旦对象引用放入缓存中,很容易遗忘,比如,之前项目在一次上线的时候,应用启动奇慢直至夯死,因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。
  • 对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。
public class MapTest {
    static Map wMap = new WeakHashMap();
    static Map map = new HashMap();
    public static void main(String[] args) {
        init();
        testWeakHashMap();
        testHashMap();
    }
    public static void init() {
        String ref1 = new String("object1");
        String ref2 = new String("object2");
        String ref3 = new String("object3");
        String ref4 = new String("object4");
        wMap.put(ref1,"cacheObject1");
        wMap.put(ref2,"cacheObject2");
        map.put(ref3,"cacheObject3");
        map.put(ref4,"cacheObject4");
        System.out.println("String引用ref1,ref2,ref3,ref4消失");
    }
    public static void testWeakHashMap(){
        System.out.println("WeakHashMap GC前");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("WeakHashMap GC后");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
    }
    public static void testHashMap() {
        System.out.println("HashMap GC前");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HashMap GC后");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }
}
//执行结果
String引用ref1,ref2,ref3,ref4消失
WeakHashMap GC前
object2=cacheObject2
object1=cacheObject1
WeakHashMap GCHashMap GC前
object4=cacheObject4
object3=cacheObject3
HashMap GC后
object4=cacheObject4
object3=cacheObject3
监听器和回调
  • 如果客户端在你实现API注册回调,却没有显示的取消,就会产生积聚,需要确保回调被当作垃圾回收的最佳方法是只保存它的弱引用,保存成WeakHashMap的键
<think>好的,用户现在问的是关于JVM参数-XX:+HeapDumpOnOutOfMemoryError的详细介绍。首先,我需要回想一下这个参数的作用。记得这个参数是在JVM发生内存溢出(OOM)时自动生成堆转储文件,也就是Heap Dump。这样可以帮助开发人员分析内存泄漏或者内存使用过高的原因。 接下来,我需要考虑用户可能的背景。用户可能遇到了OOM的问题,或者正在学习如何调优JVM,想要了解如何配置以方便故障排查。他们可能是开发人员或者运维人员,需要处理生产环境中的内存问题。因此,回答需要既详细又实用,包括如何设置参数、生成的文件位置分析工具等。 然后,要确定用户可能没有明确提到的需求。比如,用户可能不仅想知道这个参数的作用,还想知道如何使用生成的Heap Dump文件,以及相关的其他参数,比如指定转储文件路径的-XX:HeapDumpPath。此外,可能还需要对比其他相关参数或方法,比如手动生成Heap Dump的方式,或者结合其他JVM参数进行内存分析。 我需要确保回答结构清晰,分点列出核心作用、使用方法、文件分析工具、注意事项等。同时,要给出实际的示例命令,方便用户直接复制使用。还要注意提醒用户生成Heap Dump可能带来的性能影响,尤其是在生产环境中需要权衡利弊。 另外,用户可能需要知道如何分析生成的Heap Dump文件,所以需要推荐一些常用工具,比如Eclipse MAT、VisualVM等,并简要说明使用方法。还要提到即使设置了参数,在某些情况下可能无法生成Heap Dump的情况,比如磁盘空间不足或权限问题,这样帮助用户避免常见陷阱。 最后,总结这个参数的重要性和适用场景,强调它在故障排查中的价值,同时提醒配合其他JVM参数使用,比如-XX:HeapDumpPath和-XX:OnOutOfMemoryError来执行自定义脚本,增强问题处理能力。</think>### JVM参数 `-XX:+HeapDumpOnOutOfMemoryError` 详解 --- #### **核心作用** 当JVM触发 **`OutOfMemoryError`(内存溢出)** 时,自动生成 **堆转储文件(Heap Dump)**,记录内存崩溃瞬间的堆内存快照。该文件是分析内存泄漏或内存消耗异常的 **关键证据**。 --- #### **参数用法** 1. **基本启用** 在JVM启动参数中添加: ```bash -XX:+HeapDumpOnOutOfMemoryError ``` 2. **指定转储路径**(配合 `-XX:HeapDumpPath`) ```bash -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/heapdump.hprof ``` - 路径可以是目录(自动生成文件名)或完整文件路径 - 示例文件名:`java_pid12345_20240320.hprof`(含进程ID和时间戳) --- #### **典型应用场景** 1. **生产环境故障排查** 内存溢出时自动保留“案发现场”,避免重启后丢失关键信息。 2. **性能调优** 通过分析堆转储文件定位内存消耗过高的对象(如缓存未释放、大数组等)。 3. **偶发性问题捕捉** 捕获难以复现的间歇性内存泄漏问题。 --- #### **堆转储文件分析工具** | 工具名称 | 特点 | |------------------|----------------------------------------------------------------------| | **Eclipse MAT** | 内存分析神器,可视化对象依赖树,支持泄漏检测报告(需安装JDK) | | **VisualVM** | JDK自带工具,支持堆转储基础分析(命令行输入 `jvisualvm` 启动) | | **jhat** | 命令行工具,生成HTML报告(示例:`jhat heapdump.hprof`) | | **YourKit** | 商业工具,深度分析内存分配和线程状态 | --- #### **实战配置示例** ```bash java -Xmx512m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/myapp/oom_dumps \ -jar my-application.jar ``` - `-Xmx512m`:设置堆最大内存为512MB(更容易触发OOM测试) - 生成的转储文件会保存在 `/var/log/myapp/oom_dumps` 目录 --- #### **注意事项** 1. **磁盘空间** 堆转储文件大小通常与堆内存最大值相当(例如4GB的堆可能生成3GB的.hprof文件)。 2. **性能影响** 生成堆转储时JVM会暂停(STW),生产环境需评估对服务的影响。 3. **权限问题** 确保JVM进程对输出目录有写入权限,否则会静默失败。 4. **OOM类型** 仅对 `java.lang.OutOfMemoryError` 有效,其他错误(如栈溢出)不会触发转储。 --- #### **高级技巧** 1. **组合使用 `-XX:OnOutOfMemoryError`** 内存溢出时执行自定义脚本(如发送告警、重启服务): ```bash -XX:OnOutOfMemoryError="/opt/scripts/restart_service.sh" ``` 2. **压缩转储文件**(第三方工具) 使用 `gzip` 压缩节省空间(需手动处理): ```bash find /dump_dir -name "*.hprof" -exec gzip {} \; ``` 3. **自动化分析** 结合持续集成工具,自动解析堆转储并生成报告(需编写脚本)。 --- #### **手动生成堆转储** 即使未启用该参数,仍可通过以下方式获取堆转储: 1. **jmap命令** ```bash jmap -dump:format=b,file=heap.hprof <pid> ``` 2. **JVM信号触发** ```bash kill -3 <pid> # Linux/Mac ``` --- #### **关键总结** - **核心价值**:内存溢出时自动保留“内存快照”,极大提升问题诊断效率。 - **必配场景**:生产环境JVM强烈建议启用此参数。 - **分析要点**:重点关注重复创建的巨型对象、未释放的集合类(如HashMap、ArrayList)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值