当前位置:首页  >  行业资讯  > 正文

当前速递!一文搞懂 Java 中的内存泄漏(Memory Leak)

当前速递!一文搞懂 Java 中的内存泄漏(Memory Leak)
2023-02-15 12:01:52 来源:腾讯云

Hello folks,在今天的这篇文章中,我将讨论 Java 虛擬機生态体系中的一个至为关键內容—— Memory Leak(内存泄漏)。

从事 Java 开发的技术人员应该都知道:Java 的核心优势之一是基于其内置的垃圾收集器(或简称 GC)的帮助下能够进行内存自动管理。GC 隐式地负责分配和释放内存,从而使得其能够处理大多数内存泄漏问题。

诚然,在某种意义上而言,GC 能够有效地处理大部分的内存问题,但它并不是一种保证万无一失的内存泄漏解决方案。的确,GC 生性非常聪明,但它并非完美无缺,因为内存泄漏仍然可能悄悄地发生,仍然可能存在应用程序生成大量多余对象的情况,然后耗尽关键内存资源,从而导致整个应用程序失败,业务故障。


(资料图)

因此,Memory Leak (内存泄漏)是 Java 虛擬機體系中的一个真正的疑难问题。

在解析 Memory Leak(内存泄漏)之前,我們先來澄清一下相關概念。Memory Leak 與 OutOfMemoryError(內存溢出):内存泄漏可以视为一种問題, OutOfMemoryError 則视为一种症状。因此,并非所有 OutOfMemoryErrors 都意味着内存泄漏,并且并非所有内存泄漏都表现为 OutOfMemoryErrors。

何为 Java 中的 Memory Leak ?

Memory Leak ,即“内存泄漏”,通常是指一个或多个对象不再被使用,但同时又无法被持续工作的垃圾收集器清除的情况。

我们可以将内存中的对象分为两大类:

1、引用对象是可以从我们的应用程序代码访问并且正在或将要使用的对象。

2、未引用的对象是应用程序代码无法访问的对象。

垃圾收集器最终会从堆中移除未引用的对象,为新对象腾出空间,但它不会移除被引用的对象,因为它们被认为很重要。这样的对象会使 Java 堆内存越来越大,并推动垃圾回收做更多的工作。这将导致所构建的应用程序通过抛出 OutOfMemory 异常而变慢甚至最终崩溃。

通常而言,内存泄漏是不好的,在實際的業務場景中,无论是基于业务表現还是用户体验,因为它会阻塞内存资源并随着时间的推移導致系统性能下降。如果不加以及時处理,应用程序最终将耗尽其资源,最终以致命的 Java.lang.OutOfMemoryError 异常终止退出。

在 Java 内存模型设计中,有两种不同类型的对象驻留在堆内存中,“引用的”和“未引用的”。引用对象是那些在应用程序中仍然具有活动引用的对象,而未引用对象没有任何活动引用。

垃圾收集器定期清除未引用的对象,但它默认情况下不会收集仍在引用的对象。这是可能发生内存泄漏的地方,具體如下所示:

Memory Leak 症状

在實際的場景中,有一些較為明顯的症状可以让我们怀疑所构建的 Java 应用程序正在遭受内存泄漏之困扰。以下为最常见的场景:

1、应用程序运行时出现 Java OutOfMemory 错误。‍

2、应用程序运行时间较长时性能下降,并且不会在应用程序启动后立即出现。

3、应用程序运行的时间越长,垃圾收集次数就越多。

4、连接用完。

Why Memory Leak ?‍

这是一个很残酷的现实,Java 中的内存泄漏通常可能是由于代码中无法预料的错误而发生的,这些错误会保留对不需要的对象的引用,除此之外,这些链接会阻止 GC 功能操作。

在某些特定的場景下,即使指定了 System.gc() 方法也是如此。当内存不足或可用内存不足以支撐程序所需时,垃圾收集器很可能会启动。如果垃圾收集器没有释放足够的内存资源,那麼,應用程序将會使用操作系统的内存。

与 C++ 和其他编程语言中的内存泄漏相比,Java 内存泄漏通常没有那么严重。根据 IBM developerWorks Jim Patrick 的说法,在考虑内存泄漏时需要考虑两个方面:

1、泄漏的大小

2、程序的生命周期

如果 JVM 有足够的内存来运行所構建的應用程序,那么小型 Java 应用程序中的内存泄漏并不重要。另一方面,如果我們的 Java 应用程序持续运行,内存泄漏将是一个嚴肅的问题,畢竟,无限期运行的软件最终会耗尽内存,從而導致業務故障。

当應用程序使用大量内存的临时对象时,也会发生内存泄漏。如果不取消引用这些耗费大量内存的对象,程序将很快耗尽可访问的内存。

不过,幸运的是,在实际的经验总结中有几种类型的 Java 内存泄漏是众所周知的,通过在编写 Java 代码时给予一定程度的关注,我们可以确保它们不会出现在我们的代码中。

Memory Leak 实践场景‍‍‍‍

1、静态字段持有对象

可能导致潜在内存泄漏的第一种情况是大量使用静态变量。 Java 内存泄漏的最简单、直接的示例之一便是通过未清除的静态字段引用的对象。例如,一个静态字段包含一组我们永远不会清除或丢弃的对象。

以下為演示此类行为的一个简单的代码示例:

public class StaticReferenceLeak {  public static List NUMBERS = new ArrayList<>();  public void addBatch() {    for (int i = 0; i < 100000; i++) {      NUMBERS.add(i);    }  }  public static void main(String[] args) throws Exception {    for (int i = 0; i < 1000000; i++) {        (new StaticReferenceLeak()).addBatch();        System.gc();        Thread.sleep(10000);    }  }}

addBatch 方法将 100000 个整数添加到名为 NUMBERS 的集合中。当然,如果我们需要这些数据,这完全没问题。但在这种情况下,我们永远不会删除它。即使我们在 main 方法中创建了StaticReferenceLeak 对象并且没有持有对它的引用,我们也很容易看出垃圾收集器无法清理内存。相反,它不断增长:

如果我们看不到 StaticReferenceLeak 类的实现细节,我们会期望对象使用的内存被释放,但事实并非如此,因为 NUMBERS 集合是静态的。如果它不是静态的就没有问题,所以在使用静态变量时要格外小心。

解决方案:

为避免并可能防止此类 Java 内存泄漏,因此,应该尽量减少静态变量的使用。如果必须拥有它们,请格外谨慎,当然,在不再需要时从静态集合中删除数据。

2、未关闭的资源

访问位于远程服务器上的资源、打开文件并处理它们等等并不少见。此类代码需要在我们的代码中打开流、连接或文件。但我们必须记住,我们不仅要负责打开资源,还要负责关闭资源。否则,我们的代码可能会泄漏内存,最终导致 OutOfMemory 错误。

为了说明这个问题,让我们看一下下面的例子:

public class UnclosedResources {  public static void main(String[] args) throws Exception {    for (int i = 0; i < 1000000; i++) {      URL url = new URL("http://www.google.com");      URLConnection conn = url.openConnection();      InputStream is = conn.getInputStream();      // rest of the code goes here    }  }}

上述循环的每次运行都会导致打开和引用 URLConnection 实例,从而导致资源(内存)缓慢耗尽。

解决方案:

(1)始终使用 finally 块来关闭资源‍

(2)关闭资源的代码(即使在 finally 块中)本身不应有任何异常‍

(3)使用 Java 7+ 时,我们可以使用 try -with-resources 块‍

3、使用 ThreadLocals

ThreadLocal 是 Java 世界中的一个结构体,可以让我们将处理范围隔离到当前线程,从而在某些情况下实现线程安全。我们可以保留有关当前用户的信息、绑定到用户的执行上下文或任何需要在线程之间进行隔离的信息。

ThreadLocal(在 Introduction to ThreadLocal in Java tutorial 中有详细讨论)是一种构造,它使我们能够将状态隔离到特定线程,从而使我们能够实现线程安全。

使用此构造时, 每个线程都将持有对其 ThreadLocal 变量副本的隐式引用,并将维护自己的副本,而不是在多个线程之间共享资源,只要线程处于活动状态。

尽管有很多优点,但使用 ThreadLocal 变量是有争议的,因为如果使用不当,它们会因引入内存泄漏而臭名昭著。Joshua Bloch 曾经评论过线程局部使用:

“Sloppy use of thread pools in combination with sloppy use of thread locals can cause unintended object retention, as has been noted in many places. But placing the blame on thread locals is unwarranted.”

当你开始从更广阔的角度思考时,问题就出现了。现代应用程序服务器或 Servlet 容器使用线程池来控制可以并发运行的线程数,从而一遍又一遍地重用相同的线程。在这种情况下,线程会被重用并且不会被垃圾回收,因为对线程的引用一直保存在池本身中。

这不是 ThreadLocal 本身的问题,但总的来说,这是现代技术堆栈内部发生的复杂情况。我们应该预料到并记住分配给 ThreadLocal 的值将被保留,因此需要清理,否则内存将在 ThreadLocal 内部使用。

解决方案:

(1)、当我们不再使用 ThreadLocals 时,清理它们是一种很好的做法。ThreadLocals 提供了 remove()方法,该方法删除当前线程为此变量的值。

(2)、不要使用 ThreadLocal.set(null) 来清除值。它实际上并没有清除该值,而是会查找与当前线程关联的 Map,并将键值对分别设置为当前线程和 Null。

(3)、最好将 ThreadLocal 视为我们需要在 finally 块中关闭的资源,即使在出现异常的情况下也是如此:

try {    threadLocal.set(System.nanoTime());    //... further processing}finally {    threadLocal.remove();}

4、 引用外部类的内部类

在我看来,这是一个非常有趣的案例——内部私有类保留对其父类的引用的案例。具體如下场景所示:

public class OuterClass {  // some large arrays of values  private InnerClass inner;  public void create() {    inner = new InnerClass();    // do something with inner and keep it  }  class InnerClass {    // some logic of the inner class  }}

假设 OuterClass 包含对大量占用大量内存的对象的引用,即使不再使用它也不会被垃圾收集。那是因为 InnerClass 对象将隐式引用 OuterClass ,这使得它不符合垃圾收集的条件。

解决方案:

这是关于内部类的要求,是否应该访问外部类中的数据。如果不是,将内部类变为静态将解决该问题。当然,我们还可以首先考虑内部私有类是否真的需要,也许可以使用不同的架构模式。

5、 使用不正确 equals() 和 hashCode() 的实现

Java 内存泄漏的另一个常见示例便是使用具有未正确实现(或根本不存在)的自定义 equals() 和 hashCode() 方法的对象,以及使用哈希检查重复项的集合。这种集合的一个典型代表便是 HashSet。

为了说明这个问题,让我们看一下下如下的例子:

public class HashAndEqualsNotImplemented {  public static void main(String[] args) {    Set set = new HashSet<>();    for (int i = 0; i < 1000; i++) {      set.add(new Entry("test"));    }    System.out.println(set.size());  }}class Entry {  public String entry;  public Entry(String entry) {    this.entry = entry;  }}

在我们深入解释之前,问自己一个简单的问题:代码将使用 System.out.println(set.size()) 调用打印的数字是多少?如果答案是 1000,那么将是是正确的。那是因为我们没有正确实现 equals 方法。这意味着添加到 HashSet 的 Entry 对象的每个实例都会被添加,而不管从我们的角度来看它是否是重复的。这可能会导致 OutOfMemory 异常。

如果我们用正确的实现来改变我们的代码,代码将导致打印 1 作为我们的 HashSet 的大小。我們以如下場景進行簡單舉例說明,下面是 JetBrains IntelliJ 实现的 equals() 和 hashCode() 方法的代码:

public class HashAndEqualsNotImplemented {  public static void main(String[] args) {    Set set = new HashSet<>();    for (int i = 0; i < 1000; i++) {      set.add(new Entry("test"));    }    System.out.println(set.size());  }}class Entry {  public String entry;  public Entry(String entry) {    this.entry = entry;  }  @Override  public boolean equals(Object o) {    if (this == o) return true;    if (o == null || getClass() != o.getClass()) return false;    Entry entry1 = (Entry) o;    return Objects.equals(entry, entry1.entry);  }  @Override  public int hashCode() {    return Objects.hash(entry);  }}

解决方案:

根据以往的经验,在创建类时应正确实现 equals() 和 hashCode() 方法。大多数现代 IDE 将帮助实现我们进行优化。

6、使用 finalize() 方法

使用终结器是潜在内存泄漏问题的另一个来源。每当重写类的 finalize() 方法时,该类的对象不会立即被垃圾回收。取而代之的是,GC 将它们排队等待最终确定,这发生在稍后的时间点。

此外,如果在 finalize() 方法中编写的代码不是最优的,并且如果终结器队列跟不上 Java 垃圾收集器,那么迟早我们的应用程序注定会遇到 OutOfMemoryError。

解决方案:

很簡單,禁用此方法。

當然,除了如上所述的場景之外,也存在其他的場景,畢竟,基於不同的環境、不同的場景,便會展示不同的現象。

通俗地说,我们可以将内存泄漏视为一种疾病,它通过阻塞重要的内存资源来降低应用程序的性能。和所有其他疾病一样,如果不治愈,随着时间的推移,它可能会导致致命的应用程序崩溃。

Memory Leak,作為一種症狀,有的時候的確很难解决,通常需要對 Java 语言以及操作系統相關知識體系有很深的理解與掌握。畢竟,在处理内存泄漏时,没有一种万能的解决方案,因为泄漏可能通过各种不同的事件、場景发生。

然而,在實際的項目開發活動中,如果我们能夠采用最佳实践并定期执行严格的代码評審和分析,那麼,我们可以将应用程序中内存泄漏的风险降至最低,從而減少損失。‍

Adiós !

标签: Java 编程算法

(责任编辑:news01)
当前速递!一文搞懂 Java 中的内存泄漏(Memory Leak)

当前速递!一文搞懂 Java 中的内存泄漏(Memory Leak)

Hellofolks,在今天的这篇文章中,我将讨论Java虛擬機生态体系中的一个至为关键內容——MemoryLeak(内存泄漏)。
02-15 12:01:52
【天天报资讯】多哈赛中国小花郑钦文苦战三盘不敌5号种子,遭遇一轮游!

【天天报资讯】多哈赛中国小花郑钦文苦战三盘不敌5号种子,遭遇一轮游!

北京时间2月15日凌晨,WTA500卡塔尔多哈网球公开赛第一轮比赛展开争夺,上周在阿布扎比打入四强,本周世...
02-15 10:50:39
当前最新:tenda路由器登录

当前最新:tenda路由器登录

tenda产品涵盖无线、交换机、网关、接入终端、PLC电力网及网络摄像机等,能够一站式满足终端组网多种需...
02-15 10:29:27
世界聚焦:2023四省联考高三2月联考答案及各科试卷汇总_云南、安徽、黑龙江、吉林

世界聚焦:2023四省联考高三2月联考答案及各科试卷汇总_云南、安徽、黑龙江、吉林

2023四省联考高三2月联考将于2月23日开考,2023四省联考是云南、安徽、黑龙江、吉林这个四个旧高考省份...
02-15 08:55:23
世界速看:于荣光老中医_于光荣

世界速看:于荣光老中医_于光荣

1、:1985《木棉袈裟》饰祁天远1987《海市蜃楼》饰唐庭轩1988《阮氏三雄》饰阮小二1988《代号美
02-15 06:55:10
全球观热点:《童年》读后感500字范文10篇

全球观热点:《童年》读后感500字范文10篇

《童年》是高尔基所著,它与《在人间》及《我的大学》被人们称为“自传体三步曲”。那么《童年》读后感...
02-15 06:28:11
通讯!中秋国庆双节浪漫祝福语_中秋国庆双节浪漫祝福语精选

通讯!中秋国庆双节浪漫祝福语_中秋国庆双节浪漫祝福语精选

1、中秋送月饼,国庆送祝福。今天是国庆节,特意发来祝福短信。在国庆节,你离开工作的心去放松,打包一...
02-15 03:05:23
当前关注:鸡蛋羹如何做有哪些家常好吃做法

当前关注:鸡蛋羹如何做有哪些家常好吃做法

蒸鸡蛋羹是鸡蛋中很典型的一种做法,而蒸鸡蛋羹该怎么做呢?蒸鸡蛋羹的做法游泳哪些呢?下面是学习啦小编...
02-15 02:35:12
当前热文:林书豪为什么被火箭裁掉了_林书豪现在是效力于火箭吗还是别的队

当前热文:林书豪为什么被火箭裁掉了_林书豪现在是效力于火箭吗还是别的队

1、的确,目前效力于火箭队,与哈登并肩作战。2、但以后不太确定,至少根据合同,将来一段时间按不会变...
02-14 22:43:34
今日热文:公司战略规划方案范文4篇

今日热文:公司战略规划方案范文4篇

如何做好公司战略规划?下面是公司战略规划方案范文,欢迎参阅。公司战略规划方案范文1宝洁公司创于1837...
02-14 22:44:17
环球速递!生日礼物送什么给男性朋友

环球速递!生日礼物送什么给男性朋友

送男朋友的生日礼物,就送一些实用的就好了。如钱包,皮带,打火机,领带,打火机等,都是男性用品,都...
02-14 20:18:22
世界热点评!美丽乡村莱州行作文

世界热点评!美丽乡村莱州行作文

美丽乡村莱州行作文【篇一】莱州,是一座幸福的城市。她非常美丽。莱州的花美。月季花是莱州的市花。莱...
02-14 18:35:47
快消息!新地NOVO LAND第2B期短期内上载楼书及开放示位

快消息!新地NOVO LAND第2B期短期内上载楼书及开放示位

新地NOVOLAND第2B期短期内上载楼书及开放示位,楼书,户型,住宅,novo,land
02-14 18:07:41
每日动态!阅读小报内容资料大全_中秋小报内容资料

每日动态!阅读小报内容资料大全_中秋小报内容资料

1、中秋节的手抄小报内容:嫦娥应悔偷灵药,碧海青天夜夜心。2、——李商隐《嫦娥》2、今夜月明人尽望,...
02-14 16:59:03
全球滚动:大腿说不出来的难受怎么回事_大腿难受说不出来那种是什么原因

全球滚动:大腿说不出来的难受怎么回事_大腿难受说不出来那种是什么原因

1、大腿感觉不舒服,说不出来的感觉,可能是肌肉问题,也可能是神经感觉异常问题。2、1 如果是肌肉问题...
02-14 14:50:11
天天快报!世界上最贵的汽车

天天快报!世界上最贵的汽车

世界上最贵的汽车我国国家最新标准《汽车和挂车类型的术语和定义》中对汽车有如下定义:由动力驱动,具...
02-14 14:36:34
当前速读:首付30%,月均花费10010元,配备双电机+超3米轴距的蔚来ET7如何

当前速读:首付30%,月均花费10010元,配备双电机+超3米轴距的蔚来ET7如何

买车要考虑自己的经济条件,根据自己的收入水平选择适合自己的车型。对于很多工薪阶层来说,月收入五六...
02-14 13:01:32
当前播报:中国光大水务与山东淄博高青县签订环境综合治理合作协议

当前播报:中国光大水务与山东淄博高青县签订环境综合治理合作协议

2月7日上午,中国光大水务有限公司与高青县就环境综合治理项目签订合作协议,喜迎重大项目招商“开门红...
02-14 10:51:56
视焦点讯!情人节送男生什么礼物合适

视焦点讯!情人节送男生什么礼物合适

如果这个男生可以送给他一个钱包,或者他一个包、一个打火机,一个钱包都可以,只要你们心爱的人,他都...
02-14 09:23:43
要闻:外汇交易提醒:美元冲高回落,市场对美国CPI预期产生分歧

要闻:外汇交易提醒:美元冲高回落,市场对美国CPI预期产生分歧

汇通网讯——周二美元指数冲高回落,早些时候因为市场预期美国1月份通胀增速回升,美元一度逼近逾一个月...
02-14 07:21:02
今日热门!在长春,一汽集团工作是一种什么样的体验?

今日热门!在长春,一汽集团工作是一种什么样的体验?

垃圾中的战斗鸡在长春一汽集团工作挺好的。看你是以什么资格进入集团工作的,如果是管理技术人员那都是2...
02-14 04:54:28
【环球新视野】美股九洲大药房盘中上涨45.66%,现报7.210美元,当前市值为1.47亿美元。

【环球新视野】美股九洲大药房盘中上涨45.66%,现报7.210美元,当前市值为1.47亿美元。

美股九洲大药房盘中上涨45 66%,现报7 210美元,当前市值为1 47亿美元
02-14 03:04:27
世界今日讯!入木三分的历史人物是谁

世界今日讯!入木三分的历史人物是谁

入木三分的历史人物是谁,入木三分的历史人物是王羲之,出自于张怀瓘《书断·王羲之》。王羲之的书法堪...
02-14 00:48:29
世界百事通!小孩短发扎法100图片小学生_小女孩短发扎法100图片

世界百事通!小孩短发扎法100图片小学生_小女孩短发扎法100图片

1、一个,但是为了配合夏天女孩的发型,还是想把孩子的头发凉一凉。短发的孩子怎么扎头发?2、第二,马...
02-13 22:44:50
【天天报资讯】刺史和太守哪个大

【天天报资讯】刺史和太守哪个大

刺史和太守哪个大,刺史这一官职要比太守这官职大。根据史料记载,刺史这一官职始于西汉时期。在西汉时...
02-13 21:05:06
环球微动态丨西安高陵区保租房电话是多少

环球微动态丨西安高陵区保租房电话是多少

一、西安高陵区保租房咨询电话1、高陵区住房保障中心:029-869259882、安居·乐筑(泾渭店):029-68055...
02-13 19:53:15
【全球独家】更新转场套装 ||3500款视频转场效果,您想要的,都在这里了

【全球独家】更新转场套装 ||3500款视频转场效果,您想要的,都在这里了

优秀的转场在提升作品质量的同时更能让甲方爸爸眼前一亮为作品买单的同时,不失认可专业水准若是不费力...
02-13 18:03:14
视讯!小礼品送什么好

视讯!小礼品送什么好

首先是要根据客人的喜好来送礼的。如客户是个人,他可以送:中国的烟酒、中国名酒、中国结,送一些传统...
02-13 16:29:32
环球百事通!预防新冠感染重症发生!针对重点人群 浙江要求做好健康随访工作

环球百事通!预防新冠感染重症发生!针对重点人群 浙江要求做好健康随访工作

日前,省卫生健康委办公室印发通知,要求进一步做好重点人群健康随访工作,预防和减少新冠病毒感染重症...
02-13 14:47:51
全球快资讯丨珈伟新能:未涉及与特斯拉的合作

全球快资讯丨珈伟新能:未涉及与特斯拉的合作

同花顺金融研究中心2月13日讯,有投资者向珈伟新能提问,请问董秘贵公司和特斯拉在储能方面有哪些合作?...
02-13 12:29:40

精彩推送