如何在 Java 中检测和避免内存泄漏

选择使用 Java 为编程开辟了广泛的可能性,这是有充分根据的。这是一种排版的高级编程语言,由  Sun Microsystems创建,后来被Oracle赎回,语法相当灵活。20 多年来,Java 一直保持其知名度,在世界技术市场上占据领先地位之一。

它的优点之一是 Java 中的自动内存管理,特别是与没有此功能的 C 和 C++ 语言相比。它是使用内置的垃圾收集器技术« Garbage Collector » 或 GC 来执行的。

为了解释这一重要特性,值得注意的是,系统在其内存中积累了占用内存但未使用的对象。该工具显着有助于消除 内存泄漏等问题。

当然,Java 并不是唯一具有垃圾收集器这样重要且有用的功能的平台。但除了它的其他功能,例如 HotSpot 优化器或出色的向后兼容性,比较 – Java 在其他平台中肯定脱颖而出。

那么如果Java有这样一个有用的分配和清除内存的功能有什么问题呢?让我们来看看。

什么是内存泄漏及其原因

为了更好地理解直接示例的本质,您应该从 Java 内存结构开始。

1.内存结构及其清理

Java 应用程序数据可以存储在空间块中,例如 «Stack» 或 «Heap»。

  • 堆栈内存是对“堆”元素和原始值类型的存储引用的存储库;
  • 包含堆栈中的变量引用的动态对象。

因此,在默认设置下,堆在内存中占用的空间比堆栈要多得多。但是,可以通过在增加堆栈值后将堆大小设置为低于堆栈值来手动更改这些设置。

Stack 具有 LIFO原则(最后进入,先离开)。每当引用元素或原始值调用新方法时,都会在堆栈顶部释放一块内存:

与堆栈不同,堆不会自行清理。因此,系统迫切需要垃圾收集器功能。如果没有它的存在,我们只能管理堆的大小:

随着时间的推移,对可能包含堆栈变量的对象的引用成为垃圾收集器的合适目标,具体取决于其类型。并且,当堆内存中出现红色项目时,它们可以由收集器组装。

内存中的垃圾项示例:

2.内存泄漏

Java内存泄漏是GC在系统中留下未使用的元素时的一种错误。这可能是因为无法删除堆栈中可能引用的一些垃圾项。

泄漏的典型示例:

这种泄漏对利用系统资源的能力及其整体生产力具有负面影响。如果忽略此问题,系统可能会完全耗尽数据存储空间,并以不可逆的错误“Java Out Of Memory Error”结束。

最好使用内存管理工具来优化它。其中最相关的:

3、Java中内存泄漏的原因

Java 中的内存泄漏可能是由于代码中不可预见的错误导致对云中不需要的对象的引用。这些链接会阻止 GC 功能。因此,无法清理存储库,这些元素没有使用它。

Java内存泄漏的主要原因:

  • 无限缓存;
  • 一个会话中的文件溢出;
  • 更换操作系统页面过多;
  • 用户数据系统存在漏洞;
  • 在集合中插入元素而不删除它们;
  • 不可复制的聆听方式。

Java 中的内存泄漏及其类型

各种类型的泄漏都是可能的。它们的差异基于它们是如何产生的以及是什么原因造成的。

最常见的泄漏类型:

1.过度使用静态变量

Java 中静态字段的生命周期通常对应于应用程序会话时间,而不考虑具有 « ClassLoader » 功能的垃圾收集。在命令执行期间分析内存堆时,我们可以观察到控制点 1 和 2 之间的内存增加:

但是在 VisualVM 中的第 3 点停止方法 «populateList()» – 堆存储库仍未处理。但是,如果您不考虑第 2 行中的 «static»,它将更改内存值:

在这种情况下,在使用方法«populateList()»进行操作后,堆内存被收集器清理,因为对对象的所有引用都被停用:

2.参考外部的内部类的可用性

要初始化静态类,您总是需要来自外部类的示例。每个非静态默认类都包含对保存它的类的隐藏引用。如果你使用一个内部类对象——它不会被 GC 收集,即使你关闭了一个外部类对象。

例如,采用一个包含非静态值并引用多个体积元素的类。在这种情况下,内部类对象的创建有这样的统计指标:

将内部或匿名类的值平庸地更改为静态,内存值将发生根本性的变化。这可能是由于内部类的内容对外部对象的引用,从而阻塞了 GC 的主要功能。

3. 应用内未关闭的资源

每当我们创建新连接或打开线程时,JVM 总是为新线程或连接分配内存空间。此类连接可能具有带有综合数据库的会话对象。

如果我们不关闭这些资源,我们就有可能让存储被阻塞。这会产生垃圾收集器无法检测和删除这些对象的风险。如果您忽略保持稳定和正确关闭资源,它们将填满内存,直至出现错误«Java Out Of Memory Error。»

4. 使用 finalize() 方法

在该方案中,当我们有一个类重新定义了 finalize() 方法时,它的对象可能不会及时收集 GC,而是会排队等待删除。此外,finalize() 中错误覆盖的代码及其与垃圾收集器的速度不匹配可能导致错误 «OutOfMemoryError。»

例如,采用一个具有重新定义的方法 finalize() 的类,并且需要时间。

如果你有大量被 GC 收集的元素,就会形成这样的堆分数:

如果你简单地移除一个重新定义的 finalize(),你可以得到这个值:

5. 使用内部字符串

在 Java 系统版本 6 中,必须小心使用体积字符串。版本 7 将字符串池的更改从 PermGen 移到了 HeapSpace。

如果您调用方法 «intern()» 来读取大字符串 – 它会保存到常量内存 ( PermGen ) 中的字符串池中。此方法存储在 PermGen 中直到会话结束,这会导致应用程序内存不足。

永久内存的一个例子是在读取没有其国际文件的字符串时:

6. ThreadLocals 参与度

ThreadLocal – 一种通过关闭其变量的值来创建流安全性的工具。同时,所有线程都有一个指向重复 ThreadLocal 变量的隐藏链接,并保存它们的副本,而不是在所有线程中使用资源。

除了有用之外,此功能还存在错误。误用时会影响泄漏。

ThreadLocal 变量必须在删除包含它们的线程后由 GC 收集。

但是,某些系统服务器可能无法正确使用此功能。这可能是因为服务器没有为所有请求创建一个新线程,而是应用了整个线程池。

服务器中的池重用线程,使垃圾收集器无法访问它们并占用内存。

7. 实现不正确的equals()和hashCode()

在创建新类时,我们经常会遇到 equals() 和 hashCode() 方法的覆盖错误。HashSet 和 HashMap 常用此类方法,如果它们包含覆盖错误,则会导致内存消耗过多。

例如,您可以使用ORM Hibernate,它采用 equals() 和 hashCode() 方法来处理项目并将它们存储在缓存中。如果这些方法没有被覆盖,Hibernate 将不会分析这些项目,并且缓存将填充它们的副本,从而导致内存泄漏。

症状和 Java 内存泄漏检测

1.内存泄漏症状

有几个可疑点可能表明存在泄漏:

  • 持续的和不可预见的系统故障;
  • 不稳定的应用功能支持;
  • 长时间会话期间发生错误«Java.lang.OutOfMemoryError»;
  • 系统移除连接对象;
  • 显着降低整体系统性能。

2.如何检测Java内存泄漏

为了检测泄漏,需要多种工具和技术,以及它们的组合。有一个受信任方法的列表:

2.1。内存分析器:跟踪在存储库中占用空间的文件和项目的工具。他们能够检测泄漏并分析系统中使用的元素的正确分布。此外,估计处理时间。

用于分析 Java 内存的最常用工具:

2.2. 参与堆转储:这是一个用于在 Java 内存存储中创建堆的即时和及时快照的工具。需要这些图像来控制使用的对象数量及其在内存中的权重。此外,该工具还跟踪系统创建的元素数量以及可能影响泄漏的因素。

2.3. 激活详细垃圾收集 日志。该工具能够演示对存储库和 GC 中堆配置的更改。它提供了应用程序最准确的功能和性能。并通过识别堆中合适的元素、其替代方法和 JVM 参数来优化收集器的性能。

例如,使用 JVM 启动方法在应用内激活详细集合:

«-XX: +UseSerialGC -Xms1024m -Xmx1024m -verbose:gc»

«-verbose:gc» 参数激活收集信息的记录。默认情况下,日志保存到标准输出并为所有 GC 生成行。还可以使用 «-XX: +UseSerialGC» 参数指定顺序 GC,并设置堆大小。

这有助于及时检测泄漏并配置应用程序运行状况指标。

可以在GitHub服务上找到 GC 日志激活的示例。

如何修复内存泄漏。纠错方法

为了解决这个问题,首先要考虑它出现的原因。因此,有必要识别泄漏的类型并根据其种类解决问题。

对于每种类型,都有不同的方案来修复 Java 中的内存错误:

1.如何解决内存错误?

1.1。使用静态变量时:

尽量减少系统中静态字段的使用。在这种情况下,您可以使用 «lazy» 而不是紧急加载对象。

1.2. 如果有内部类,参考外部:

如果不需要外部类元素,您可以将内部类转换为静态类。

1.3. 如果应用程序的资源没有关闭:

«finally» 应及时激活以完成资源的使用。

1.4. 使用 finalize() 方法时:

应将与决赛选手的任何工作减少到零。

1.5。使用内部字符串时

尝试将 Java 应用程序升级到最新版本。这可以通过在第 6 个版本之后将字符串池移动到堆的空闲位置来实现。并且为了避免错误« Out Of Memory Error »,在使用批量字符串时,您可以扩展«PermGen»大小。

1.6. 使用 ThreadLocals 时

在不需要时稳定地清理 ThreadLocal 变量。具有 remove() 属性的 ThreadLocal – 删除所有当前线程的变量值。它必须在 «finally» 块中关闭以确保它被停用。

1.7. 当实现不正确的 equals() 和 hashCode()

创建新元素时,最好覆盖 equals() 和 hashCode() 路径。

2.其他消除内存泄漏的方法

当对内存泄漏的发生原因没有清楚的了解时,您还可以应用其他方法来对抗内存泄漏。

2.1。使用基准测试:

要详细分析 Java 中的代码性能,您可以将其测试与基准测试结合使用。

通过这种方式,通过比较它们的有效性,可以很容易地评估执行任务的不同方式的生产力。这优先考虑最佳实践,这将避免不必要的内存消耗。

2.2. 参考对象应用程序:

在 Java 中应用特殊引用对象而不是直接引用应用程序中的元素的选项。使用包«java.lang.ref»中包含的此类链接允许垃圾收集器简单地删除多余的对象。

2.3. 代码审查:

平庸,但在某些情况下同样有效的方法——检查代码。有时它有助于消除内存泄漏。

2.4. 启用分析:

通过使用上述方法,您可以发现可用于存储应用程序资源的有用存储区域。

2.5。详细垃圾收集:

前面提到的另一种方式。包括这种模式,我们可以密切监控GC的工作。

遵守此类存储空间规则及其合理使用有助于消除造成 Java 备份编程和 Java 备份语言激增的情况。这是整个系统正常运行的基础。

3. 删除工具出错:

作为使用 Java 的专家报告,在门户网站Habr上– 引用:

“找到并修复泄漏后,再次执行所有步骤,分析内存,并向 内存分析器报告以确保修复有所帮助,这并不是多余的。”

结论

因此,分析这个问题——什么是 Java 中的内存泄漏,很明显它会影响系统,因为它会影响系统并使其整体状况恶化。如果不进行治疗,可能会导致无法挽回的后果。由于这是一个非常严重的问题,虽然乍一看并不明显,但需要及时发现和解决。

如果在其存在的最初迹象出现很久之后才发现该问题,则该问题不容易解决。没有什么“灵丹妙药”可以一次性修复泄漏,即使涉及到 Java 编程方面的高素质专家的工作也是如此。

但是,如果使用该应用程序始终涉及使用经过验证的方法、分析、跟踪、Java 内存管理和代码验证 – 此类问题的出现可以减少到零。

你有没有遇到过 Java 中的内存泄漏?在评论中分享您识别问题类型的经验以及您解决问题的方法!