一文打通java中内存泄露

news2024/11/24 11:30:54

目录

前置知识

 内存泄漏(memory leak)

内存溢出(out of memory)

Java中内存泄露的8种情况

静态集合类

 单例模式

内部类持有外部类

各种连接,如数据库连接、网络连接和IO连接等

变量不合理的作用域

改变哈希值

缓存泄露

监听器和其他回调


前置知识

 内存泄漏(memory leak)

可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。

> 是否还被使用?是

> 是否还被需要?否

 严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致00M,也可以叫做宽泛意义上的“内存泄漏”。

如下图,当Y生命周期结束的时候,X依然引用着Y,这时候,垃圾回收期是不会回收对象Y的;如果对象X还引用着生命周期比较短的A、B、C,对象A又引用着对象 a、b、c,这样就可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出。

申请了内存用完了不释放,比如一共有1024M的内存,分配了512M的内存一直不回收,那么可以用的内存只有512M了,仿佛泄露掉了一部分。

内存溢出(out of memory)

申请内存时,没有足够的内存可以使用;通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。

泄漏的分类

  1. 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
  2. 偶然发生:在某些特定情况下才会发生
  3. 一次性:发生内存泄露的方法只会执行一次;
  4. 隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

Java中内存泄露的8种情况

静态集合类

静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。 

public class MemoryLeak {
    static List list = new ArrayList();
    public void oomTests(){
        Object obj=new Object();//局部变量
        list.add(obj);
    }
}

 单例模式

单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和 JVM 的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。

内部类持有外部类

内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。

各种连接,如数据库连接、网络连接和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","","");
        Statement stmt =conn.createStatement();
        ResultSet rs =stmt.executeQuery("....");
    } catch(Exception e){//异常日志
    } finally {
        // 1.关闭结果集 Statement
        // 2.关闭声明的对象 ResultSet
        // 3.关闭连接 Connection
    }
}

变量不合理的作用域

变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。

public class UsingRandom {
    private String msg;
    public void receiveMsg(){
        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 ChangeHashCode {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        Person p1 = new Person(1001, "AA");
        Person p2 = new Person(1002, "BB");

        set.add(p1);
        set.add(p2);

        p1.name = "CC";//导致了内存的泄漏
        set.remove(p1); //删除失败

        System.out.println(set);

        set.add(new Person(1001, "CC"));
        System.out.println(set);

        set.add(new Person(1001, "AA"));
        System.out.println(set);

    }
}

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 * 例2
 */
public class ChangeHashCode1 {
    public static void main(String[] args) {
        HashSet<Point> hs = new HashSet<Point>();
        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());//size = 2

        System.out.println(hs);
    }

}

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;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                '}';
    }
}

缓存泄露

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。

对于这个问题,可以使用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("obejct1");
        String ref2 = new String("obejct2");
        String ref3 = new String("obejct3");
        String ref4 = new String("obejct4");
        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);
        }
    }

}

上面代码和图示主演演示WeakHashMap如何自动释放缓存对象,当init函数执行完成后,局部变量字符串引用weakd1,weakd2,d1,d2都会消失,此时只有静态map中保存中对字符串对象的引用,可以看到,调用gc之后,HashMap的没有被回收,而WeakHashMap里面的缓存被回收了。

监听器和其他回调

内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚。需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB2.0(一):基础

一、总线标准 USB1.1&#xff1a;支持12Mbps全速率&#xff08;FullSpeed&#xff09;和1.5Mbps低速率&#xff08; HalfSpeed&#xff09;USB2.0&#xff1a;支持480Mbps高速率&#xff08;High Speed&#xff09;&#xff0c;兼容1.1USB3.0&#xff1a;支持5Gbps超高速率&am…

无线传感器网络(WSN)中的空穴和边界节点检测(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 ​由于无线传感器网络中可能会出现覆盖漏洞,导致网络无法提供高质量的数据,所以需要检测边界节点以准确找到覆盖漏洞进行修复。…

通用医学图像分割模型UniverSeg

虽然深度学习模型已经成为医学图像分割的主要方法&#xff0c;但它们通常无法推广到涉及新解剖结构、图像模态或标签的unseen分割任务。给定一个新的分割任务&#xff0c;研究人员通常必须训练或微调模型&#xff0c;这很耗时&#xff0c;并对临床研究人员构成了巨大障碍&#…

交互式数据分析和处理新方法:pandas-ai =Pandas + ChatGPT

Python Pandas是一个为Python编程提供数据操作和分析功能的开源工具包。这个库已经成为数据科学家和分析师的必备工具。它提供了一种有效的方法来管理结构化数据(Series和DataFrame)。 在人工智能领域&#xff0c;Pandas经常用于机器学习和深度学习过程的预处理步骤。Pandas通…

windows下Ubuntu保姆级安装教程 [附VMware资源]

windows下Ubuntu保姆级安装教程 [附VMware资源] 前言1. VMware安装2. Ubuntu镜像下载3.创建新的虚拟机开启虚拟机 前言 一直在用Linux开发板&#xff0c;都忘记自己的新电脑没装虚拟机了。 这期我们来学习Windows环境下VMware安装Ubuntu虚拟机&#xff0c;以便后续学习Linux。…

「领域驱动设计」DDD,六边形架构,洋葱架构,整洁架构和CQRS的整合

这篇文章是软件架构编年史的一部分&#xff0c;一系列关于软件架构的文章。在这些文章中&#xff0c;我写了我对软件架构的了解&#xff0c;我如何看待它&#xff0c;以及我如何使用这些知识。如果您阅读了本系列以前的文章&#xff0c;那么本文的内容可能更有意义。 今天的帖子…

Java基础(十六)泛型

1. 泛型概述 1.1 生活中的例子 举例1&#xff1a;中药店&#xff0c;每个抽屉外面贴着标签 举例2&#xff1a;超市购物架上很多瓶子&#xff0c;每个瓶子装的是什么&#xff0c;有标签 举例3&#xff1a;家庭厨房中&#xff1a; Java中的泛型&#xff0c;就类似于上述场景中的…

聚观早报|五一假期机票均价超1200元;苹果自动驾驶测试减员超25%

今日要闻&#xff1a;五一假期国内机票均价超1200元&#xff1b;谷歌、微软、OpenAI等将讨论AI问题&#xff1b;苹果自动驾驶测试司机团队减员超25%&#xff1b;“五一”最热十大景区出炉&#xff1b;李想辟谣理想汽车砸钱雇媒体营销 五一假期国内机票均价超1200元 5 月 3 日…

DGIOT智慧农业案例分享

场景说明 农业-农业大棚智能监测场景 背景 在农业大棚中&#xff0c;引入物联网技术&#xff0c;实现温室种植的高效和精准化的管理。物联网技术可以应用到温室生产的不同阶段&#xff0c;把不同阶段植物的表现和环境因子进行分析&#xff0c;反馈到下一轮的生产中。实现精准…

JVM原理之详解现代垃圾回收器 Shenandoah 和 ZGC

Shenandoah Shenandoah 一词来自于印第安语&#xff0c;十九世纪四十年代有一首著名的航海歌曲在水手中广为流传&#xff0c;讲述一位年轻富商爱上印第安酋长 Shenandoah 的女儿的故事。 后来美国有一条位于 Virginia 州西部的小河以此命名&#xff0c;所以 Shenandoah 的中文译…

【图像分割】【深度学习】SAM官方Pytorch代码-各模块的功能解析

【图像分割】【深度学习】SAM官方Pytorch代码-各功能模块解析 Segment Anything&#xff1a;建立了迄今为止最大的分割数据集&#xff0c;在1100万张图像上有超过1亿个掩码&#xff0c;模型的设计和训练是灵活的&#xff0c;其重要的特点是Zero-shot(零样本迁移性)转移到新的图…

mysql数据库高级操作

文章目录 一、克隆表1.方法一2.方法二 二、清空表&#xff0c;删除表内所有数据1.方法一2.方法二3.drop、truncate、delete对比 三、创建临时表四、用户管理五、忘记root密码的解决措施六、用户授权总结 一、克隆表 1.方法一 ​create table 新表名 like 复制的表名; ​ ​复…

如何应用PreMaint预测性维护助力企业节能减排

随着全球能源资源的日益减少和环境的变化&#xff0c;各行各业都在积极探索节能降耗的方法&#xff0c;以减少能源消耗和环境污染。而在工厂生产过程中&#xff0c;设备的稳定运行是节能降耗的关键。设备健康管理软件在这方面的应用&#xff0c;对于提高工厂的节能降耗效果具有…

NSSCTF之Misc篇刷题记录⑧

NSSCTF之Misc篇刷题记录 [MMACTF 2015]welcome[虎符CTF 2022]Plain Text[SWPUCTF 2021 新生赛]原来你也玩原神[SWPUCTF 2021 新生赛]我flag呢&#xff1f; NSSCTF平台&#xff1a;https://www.nssctf.cn/ PS&#xff1a;记得所有的flag都改为NSSCTF [MMACTF 2015]welcome NSS…

MySQL MVCC 机制

什么是mvcc mvcc(多版本并发控制&#xff09;&#xff0c;作用在于可无锁实现隔离级别中的“可重复读” 提高数据库关于事务处理上的性能问题&#xff0c;其中"多版本" 指的是 UndoLog 链中的多个事务&#xff0c;“控制” 指的是我当前应当读取那个事务id对应的数据…

「STC8A8K64D4开发板」——外部中断(INT0~INT4)

第2-4讲&#xff1a;外部中断(INT0~INT4) 学习目的学习中断的相关概念。掌握外部中断配置及中断优先级配置的程序设计。掌握中断服务程序的编写。 中断相关概念 什么是中断 中断系统是为使 CPU 具有对外界紧急事件的实时处理能力而设置的。 CPU在处理某一事件A时&#xff0c…

面试题30天打卡-day19

1、TCP 和 UDP 协议有什么区别&#xff0c;分别适用于什么场景&#xff1f; TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常用的传输层协议&#xff0c;两者的区别比较如下&#xff1a; TCPUDP可靠性…

数据库初认识之MySQL

关系型数据库 数据结构 二维表格 库 -> 表 -> 列&#xff08;字段&#xff09;&#xff1a;用来描述对象的一个属性 行&#xff08;记录&#xff09;&#xff1a;用来描述一个对象的信息 Mysql&#xff08;5.7/8.0&#xff09; …

2023百度强引百度蜘蛛方法(百度蜘蛛秒引工具)

当谈到SEO时&#xff0c;关键字是最基本的元素之一。使用正确的关键字可以帮助您的网站在搜索引擎结果页面&#xff08;SERP&#xff09;中排名更高。在本篇文章中&#xff0c;我们将重点介绍2023年百度强引百度蜘蛛方法和百度蜘蛛秒引工具&#xff0c;并提供一些有用的SEO技巧…

比特米盒子刷安卓ATV6.0

最近海鲜市场有很多比特米盒子&#xff0c;50多块包邮&#xff0c;买来的盒子回来折腾下&#xff0c;买回来发现一直卡在“系统启动"中无法进入&#xff0c;不知道原来的是啥系统&#xff0c;看来只能找找线刷的办法&#xff0c;重新拯救救个这盒子。 原文链接地址&#x…