容易发生内存泄漏的八个场景,你都知道吗?

内存泄漏与内存溢出

JVM在运行时会存在大量的对象,一部分对象是长久使用的,一部分对象只会短暂使用

JVM会通过可达性分析算法和一些条件判断对象是否再使用,当对象不再使用时,通过GC将这些对象进行回收,避免资源被用尽

内存泄漏:当不再需要使用的对象,因为不正确使用时,可能导致GC无法回收这些对象

当不正确的使用导致对象生命周期变成也是宽泛意义上的内存泄漏

内存溢出:当大量内存泄漏时,可能没有资源为新对象分配

举例内存泄漏

接下来将从对象生命周期变长、不关闭资源、改变对象哈希值、缓存等多个场景举例内存泄漏

对象生命周期变长引发内存泄漏
静态集合类
public class StaticClass {
    private static final List<Object> list = new ArrayList<>();

    /**
     * 尽管这个局部变量Object生命周期非常短
     * 但是它被生命周期非常长的静态列表引用
     * 所以不会被GC回收 发生内存溢出
     */
    public void addObject(){
        Object o = new Object();
        list.add(o);
    }
}

类卸载的条件非常苛刻,这个静态列表生命周期基本与JVM一样长

静态集合引用局部对象,使得局部对象生命周期变长,发生内存泄漏

饿汉式单例模式
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
        if (INSTANCE!=null){
            throw new RuntimeException("not create instance");
        }
    }

    public static Singleton getInstance(){
        return INSTANCE;
    }
}

饿汉式的单例模式也是被静态变量引用,即时不需要使用这个单例对象,GC也不会回收

非静态内部类

非静态内部类会有一个指针指向外部类

public class InnerClassTest {

    class InnerClass {

    }

    public InnerClass getInnerInstance() {
        return this.new InnerClass();
    }

    public static void main(String[] args) {
        InnerClass innerInstance = null;

        {
            InnerClassTest innerClassTest = new InnerClassTest();
            innerInstance = innerClassTest.getInnerInstance();
            System.out.println("===================外部实例对象内存布局==========================");
            System.out.println(ClassLayout.parseInstance(innerClassTest).toPrintable());

            System.out.println("===================内部实例对象内存布局===========================");
            System.out.println(ClassLayout.parseInstance(innerInstance).toPrintable());
        }

        //省略很多代码.....
    }
}

当调用外部类实例方法通过外部实例对象返回一个内部实例对象时(调用代码中的getInnerInstance方法)

外部实例对象不需要使用了,但内部实例对象被长期使用,会导致这个外部实例对象生命周期变长

因为内部实例对象隐藏了一个指针指向(引用)创建它的外部实例对象

image-20210520194055109.png

实例变量作用域不合理

如果只需要一个变量作为局部变量,在方法结束就不使用它了,但是把他设置为实例变量,此时如果该类的实例对象生命周期很长也会导致该变量无法回收发生内存泄漏(因为实例对象引用了它)

变量作用域设置的不合理会导致内存泄漏

隐式内存泄漏

动态数组ArrayList中remove操作会改变size的同时将删除位置置空,从而不再引用元素,避免内存泄漏

image-20210520214827223.png

不置空要删除的元素对数组的添加删除查询等操作毫无影响(看起来是正常的),只是会带来隐式内存泄漏

不关闭资源引发内存泄漏

各种连接: 数据库连接、网络连接、IO连接在使用后忘记关闭,GC无法回收它们,会发生内存泄漏

所以使用连接时要使用 try-with-resource 自动关闭连接

改变对象哈希值引发内存泄漏

一般认为对象逻辑相等,只要对象关键域相等即可

一个对象加入到散列表是通过计算该对象的哈希值,通过哈希算法得到放入到散列表哪个索引中

如果将对象存入散列表后,修改了该对象的关键域,就会改变对象哈希值,导致后续要在散列表中删除该对象,会找错索引从而找不到该对象导致删除失败(极小概率找得到)

public class HashCodeTest {
    /**
     * 假设该对象实例变量a,d是关键域
     * a,d分别相等的对象逻辑相等
     */
    private int a;
    private double d;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        HashCodeTest that = (HashCodeTest) o;
        return a == that.a &&
                Double.compare(that.d, d) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(a, d);
    }

    public HashCodeTest(int a, double d) {
        this.a = a;
        this.d = d;
    }

    public HashCodeTest() {
    }

    @Override
    public String toString() {
        return "HashCodeTest{" +
                "a=" + a +
                ", d=" + d +
                '}';
    }

    public static void main(String[] args) {
        HashMap<HashCodeTest, Integer> map = new HashMap<>();
        HashCodeTest h1 = new HashCodeTest(1, 1.5);
        map.put(h1, 100);
        map.put(new HashCodeTest(2, 2.5), 200);

        //修改关键域 导致改变哈希值
        h1.a=100;

        System.out.println(map.remove(h1));//null

        Set<Map.Entry<HashCodeTest, Integer>> entrySet = map.entrySet();
        for (Map.Entry<HashCodeTest, Integer> entry : entrySet) {
            System.out.println(entry);
        }
        //HashCodeTest{a=100, d=1.5}=100
        //HashCodeTest{a=2, d=2.5}=200
    }
}

所以说对象当作Key存入散列表时,该对象最好是逻辑不可变对象,不能在外界改变它的关键域,从而无法改变哈希值

image-20210520211835353.png

将关键域设置为final,只能在实例代码块中初始化或构造器中

如果关键域是引用类型,可以用final修饰后,对外不提供改变该引用关键域的方法,从而让外界无法修改引用关键域中的值 (如同String类型,所以String常常用来当作散列表的Key)

缓存引发内存泄漏

当缓存充当散列表的Key时,如果不再使用该缓存,就要手动在散列表中删除,否则会发生内存泄漏

如果使用的是WeakHashMap,它内部的Entry是弱引用,当它的Key不再使用时,下次垃圾回收就会回收掉,不会发生内存泄漏

public class CacheTest {
    private static Map<String, String> weakHashMap = new WeakHashMap<>();
    private static  Map<String, String> map = new HashMap<>();
    public static void main(String[] args) {
        //模拟要缓存的对象
        String s1 = new String("O1");
        String s2 = new String("O2");
        weakHashMap.put(s1,"S1");
        map.put(s2,"S2");

        //模拟不再使用缓存
        s1=null;
        s2=null;

        //垃圾回收WeakHashMap中存的弱引用
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //遍历各个散列表
        System.out.println("============HashMap===========");
        traverseMaps(map);
        System.out.println();
        System.out.println("============WeakHashMap===========");
        traverseMaps(weakHashMap);
    }

    private static void traverseMaps(Map<String, String> map){
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry);
        }
    }
}

结果

image-20210520214132945.png

注意: 监听器和回调 也应该像这样成为弱引用

总结

这篇文章介绍内存泄漏与内存溢出的区别,并从生命周期变长、不关闭资源、改变哈希值、缓存等多方面举例内存泄漏的场景

内存泄漏是指当对象不再使用,但是GC无法回收该对象

内存溢出是指当大量对象内存泄漏,没有资源再给新对象分配

静态集合、饿汉单例、不合理的设置变量作用域都会使对象生命周期变长,从而导致内存泄漏

非静态内部对象有隐式指向外部对象的指针、使用集合不删除元素等都会隐式导致内存泄漏

忘记关闭资源导致内存泄漏(try-with-resource自动关闭解决)

使用散列表时,充当Key 对象的哈希值被改变导致内存泄漏(key 使用逻辑不可变对象,关键域不能被修改)

缓存引发内存泄漏(使用弱引用解决)

最后(一键三连求求拉~)

本篇文章将被收入JVM专栏,觉得不错感兴趣的同学可以收藏专栏哟~

本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/419328.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

调试工具vue,react,redux

React Developer Tools Redux DevTools Vue devtools 使用浏览器官方组件扩展搜索安装

2369. 检查数组是否存在有效划分(动态规划)

2024-3-1 文章目录 [2369. 检查数组是否存在有效划分](https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/)思路&#xff1a;代码&#xff1a; 2369. 检查数组是否存在有效划分 思路&#xff1a; 1.状态定义:f[i]代表考虑将[0,i]是否能被有效划…

PMP考试培训费用多少钱?

PMP考试的相关费用包括报名费用、培训费用和证书续证费用三个部分。 一、PMP考试报名费用&#xff1a; 首次报考费用为3900元&#xff0c;如果未通过考试可以在英文报名有效期内进行补考报名&#xff0c;补考费用为2500元。 付费方式是在项目管理学会官方网站上提交报考资料…

ASO 对App产品性能的影响

可发现性 拥有出色的App还不足以让人们发现它&#xff0c;ASO技术通过提高搜索排名来增强App的可发现性。当用户可以在搜索结果中轻松找到应用程序时&#xff0c;那么下载和成功的潜力就会飙升。 设定期望 实施有效的应用商店优化不仅可以为潜在用户建立正确的期望&#xf…

职场的过早优化

过早优化&#xff0c;指的是还没弄清楚需求未来的变化的走向的时候&#xff0c;忽略了更重要的问题。 放在职业发展上&#xff1a;你在没有积累足够职场资源&#xff08;眼界、能力、人脉等等&#xff09;&#xff0c;也没有对职业发展形成清晰认知的时候&#xff0c;就过早地进…

Hive SQL 开发指南(二)使用(DDL、DML,DQL)

在大数据领域&#xff0c;Hive SQL 是一种常用的查询语言&#xff0c;用于在 Hadoop上进行数据分析和处理。为了确保代码的可读性、维护性和性能&#xff0c;制定一套规范化的 Hive SQL 开发规范至关重要。本文将介绍 Hive SQL 的基础知识&#xff0c;并提供一些规范化的开发指…

2023年世界前沿科技发展态势及2024年趋势展望——综述篇

源自&#xff1a;全球技术地图 “人工智能技术与咨询” 发布 一、2023年前沿科技发展与竞争态势总结 &#xff08;一&#xff09;人工智能引领前沿技术加速进步&#xff0c;产业应用一触即发&#xff0c;技术奇点苗头出现 &#xff08;二&#xff09;科技之变引发战争形态之…

express+mysql+vue,从零搭建一个商城管理系统7--文件上传,大文件分片上传

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、安装multer&#xff0c;fs-extra二、新建config/upload.js三、新建routes/upload.js四、修改routes下的index.js五、修改index.js六、新建上传文件test.html七、开启jwt验证token&#xff0c;通过login接…

嵌入式通信协议对比介绍(SPI/I2C/UART/RS485/CAN)

1 前言 首先来看看一些基本的概念&#xff1a; 串行通信&#xff1a; 这指的是在通信线路上一次只能传输一个位&#xff08;或字节&#xff09;的数据。每个位依次传输&#xff0c;因此通信是按顺序进行的。这可以用于较简单的系统或长距离通信&#xff0c;但可能导致较低的传…

抖音视频评论关键词批量下载软件|视频内容提取工具

我们团队自主研发的视频评论关键词批量下载软件&#xff0c;为您带来全新的视频获取方式&#xff0c;让您轻松实现对特定关键词的视频搜索和批量下载。以下是软件操作说明的框架&#xff1a; 软文框架&#xff1a; 操作简要说明&#xff1a;说明使用工具的基本流程&#xff0c…

记录java.util.Map.getOrDefault()方法导致JSP编译失败异常:Unable to compile class for JSP

记录java.util.Map.getOrDefault()方法导致JSP编译失败异常:Unable to compile class for JSP; 一、报错信息&#xff1a; type Exception report message Unable to compile class for JSP: description The server encountered an internal error that prevented it from fu…

C 嵌入式系统设计模式 15:基本并发概念

本书的原著为&#xff1a;《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》&#xff0c;讲解的是嵌入式系统设计模式&#xff0c;是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述嵌入式并发和资源管理模式之一…

芯科科技为全球首批原生支持Matter-over-Thread的智能锁提供强大助力,推动Matter加速成为主流技术

智能锁领域的先锋企业U-tec和Nuki选择芯科科技解决方案&#xff0c;成为Matter-over-Thread应用的领先者 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;今…

软考中级 软件设计师备考经验

考试介绍 软考中级的软件设计师需要考两个部分&#xff0c;选择题和大题&#xff0c;每科满分75&#xff0c;需要在同一次考试中两科同时大于等于45分才算通过。考试的内容包括计算机组成原理、数据结构、数据库、专业英语、信息安全、计算机网络等&#xff0c;范围比较广但考…

T2I:zero shot笔记

1 Title Zero-Shot Text-to-Image Generation&#xff08;Aditya Ramesh 、 Mikhail Pavlov 、Gabriel Goh Scott Gray、 Chelsea Voss 、 Alec Radford 、 Mark Chen 、Ilya Sutskever&#xff09; 2 Conclusion This study describes a simple approach for this ta…

在vue前端开发中基于refreshToken和axios拦截器实现token的无感刷新

文章目录 一、需求背景二、token刷新的方案1、根据过期时间重新获取2、定时刷新token接口3、使用了RefreshToken 三、关于RefreshToken四、Refresh Token的优点五、Refresh Token的工作原理六、Refresh Token的使用流程七、Refresh Token的实现步骤1、登录成功后保存AccessToke…

持安科技孙维伯:零信任在攻防演练下的最佳实践|DISCConf 2023

近日&#xff0c;在2023数字身份安全技术大会上&#xff0c;持安科技联合创始人孙维伯应主办方的特别邀请&#xff0c;发表了主题为“零信任在攻防演练下的最佳实践”的演讲。 孙维伯在2023数字身份安全技术大会上发表演讲 以下为本次演讲实录&#xff1a; 我是持安科技的联合…

WPF 【十月的寒流】学习笔记(2):MVVM中是怎么实现通知的

文章目录 前言相关链接代码仓库项目配置代码初始代码ViewPersonViewModel 尝试老办法通知解决方案ObservableCollectionBindingListICollectionView 总结 前言 我们这次详细了解一下列表通知的底层是怎么实现的 相关链接 十月的寒流 MVVM实战技巧之&#xff1a;可被观测的集合…

深入理解Java中的优先级队列(堆)——PriorityQueue

引言&#xff1a; 在Java中&#xff0c;优先级队列&#xff08;PriorityQueue&#xff09;是一种基于堆结构实现的队列&#xff0c;其中每个元素都有一个优先级&#xff0c;优先级高的元素在队列中具有更高的优先级&#xff0c;排在前面。优先级队列常用于任务调度、事件处理等…

js 面试运行机制和存储(从以下几方面理解),栈和堆的理解

1 工作原理 每个浏览器都有自己的引擎&#xff0c;通过引擎把代码解析运行起来。 2 生命周期 3-1 内存分配 3-2 内存使用 3-3 内存回收 3 栈和堆的理解 timer也是个函数--所以也是引用类型。 4 如何运行 以下可忽略 首先声明变量&#xff0c;放在左侧栈中执行&#xff0c;在执行…