内存泄漏的8种情况(附代码示例)

2023-11-09

一. 内存泄漏(memroy leak)

        严格来说,只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫内存泄漏

        宽泛的讲,实际情况中很多时候一些不太好的实践会导致对象的生命周期变得很长甚至导致OOM,也叫“内存泄漏”

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

二. 内存溢出(out of memory)

        申请内存时,没用足够的内存可以使用。 

三. 两者的关系

        内存泄漏的增多,最终会导致内存溢出。

四. 内存泄漏的分类 

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

五. 内存泄漏的8种情况

1. 静态集合类

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

public class test {
    static List list = new ArrayList<>();

    public void oomTest() {
        Object object = new Object();
        list.add(object);
    }
}

2. 单例模式

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

class Singleton {
	private static volatile Singleton instance;
	
	private Singleton() {}
	
	//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
	//同时保证了效率, 推荐使用
	
	public static synchronized Singleton getInstance() {
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
			
		}
		return instance;
	}
}

3. 内部类持有外部类

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

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

        在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、 Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

public class test {
    public static void main(String[] args) {
        try {
            Connection conn = null;
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("url","","");
            Statement statement = conn.createStatement();
            ResultSet resultSet = statement.executeQuery();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            //1. 关闭结果集 statement
            //2. 关闭声明的对象 resultSet
            //3. 关闭连接 Connection
        }
    }
}

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

public class test {
   private  String msg;
   public void receiveMsg(){
       readFromNet();//从网络中接受数据保存到msg中
       saveDB();//把msg保存到数据库中
   }
}

        如上述伪码,msg保存到数据库后已经没用了,但是其生命周期与对象生命相同,则msg还不回收,则造成了内存泄漏。

        修正,将msg变量置于方法内部

  public void receiveMsg(){
       String msg;
       readFromNet();//从网络中接受数据保存到msg中
       saveDB();//把msg保存到数据库中
   }

                或者在使用完msg后,将其置空 

public class test {
   private  String msg;
   public void receiveMsg(){
       readFromNet();//从网络中接受数据保存到msg中
       saveDB();//把msg保存到数据库中
       msg = null;
   }
}

6. 改变哈希值

        当一个对象被存储进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>();
        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));//cc的哈希值改变,找不到对象删除false
        hs.add(cc);
        System.out.println("hs.size = " + hs.size());// size = 2 

        System.out.println(hs); // [Point{x=20}, Point{x=20}]
    }

}

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

7. 缓存泄漏

        内存泄漏的一个常见来源是缓存,一旦你把对象引用放入到缓存中,就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个表中的数据到缓存(内存)中,测试环境只有几百条数据,但是生产环境有几百万的数据。|
        对于这个问题,可以使用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);
        }
        System.out.println("wMap中无数据");
    }

    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);
        }
        System.out.println("Map中有数据");
    }

}



8. 监听器和回调

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

六. 示例

        自定义一个栈,模拟入栈出栈。但是出栈时,只设置指针下移,并没有将出栈数据设为空,即使程序不再使用它们,它们也不会被回收,因为栈中仍然存储着它们的引用,俗称过期引用

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) { //入栈
        ensureCapacity();
        elements[size++] = e;
    }
    //存在内存泄漏
    public Object pop() { //出栈
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

   

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

        修正:出栈时,设置指针下移的同时,将出栈数据设为空

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) { //入栈
        ensureCapacity();
        elements[size++] = e;
    }


    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内存泄漏的8种情况(附代码示例) 的相关文章

  • 哪个类调用了我的静态方法?

    假设我有一个带有静态方法的 Java 类 如下所示 class A static void foo Which class invoked me 进一步假设 A 类有任意数量的子类 class B extends A class C ext
  • 不同的 JDK 更新会产生不同的 Java 字节码吗?

    假设场景 我有一个项目 其源合规性级别指定为 1 5 现在 我使用两种不同的 JDK 编译此项目 首先使用 JDK 6 Update 7 然后使用 JDK 6 Update 20 这两个不同的 JDK 是否会生成不同的 Java 字节代码
  • 警告:跳过条目,因为它不是绝对 URI。 NetBeans 中的 GlassFish

    我成功安装了 GlassFish 但是 当我启动服务器时 我收到两条警告消息 警告 跳过条目 因为它不是绝对 URI 那是关于什么的 Launching GlassFish on Felix platform Aug 09 2014 10
  • 我们如何测试我们的 Java UI?

    我们正在寻找记录和回放类型的测试工具来自动化我们的一些 UI 功能测试 我们已经研究了从 Silke 到 QTP 的大多数常见嫌疑 但没有一个起作用 当需要右键单击才能从右键单击菜单中选择某些内容时 或者当您必须在网格的下拉列表中选择一个值
  • 将 JSON Map 传递到 Spring MVC 控制器

    我正在尝试将 Map 的 JSON 表示形式作为 POST 参数发送到我的控制器中 RequestMapping value search do method RequestMethod GET consumes application j
  • 获取TextView的高度

    我有一些文字将被放入TextView 我这样做是使用setText 现在我需要找到文本在文本中占据的行数或高度TextView 我尝试使用getHeight 但它总是返回 0 无论如何 有没有办法获得文本中存在的文本的高度TextView
  • ASM之前看一下maxStack指令吗?

    我正在尝试使用 ASM 库将字节代码转换为不同的格式 这可以使用 MethodVisitor 来完成 就像这个简单的测试代码一样 return new MethodVisitor ASM7 Override public void visi
  • 确定代码是否在 App Engine 运行时 (Java) 上运行

    如何确定某些代码 Serv let 或简单的类 是否正在 Google App Engine 运行时 Java 上运行 以便决定是否使用 App Engine 的特定库 是否有一些可靠的运行时环境 ID 您可以检查com google ap
  • Java 反射:如何检索匿名内部类?

    我在另一个类中有一个匿名内部类 SomeClass Both SomeClass class getClasses and SomeClass class getDeclaredClasses 返回空数组 我在中找不到一些关于此的提示Cla
  • 将 EditText 聚焦在设备上运行的 PopupWindow 中时出现异常

    我正在为 Android 开发一个弹出窗口 它正在工作 我在上面添加了一个 EditText 和一个按钮 当在 ADV 上运行时 它可以正常工作 而在设备上运行时 当我专注于 EditText 时 这会抛出一个奇怪的异常 android v
  • 在java中是否可以使用反射创建没有无参数构造函数的“空白”类实例?

    我有一个没有默认构造函数的类 我需要一种方法来获取此类的 空白 实例 空白 意味着实例化后所有类字段都应具有默认值 如 null 0 等 我问这个问题是因为我需要能够序列化 反序列化大对象树 而且我无法访问该对象类的源 并且类既没有默认构造
  • java Runtime.getRunTime().exec 和通配符?

    我正在尝试使用删除垃圾文件 Process p Runtime getRuntime exec 只要我不使用通配符 它 就可以正常工作 即 Process p Runtime getRuntime exec bin rm f specifi
  • java 1.8下无法启动eclipse

    java 1 8 升级后我无法启动 eclipse 附上错误截图 这是我的 eclipse 配置设置 我该如何解决 startup plugins org eclipse equinox launcher 1 3 0 v20120522 1
  • 我可以关闭并重新打开套接字吗?

    我学习了一个使用套接字的例子 在此示例中 客户端向服务器发送请求以打开套接字 然后服务器 侦听特定端口 打开套接字 一切都很好 套接字从双方 客户端和服务器 打开 但我仍然不清楚这个东西有多灵活 例如 客户端是否可以关闭一个打开的 从两端
  • 使用链接列表插入优先级队列的方法

    首先 我觉得我应该提到这是一项作业 我并不是在寻找直接的代码答案 只是为了指出正确的方向 我们被要求在链表中实现优先级队列 我正在努力编写 insert 函数的第一部分 在代码中我尝试检查是否head包含任何内容 如果没有则设置为head
  • 计算移动的球与移动的线/多边形碰撞的时间(2D)

    我有一个多边形 里面有一个移动的球 如果球撞到边界 它应该反弹回来 My current solution I split the polygon in lines and calculate when the ball hits the
  • 如何告诉 IntelliJ 使用 Java 1.6 JDK 启动 gradle?

    一个简单的问题 即使经过几个小时的尝试和搜索 我也无法弄清楚 我安装了 Java 6 和 7 如何告诉 IntelliJ 使用 JDK 版本 1 6 启动 Gradle 构建 无论我做什么 IntelliJ 都会以以下方式开始我的 grad
  • Java 相当于 Python 的 urllib.urlencode(基于 HashMap 的 UrlEncode)

    From https stackoverflow com questions 2018026 should i use urllib or urllib2 2018103 2018103 Java 中 Python 的 urllib url
  • Python 可以替代 Java 小程序吗?

    除了制作用于物理模拟 如抛射运动 重力等 的教育性 Java 小程序之外 还有其他选择吗 如果你想让它在浏览器中运行 你可以使用PyJamas http pyjs org 这是一个 Python 到 Javascript 的编译器和工具集
  • 使用 Hibernate 防止无限循环数据检索

    我想知道 想象一个场景 例如 POJO public class User private String userName private String name private String surname private List

随机推荐

  • 3椭圆曲线密码学:ECDH和ECDSA

    原文链接 Elliptic Curve Cryptography ECDH and ECDSA 这篇文章是ECC系列的第三篇 在之前的文章中 我们已经知道了椭圆曲线是什么 并且为了对椭圆曲线上的点做一些数学运算我们定义了群公理 然后我们将椭
  • 静态方法中调用非静态方法

    1 常见现象 静态static方法中不能直接调用非静态non static方法 但可以通过将对象引用传入静态方法内 进而再调用该对象非静态 non static 方法 在主函数 static方法 中 我们经常需要创建某个类的实例 再引用其非
  • Mysql高级部分

    1 索引 索引 index 是帮助Mysql高效获取数据的数据结构 索引的目的在于提高查询效率 可以类比字典 可以简单理解为 排好序的快速查找数据结构 在数据之外 数据库系统还维护着满足特定查找算法的数据结构 这些数据结构以某种方式引用 指
  • tslib1.4的交叉编译

    tslib是touch screen lib 即支持触摸屏的库文件 要交叉编译qte4 5 2 就必须先要编译tslib1 4 今天做了这个工作 记录一下 平台 VMware Centos5 4 交叉编译器 arm linux 3 4 1
  • windows下使用cmake编译c++

    好久没有更新博客了 最近在做c 相关的 编译起来确实很痛苦 所以心血来潮 继续更新一下 主要还是一些跨平台的库 比如zlib libpng opencv ffmpeg 编译工具使用mingw作为主要编译环境支持 使用msys进行编译 一 下
  • R语言实战笔记--第八章 OLS回归分析

    R语言实战笔记 第八章 OLS回归分析 标签 空格分隔 R语言 回归分析 首先 是之前的文章 数理统计里面的简单回归分析 这里简单回顾一下 简单回归分析的原理 最小二乘法 即使回归函数与实际值之差的平方和最小 所以它在R中也称为OLS模型
  • 人工智能可以用来预测人口增长吗?

    人工智能可以用来预测人口增长 利用人工智能技术 可以对历史人口数据进行分析 并根据这些数据建立预测模型 从而预测未来的人口增长趋势 此外 人工智能还可以用于预测人口结构变化 人口流动趋势等方面的问题 这些预测结果可以为政府制定人口政策提供参
  • java读写文件大全(字节流读取得方法)

    原文地址 http blog sina com cn s blog 6a4af8630101et2t html 使用Java操作文本文件的方法详解 摘要 最初java是不支持对文本文件的处理的 为了弥补这个缺憾而引入了Reader和Writ
  • Ubuntu下的终端产生多标签和多标签切换快捷键

    ctrl alt t是打开一个terminal ctrl shift t是在terminal中打开多个标签 在多个标签中切换的方法 方法1 alt 1 alt 2 alt 3 方法二 ctrl pageUp ctrl pageDown ct
  • 【vision transformer】DETR原理及代码详解(四)

    本节是 DETR流程及 构建backbone和position embedding 相关部分的代码解析 一 DETR代码流程 STEP 1 Create model and criterion 构建模型和标准 STEP 2 Create t
  • IPv4和IPv6的互操作性

    概述 在IPv4到IPv6发展的过度阶段 必然出现v4和v6主机之间互操作的问题 下面从机制层面讨论互操作的可能性 并假设主机之间网络路由已经打通 IPv4客户端访问IPv6服务端 IPv4客户端访问IPv6服务端指的是服务端是双栈主机 客
  • OpenCV(二)——图像基本处理(二)

    目录 2 图像的几何变换 2 1 图像平移 2 2 图像缩放 2 3 图像旋转 2 4 仿射变换 2 5 透视变换
  • 电脑开机全是英文进不了系统怎么办

    现在工作都离不开电脑 但是有时候电脑罢工了 电脑开机全是英文进不了系统怎么办呢 下面就和大家讲讲电脑开机全是英文进不了系统解决办法吧 装机吧 电脑一键重装系统领域装机大师 重装系统xp win7 win8 win10 win11 怎么能缺少
  • python毕业设计基于django中小学信息技术课程考试系统

    文末获取资源 收藏关注不迷路 文章目录 一 项目介绍 二 主要使用技术 三 研究内容 四 核心代码 五 文章目录 一 项目介绍 通篇文章的撰写基础是实际的应用需要 然后在架构系统之前全面复习大学所修习的相关知识以及网络提供的技术应用教程 以
  • Neo4j 融资 3.25 亿美元,数据库史上最大的一笔投资

    加州圣马提奥 2021年6月17日 Neo4j图形技术的领先者 今天宣布了一轮F系列融资 这是一项3 25亿美元投资的一部分 亚拉齐奥 RF Pa GV 前称谷歌风投 今天的交易代表了对私人数据库公司的最大投资 使Neo4j的估值超过20亿
  • 什么是.NET?什么是.NET Core?.NET和.NET Core区别又是什么呢?

    本文首发于码友网 什么是 NET 什么是 NET Core NET和 NET Core区别又是什么呢 概述 对于 NET平台的初学者来说 有时候比较困惑 什么是 NET 什么是 NET Core NET和 NET Core区别又是什么呢 确
  • 【一句话攻略】彻底理解JS中的回调(Callback)函数

    作为JS的核心 回调函数和异步执行是紧密相关的 也是必须跨过去的一道个门槛 那么究竟什么是回调函数 Callback 其实回调函数并不复杂 明白两个重点即可 1 函数可以作为一个参数在另一个函数中被调用 2 JS是异步编程语言 这就是说JS
  • python-opencv滑动条,cv2滑动条,模糊示例

    cv2的滑动条使用起来其实挺简单的 只需记住两个函数就行 cv2 createTrackbar创建滑动条 cv2 getTrackbarPos获取滑动条当前值 其他要注意的是滑动条的名字以及滑动条创建在哪个窗口上 这个要一一对应 详见下例
  • react源码分析:babel如何解析jsx

    同作为MVVM框架 React相比于Vue来讲 上手更需要JavaScript功底深厚一些 本系列将阅读React相关源码 从jsx gt VDom gt RDOM等一些列的过程 将会在本系列中一一讲解 工欲善其事 必先利其器 经过多年的发
  • 内存泄漏的8种情况(附代码示例)

    一 内存泄漏 memroy leak 严格来说 只有对象不会再被程序用到了 但是GC又不能回收它们的情况 才叫内存泄漏 宽泛的讲 实际情况中很多时候一些不太好的实践会导致对象的生命周期变得很长甚至导致OOM 也叫 内存泄漏 申请了内存用完了