内存泄漏详解

文章目录

    • 什么是内存泄漏
    • 内存泄漏的原因
    • 排查及解决内存泄漏
    • 避免内存泄漏
      • 及时释放资源
      • 设置合理的变量作用域
      • 及时清理不需要的对象
      • 避免无限增长
      • 避免内部类持有外部类引用
      • 使用弱引用

什么是内存泄漏

内存泄漏是指不使用的对象持续占有内存使得内存得不到释放,从而造成内存空间的浪费。严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。但实际情况很多时候一些不太好的实践会导致对象的生命周期变得很长,甚至导致00M,也可以叫做宽泛意义上的“内存泄漏”。

举个例子,创建的连接不再使用时,需要调用close方法关闭连接,只有连接被关闭后,GC才会回收对应的对象。忘记关闭这些资源会导致持续占有内存,无法被GC回收。这样就会导致内存泄露,最终导致内存溢出。

public class MemoryLeak {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}}
}

内存泄漏最明显的问题是频繁GC,从而STW次数增加,导致用户体验变差。如果内存泄露问题严重,会导致OOM,直接导致程序不能正常运行。尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现OutOfMemory异常,导致程序崩溃。

内存泄漏的原因

Java使用可达性分析算法来标记垃圾对象。在这个过程中,算法会标记那些仍然可以从根对象(如栈、静态变量等)直接访问到的对象为“可达”,而那些无法从根对象访问到的对象则标记为“不可达”。不可达的对象是候选垃圾,可以被回收。有时候即使某些对象不再使用,它们的引用链可能仍然存在,导致这些对象没有被标记为不可达,从而造成内存泄漏。在这种情况下,虽然这些对象已经不再被实际使用,但由于引用链未断开,它们仍然占用内存。

大多数内存泄露的原因是,长生命周期的对象引用了短生命周期的对象。例如,A对象引用B对象,A对象的生命周期(t1-t4)比B对象的生命周期(t2-t3)长的多。当B对象没有被应用程序使用之后,A对象仍然在引用着B对象。这样,垃圾回收器就没办法将B对象从内存中移除,从而导致内存泄露问题。

所以减少长生命周期对象持有短生命周期对象的强引用是解决内存泄漏的一个关键点。利用弱引用或者软引用可以让垃圾回收器更容易回收不再需要的对象。对于外部资源,如数据库连接、文件、网络连接,用完后应该及时关闭。try-with-resources语句是管理这些资源的有效工具,同时移除不再需要的事件监听器也能防止内存泄漏。管理集合时,设定大小限制并定期清理过期数据可以避免无限增长。使用有界数据结构能帮助控制缓存的大小。静态集合要特别留意,避免它们占用过多内存,通过定期清理来管理数据的存储。通过这些措施,可以减少内存泄漏的风险。

排查及解决内存泄漏

根据运维之前收集到的内存数据、GC日志尝试判断哪里出现了问题。结果发现老年代的内存使用就算是发生GC也一直居高不下,而且随着时间推移也越来越高。

在这里插入图片描述

使用jstat -gc <vmid> 查看GC垃圾回收统计信息,看Full GC后堆空间使用内存还持续增长,且有增长到Xmx设定值的趋势,基本可以肯定存在内存泄露。如果当前完全垃圾回收后内存增长到一个值之后,又能回落,总体上处于一个动态平衡,那么内存泄漏基本可以排除;也可以隔断时间抽取老年代占用内存情况,如果老年代占用情况持续上升也很有可能存在内存泄露的情况。

内存泄漏的主要表象就是内存不足,所以首先要看一下JVM启动参数中内存空间分配是否过小,如果是这种问题调整该参数即可。如果不是参数调的太小,那么应该确定是否新部署或有新变更。首先需要确认是否在最近进行了新的部署或有其他相关的变更,例如代码更新、配置修改等。这些变更可能导致应用出现性能问题,特别是在高负载情况下。

遇到内存泄漏问题,最经典的就是用MAT工具分析dump文件然后找到具体的代码。但如果dump文件巨大就不建议这样,可以使用其他方案,例如,重启、本地复现、jmap -histo:live <pid>在线进行分析等其他方案解决。使用MAT定位内存泄漏思路:

  1. 打开MAT中histogram,找到堆内存中占用最大的对象,内存泄漏很有可能就是由大对象导致的;

    在这里插入图片描述

  2. 由大对象找被哪些线程引用,查看内存占用最大的线程;

    在这里插入图片描述

    在这里插入图片描述

  3. 从线程中的堆栈信息找到项目中自定义的包和对象,从而可定位到具体的代码;

    在这里插入图片描述

    在这里插入图片描述

避免内存泄漏

内存泄漏是由代码中的问题导致的,这些问题通常源于编程错误、设计不良或对资源管理的忽视。那想要避免内存泄漏,就需要从代码层面入手。

及时释放资源

如数据库连接、网络连接和IO连接等,当不再使用时,需要调用close方法来释放连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则如果在连接过程中,对一些对象不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

在这个例子中,数据库连接conn在方法结束时没有被关闭。这导致了连接资源的泄漏,因为即使在出现异常时也没有释放连接,可能会导致数据库连接池资源被耗尽,影响系统的正常运行。

public class ResourceLeakExample {public void process() {Connection conn = null;try {conn = DriverManager.getConnection(url, user, password);// 使用连接} catch (SQLException e) {e.printStackTrace();}// 连接没有关闭,导致资源泄漏}
}

这里使用了try-with-resources语句来解决这个问题,这种方式可以自动管理资源的释放。连接conn会在try块结束时被自动关闭,即使发生了异常也不会有资源泄漏的问题。

public class ResourceLeakFixedExample {public void process() {try (Connection conn = DriverManager.getConnection(url, user, password)) {// 使用连接} catch (SQLException e) {e.printStackTrace();}// 连接自动关闭}
}

设置合理的变量作用域

一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。静态变量cache持有所有添加对象的引用。由于这个集合是静态的,它会持续存在,导致对象不会被垃圾回收,可能导致内存使用逐渐增大,最终可能耗尽内存。

public class StaticCacheExample {private static List<Object> cache = new ArrayList<>();public static void addToCache(Object obj) {cache.add(obj); // 对象添加到静态列表}
}

可以将其作用域缩小,解决这个问题。在这个例子中,localCache是一个局部变量,存在于方法的作用域内。当方法执行完毕后,localCache变量会被垃圾回收器回收。通过调用clear方法清空缓存,可以进一步减少内存占用。

public class LocalCacheExample {public void process() {List<Object> localCache = new ArrayList<>();localCache.add(new Object());// 使用 localCachelocalCache.clear(); // 清理缓存}
}

及时清理不需要的对象

这个示例中,cache列表不断添加新对象,但没有进行清理。这样的话,随着时间的推移,cache列表的大小会不断增加,占用大量内存,可能导致系统性能问题。

public class MemoryLeakExample {private List<Object> cache = new ArrayList<>();public void process() {cache.add(new Object()); // 添加对象到缓存// 缓存不清理}
}

修改后的代码中process方法会定期检查cache大小。如果cache超过了设定的大小,就会调用clear方法来清空缓存。这可以帮助控制内存使用,避免列表无限增长。

public class MemoryLeakFixedExample {private List<Object> cache = new ArrayList<>();public void process() {cache.add(new Object());if (cache.size() > 100) {cache.clear(); // 定期清理缓存}}
}

避免无限增长

如果一个集合或缓存没有限制其大小且没有清理机制,它可能无限增加,导致不再需要的对象无法被垃圾回收,从而引发内存泄漏。这个示例中,data列表不断增加对象,没有进行任何限制或清理。这会导致data列表无限增长,逐渐消耗大量内存,最终可能导致内存不足。

public class InfiniteGrowthExample {private List<Object> data = new ArrayList<>();public void addData(Object obj) {data.add(obj); // 不断添加对象// 无限制增长}
}

这里使用LinkedList作为数据结构,并设置了最大大小限制。当列表的大小超过限制时,最旧的元素会被移除。这种做法可以控制内存的使用,避免数据结构无限增长。

public class BoundedGrowthExample {private List<Object> data = new LinkedList<>();public void addData(Object obj) {if (data.size() > 100) {data.remove(0); // 保持列表大小限制}data.add(obj);}
}

避免内部类持有外部类引用

匿名内部类Runnable持有对外部类实例的隐式引用。如果外部类的生命周期很长,可能导致外部类无法被垃圾回收,从而引发内存泄漏。

public class AnonymousInnerClassExample {public void process() {Runnable r = new Runnable() {public void run() {// 使用外部类的实例}};new Thread(r).start();}
}

在改进后的代码中,使用了lambda表达式,避免了匿名内部类带来的隐式引用。这样可以减少对外部类实例的持有,降低内存泄漏的风险。

public class AnonymousInnerClassFixedExample {public void process() {Runnable r = () -> {// 使用外部类的实例};new Thread(r).start();}
}

使用弱引用

弱引用是Java中的一种引用类型,用于在对象不再被强引用时允许垃圾回收器回收这些对象,它主要用于实现缓存和其他需要动态释放内存的场景。与强引用不同,弱引用在垃圾回收时不会阻止对象的回收。如果一个对象只有弱引用指向它,那么在垃圾回收时,这个对象会被回收。弱引用的设计目的是帮助避免内存泄漏。

public class WeakReferenceCache {private Map<String, WeakReference<Object>> cache = new HashMap<>();public void addToCache(String key, Object value) {cache.put(key, new WeakReference<>(value));}public Object getFromCache(String key) {WeakReference<Object> ref = cache.get(key);return (ref != null) ? ref.get() : null;}
}

尽管弱引用设计的目的是避免内存泄漏,但在以下情况下仍然可能出现问题:

  • 缓存失控:如果使用弱引用实现缓存,并且缓存管理策略不当,可能会导致频繁的对象创建和回收,影响性能。
    例如,缓存的对象如果不断被创建和清除,可能导致大量的对象创建压力,从而影响性能。
  • 内存使用不均:弱引用对象的回收是非确定性的。垃圾回收器的回收行为可能不一致,这可能导致应用程序的内存使用行为不如预期。

在这个示例中,clearCache方法会清空缓存。在大多数情况下,缓存中的对象会在下一次垃圾回收时被回收,但如果缓存使用不当,频繁的缓存清空操作可能会影响系统性能。

public class CacheWithPotentialIssues {private Map<String, WeakReference<Object>> cache = new HashMap<>();public void addToCache(String key, Object value) {cache.put(key, new WeakReference<>(value));}public Object getFromCache(String key) {WeakReference<Object> ref = cache.get(key);return (ref != null) ? ref.get() : null;}// 可能引起问题的代码public void clearCache() {cache.clear(); // 清空缓存}
}

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

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

相关文章

【Java语法基础】1、变量、运算符、输入输出

1.变量、运算符、输入输出 跟C一样&#xff0c;先把必须写的框架写出来&#xff1a; package org.example; public class Main{public static void main(String[] args){//在里面写实际的代码} }变量 必须先定义&#xff0c;才能使用。与C、C差不多。 没有赋初值的变量无法…

windows网络应急排查

一、系统排查 msinfo32 #GUI显示的系统信息systeminfo #简单了解系统信息用户信息排查 排查恶意账号&#xff1a; 黑客喜欢建立相关账号用作远控: 1.建立新账号2.激活默认账号3.建立隐藏账号(windows中账号名$)cmd方法 net user #打印用户账号信息 ---看不到$结尾的隐藏账…

Linux - 进程的概念、状态、僵尸进程、孤儿进程及进程优先级

目录 进程基本概念 描述进程-PCB task_struct-PCB的一种 task_struct内容分类 查看进程 通过系统目录查看 通过ps命令查看 通过系统调用获取进程的PID和PPID 通过系统调用创建进程- fork初始 fork函数创建子进程 使用if进行分流 Linux进程状态 运行状态-R 浅度睡眠状态-S…

Apache Filnk----入门

文章目录 Flink 概述Flink 是什么有界流和无界流有状态流处理Flink 特点Flink vs SparkStreamingFlink 分层API Flink 快速上手WordCount 代码编写批处理流处理读取socket文本流 Flink 概述 Flink 是什么 有界流和无界流 无界数据流: 有定义流的开始&#xff0c;但没有定义流…

ts一些解决vscode飘红的方法

1、查看是否有些ts的数据类型定义问题&#xff0c;属性缺少或者属性类型不对 把对应属性加上即可 2、在飘红的代码前面设置// ts-ignore忽略此行校验&#xff08;不过一般不建议用这个方法&#xff09; 3、移除高版本不用的属性&#xff08;版本属性兼容问题&#xff09; 原因…

PP-Human行为识别(RTSP协议视频流实时检测)

基于PaddleDetection本地实现PP-Human行为识别模块&#xff08;RTSP协议视频流实时检测&#xff09; 项目介绍环境准备1. Anaconda 创建环境2. 获取 PaddleDetection3. 获取 [MediaMTX](https://github.com/bluenviron/mediamtx/releases/tag/v1.8.4)4. FFmpeg 获取5. VLC 获取…

.NET开源、简单、实用的数据库文档生成工具

前言 今天大姚给大家分享一款.NET开源&#xff08;MIT License&#xff09;、免费、简单、实用的数据库文档&#xff08;字典&#xff09;生成工具&#xff0c;该工具支持CHM、Word、Excel、PDF、Html、XML、Markdown等多文档格式的导出&#xff1a;DBCHM。 支持的数据库 Sq…

IEEE官方列表会议 | 第三届能源与环境工程国际会议(CFEEE 2024)

会议简介 Brief Introduction 2024年第三届能源与环境工程国际会议(CFEEE 2024) 会议时间&#xff1a;2024年12月2日-4日 召开地点&#xff1a;澳大利亚凯恩斯 大会官网&#xff1a;CFEEE 2024-2024 International Conference on Frontiers of Energy and Environment Engineer…

Android APP 音视频(01)MediaCodec解码H264码流

说明&#xff1a; 此MediaCodec解码H264实操主要针对Android12.0系统。通过读取sd卡上的H264码流Me获取视频数据&#xff0c;将数据通过mediacodec解码输出到surfaceview上。 1 H264码流和MediaCodec解码简介 1.1 H264码流简介 H.264&#xff0c;也被称为MPEG-4 AVC&#xff…

uni-app 影视类小程序开发从零到一 | 开源项目分享

引言 在数字娱乐时代&#xff0c;对于电影爱好者而言&#xff0c;随时随地享受精彩影片成为一种日常需求。分享一款基于 uni-app 开发的影视类小程序。它不仅提供了丰富的影视资源推荐&#xff0c;还融入了个性化知乎日报等内容&#xff0c;是不错的素材&#xff0c;同时对电影…

就业管理功能概述:构建智慧校园企业招聘平台

在智慧校园整体解决方案中&#xff0c;就业管理模块连接着学校与企业两端&#xff0c;更成为学生们步入社会、开启职业生涯梦想的关键门户。这一功能的核心价值&#xff0c;在于它如何巧妙地运用科技的力量&#xff0c;简化招聘流程&#xff0c;提升招聘效率&#xff0c;同时为…

5G赋能车联网,无人驾驶引领未来出行

无人驾驶车联网应用已成为智能交通领域的重要发展趋势。随着无人驾驶技术的不断进步和5G网络的广泛部署&#xff0c;5G工业路由器在无人驾驶车联网中的应用日益广泛&#xff0c;为无人驾驶车辆提供了稳定、高效、低时延的通信保障。 5G工业路由器的优势 低时延&#xff1a;5G网…

Python教程(一):环境搭建及PyCharm安装

目录 引言1. Python简介1.1 编译型语言 VS 解释型语言 2. Python的独特之处3. Python应用全览4. Python版本及区别5. 环境搭建5.1 安装Python&#xff1a; 6. 开发工具&#xff08;IDE&#xff09;6.1 PyCharm安装教程6.2 永久使用教程 7. 编写第一个Hello World结语 引言 在当…

Open3D 可视化窗口中查看点的坐标数据

目录 一、概述 1.1实现步骤 1.2应用 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1选取点 3.2数据显示 前期试读&#xff0c;后续会将博客加入下列链接的专栏&#xff0c;欢迎订阅 Open3D与点云深度学习的应用_白葵新的博客-CSDN博客 一、概述 可以使用Op…

Java语言程序设计基础篇_编程练习题**15.19 (游戏:手眼协调)

**15.19 (游戏:手眼协调) 请编写一个程序&#xff0c;显示一个半径为10像素的实心圆&#xff0c;该圆放置在面板上的随机位置&#xff0c;并填充随机的顔色&#xff0c;如图15-29b所示。单击这个圆时&#xff0c;它会消失&#xff0c;然后在另一个随机的位置显示新的随机颜色的…

【工具】轻松转换JSON与Markdown表格——自制Obsidian插件

文章目录 一、插件简介二、功能详解三、使用教程四、插件代码五、总结 一、插件简介 JsonMdTableConverter是一款用于Obsidian的插件&#xff0c;它可以帮助用户在JSON格式和Markdown表格之间进行快速转换。这款插件具有以下特点&#xff1a; 轻松识别并转换JSON与Markdown表格…

Java | Leetcode Java题解之第278题第一个错误的版本

题目&#xff1a; 题解&#xff1a; public class Solution extends VersionControl {public int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVers…

【linux驱动开发】卸载驱动时报错:Trying to free already-free IRQ 0

【linux驱动开发】free_irq时报错:Trying to free already-free IRQ 0 卸载驱动时报错Trying to free already-free IRQ 0 第一次加载卸载驱动没有任何问题。第二次加载驱动&#xff0c;按键中断触发失效&#xff0c;卸载驱动时报错:Trying to free already-free IRQ 0 看了…

牛客周赛50轮+cf955+abc363

D-小红的因式分解_牛客周赛 Round 50 (nowcoder.com) 思路&#xff1a; 巨蠢的题目&#xff0c;ax^2bxca1*a2*x^2(b1*a2b2*a1)xb1*b2&#xff0c;即&#xff1a; aa1*a2,ba1*b2a2*b1,cb1*b2 数据范围很小&#xff0c;直接暴力枚举吧&#xff08;注意条件&#xff09; 代码…

简单使用SpringMVC写一个图书管理系统的登入功能和图书展示功能

准备好前端的代码 这里已经准备好了前端的代码&#xff0c;这里仅仅简单的介绍登入功能&#xff0c;和展示图书列表的功能。 如图&#xff1a; 如上图所示&#xff0c;这里的前端代码还是比较多的&#xff0c;在这里我介绍&#xff0c;login.html还有book_list.html这两个。 l…