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

一文讲清JVM中的内存泄漏问题

在Java学习中,内存溢出和内存泄漏似乎总是让人傻傻分不清,今天这篇博文,讲下这二者以及相关的知识点;

  • 内存溢出,俗称OOM,是指当程序请求分配内存时,由于没有足够的内存空间,从而抛出OutOfMemoryError.
    • 内存溢出可能是因为堆、元空间、栈或直接内存不足导致的。可以通过优化内存配置,减少对象分配来解决;
  • 内存泄漏是指程序在使用完内存之后,未能及时释放,导致占用的内存无法再被使用。随着时间的推移,内存泄漏会导致可用内存逐渐减少,最终导致内存溢出;
    • 内存泄漏通常是因为长期存活的对象持有短期存活对象的引用,又没有及时的释放,从而导致短期存活对象无法被回收导致的。
      可以用一个生活化的例子来理解这两个概念,内存溢出就是你想去餐厅吃饭,但是餐厅没位置了;而内存泄漏就是有人不吃饭在餐厅蹭网不走,导致餐厅座位不够用;
      在这里插入图片描述
      接下来,我想进一步讲下内存泄漏的原因,大致有以下几个原因:
  • 静态的集合中添加的对象越来越多,但却没有及时清理,静态变量的生命周期和应用程序相同,如果静态变量持有对象的引用,这些对象将无法被GC回收;
class OOM{
    static List List= new ArrayList();
    public void oomTests(){
        Object obj = new Object();
        list.add(obj);
    }
}
  • 单例模式下对象持有的外部引用无法及时释放;单例对象在整个应用程序的生命周期中存活,如果单例对象持有其他对象的引用,这些对象就无法被回收;
class Singleton{
    private static final Singleton INSTANCE = new Singleton();
    private List<Object> obj  = new ArrayList<>();
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
  • 补充:【温习下单例模式中经典的饿汉式和懒汉式】
    //饿汉式,在类加载的时候就创建实例,线程安全,但可能会浪费资源
//饿汉式,在类加载的时候就创建实例,线程安全,但可能会浪费资源
public class Singeton{
    //在类加载时创建实例
    private static final Singleton instance = new Singleton();
    //私有构造函数,防止外部实例化
    private Singleton(){}
    //提供全局访问点
    public static Singleton getInstance(){
        return instance;
    }
}

//懒汉式,在第一次调用getInstance()时创建实例,线程不安全;
public class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();//线程不安全
        }
        return instance;
    }
}
  • 数据库,IO、Socket等连接资源没有及时关闭;
try{
    Connection conn = null;
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverMannager.getConnection("url","","");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery(".....");
}catch (Exception e){
    //......
}finally{
    //不关闭连接
}

  • ThreadLocal的引用未被清理,线程退出后仍然有对象引用;在线程执行完成后,要调用ThreadLocal的remove方法进行清理。
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set(new Object());//未清理

那么,到这里,如果涉及到内存泄漏问题的实际处理呢?
比如我当时在做一个项目XXX的时候,由于ThreadLocal没有及时清理导致出现了内存泄漏问题。我用可视化的监控工具VisualVM,配合JDK自带的jstack等命令行工具进行排查。
我可以复盘下当初的处理过程,大致有七步;

  1. 第一步就是使用jps -l 查看运行的Java进程ID;
    在这里插入图片描述
  2. 第二步就是使用top -p [pid]查看进程使用CPU和内存占用情况。
    在这里插入图片描述
  3. 第三步就是使用top -Hp [pid]查看进程下的所有线程占用CPU和内存情况。
    在这里插入图片描述
  4. 第四步,抓取线程栈: jstack -F 29452 > 29452.txt ,可以多抓几次做个对比【29452为pid,顺带作为文件名】
    在这里插入图片描述
    看看有没有线程死锁,死循环或长时间等待这些问题
    在这里插入图片描述
  5. 第五步,可以使用jstat -gcutil [pid] 5000 10 每隔5秒输出GC信息,输出10次,查看YGC和Full GC次数;
    在这里插入图片描述
    通常会出现YGC不增加或增加缓慢,而FullGC增加很快。
    或者使用jstat -gccause [pid] 5000输出GC摘要信息。
    在这里插入图片描述
    或者说使用jmap -heap [pid]查看堆的摘要信息,关注老年代内存使用是否达到阈值,若达到阈值就会执行FullGC。
    在这里插入图片描述
  6. 第6步,生成dump文件,然后借助可视化工具分析哪个对象非常多,基本就能定位到问题根源了。执行命令 jmap -dump:format=b,file=heap.hprof 10025 会输出进程10025的堆快照信息,保存到文件heap.hprof中。
    在这里插入图片描述
  7. 第七步就是使用图形化工具分析,如JDK自带的VisualVM,从菜单>文件>装入dump文件。
    在这里插入图片描述
    然后在结果中观察内存占用最多的对象,找到内存泄漏的源头;

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

相关文章:

  • VSCode下EIDE插件开发STM32
  • 想品客老师的第六天:函数
  • Linux之Tcp粘包笔记
  • PPT添加与管理批注的操作指南
  • [Python学习日记-79] socket 开发中的粘包现象(解决模拟 SSH 远程执行命令代码中的粘包问题)
  • DDD该怎么去落地实现(1)关键是“关系”
  • Go语言中的值类型和引用类型特点
  • STM32项目分享:智能宠物喂食系统(升级版)
  • 软件过程模型
  • python动态全局缓存配置
  • 【论文+源码】 SeqDiffuSeq带有序列到序列生成的编码器变压器的文本扩散模型
  • OpenCV相机标定与3D重建(65)对图像点进行去畸变处理函数undistortPoints()的使用
  • 洛谷P1469 找筷子
  • Scala语言的移动应用开发
  • 使用select函数创建多线程TCP服务端
  • Skia使用Dawn后端在Windows窗口中绘图
  • 反向代理模块1
  • 第五天 Labview数据记录(5.1 INI配置文件读写)
  • python+playwright自动化测试(九):expect断言和expect_xxx()元素及事件捕获
  • 隐马尔科夫模型HMM
  • HDLC,pap,chap网络
  • C语言初阶--折半查找算法
  • Titans 架构下MAC变体的探究
  • polars as pl
  • 消息队列:春招面试的重要知识模块
  • Mono里运行C#脚本34—内部函数调用的过程