更新时间:2019-09-04 09:55:48 来源:极悦 浏览2056次
如何在Java应用程序中发生内存泄漏
我们来分析Java应用程序中的内存泄漏的解剖结构,以了解它们的来源,以及如何为他们做好准备,找到并修复它们。
1.什么是Java内存泄漏?
内存泄漏的标准定义是当对象不再被应用程序使用时发生的情况,但垃圾收集器无法将其从工作内存中删除-因为它们仍被引用。因此,应用程序消耗越来越多的资源-这最终导致致命的OutOfMemoryError。
为了更好地理解这个概念,这里有一个简单的视觉表示:
我们可以看到,我们有两种类型的对象-引用和未引用;垃圾收集器可以删除未引用的对象。引用的对象不会被收集,即使它们实际上不再被应用程序使用。
检测内存泄漏可能很困难。许多工具执行静态分析以确定潜在的泄漏,但这些技术并不完美,因为最重要的方面是运行系统的实际运行时行为。
因此,通过分析一些常见的情况,让我们重点关注一些防止内存泄漏的标准做法。
Java堆漏洞
在这个初始部分中,我们将重点介绍经典的内存泄漏情况,其中Java对象不断发布,不断创建。
理解这些情况的有利技术是通过为堆设置较小的尺寸来使再现内存泄漏更容易。这就是为什么在开始我们的应用程序时,我们可以调整JVM以适应我们的内存需求:
这些参数指定了初始Java堆大小以及最大堆大小。
2.1。静态场控制在对象引用
可能导致Java内存泄漏的第一种情况是引用具有静态字段的重型对象。
我们来看一个简单的例子:
我们创建了我们的ArrayList作为一个静态字段-即使在完成了用于计算的JVM进程的生命周期之后,这些静态字段也永远不会被JVM垃圾收集器收集。我们还调用了Thread.sleep(10000)来允许GC执行完整的集合,并尝试回收可以回收的所有内容。
我们来运行测试并使用我们的分析器分析JVM:
注意,一开始,所有的记忆当然是免费的。
然后,在短短2秒钟内,迭代过程就会运行并完成-将所有内容加载到列表中(当然这取决于运行测试的机器)。
之后,触发一个完整的垃圾收集循环,并继续执行测试,以允许此循环时间运行并完成。如您所见,列表不被回收,并且内存消耗不会下降。
我们现在看到完全相同的例子,只有这一次,ArrayList不被静态变量引用。相反,它是一个被创建,使用然后被丢弃的局部变量:
一旦该方法完成工作,我们将在下面的图像上观察主要的GC集合,大约第五十秒。
请注意,GC现在可以回收JVM使用的一些内存。
如何预防
现在您了解了这种情况,当然有办法阻止它发生。
首先,我们需要密切关注静电的使用;将任何集合或重对象声明为静态,将其生命周期与JVM本身的生命周期相关联,并使整个对象图形不可能收集。
我们还需要了解一般的集合-这是一种常用的方式,无意中持续参考超过我们需要的。
2.2。在LongString上调用String.intern()
经常导致内存泄漏的第二组方案涉及到字符串操作-特别是String.intern()API。
我们来看一个简单的例子:
在这里,我们只是尝试将大文本文件加载到运行的内存中,然后返回使用的规范表单。intern()。
该实习生API将会把STR在JVM的内存池字符串-它无法采集-又一次,这将导致GC是无法腾出足够的内存:
我们可以清楚地看到,在第一个15秒JVM是稳定的,那么我们加载文件和JVM执行垃圾收集(第20秒)。
最后,调用了str.intern(),这会导致内存泄漏-表示高堆内存使用的稳定行,永远不会被释放。
如何预防
请记住,internedString对象存储在PermGen空间中-如果我们的应用程序旨在对大字符串执行大量操作,我们可能需要增加永久代码的大小:
第二个解决方案是使用Java8,其中PermGen空间由Metaspace替代,当在Strings上使用intern时不会导致任何OutOfMemoryError:
最后,还有几个选项可以避免字符串上的.intern()API。
2.3。未封闭的流
忘记关闭流是一个非常常见的情况,当然,大多数开发人员可以关联流。当自动关闭所有类型的流的能力被引入到try-with-resource子句中时,Java7中部分删除了该问题。
为什么部分因为在尝试,有资源的语法是可选的:
让我们看一下从URL中加载大文件时应用程序的内存:
我们可以看到,堆的使用量随着时间逐渐增加-这是由于不关闭流引起的内存泄漏的直接影响。
如何预防
我们总是需要记住手动关闭流,或者使用Java8中引入的自动关闭功能:
在这种情况下,BufferedReader将在try语句的结尾自动关闭,而不需要在显式的finally块中关闭它。
2.4。未关闭的连接
这种情况与前一种情况非常相似,处理未关闭连接(例如数据库,FTP服务器等)的主要区别。再次,不正确的实施可能会造成很大的伤害,导致内存问题。
我们来看一个简单的例子:
该URLConnection的仍然是开放的,其结果是,可以预见,内存泄漏:
注意垃圾收集器如何无法释放未使用但引用的内存。情况在第一分钟后立即清除-GC操作的数量迅速减少,导致堆内存使用增加,从而导致OutOfMemoryError。
如何预防
这里的答案很简单-我们需要始终以严谨的方式关闭联系。
2.5。将没有hashCode()和equals()的对象添加到HashSet中
可能导致内存泄漏的一个简单但非常常见的示例是使用HashSet与缺少其hashCode()或equals()实现的对象。
具体来说,当我们开始将重复的对象添加到一个集合中时,它只会增长,而不是忽略重复的对象。一旦添加,我们也将无法删除这些对象。
我们创建一个没有equals或者hashCode的简单类:
现在,我们来看一下这个场景:
这个简单的实现将导致运行时的以下情况:
注意垃圾收集器如何停止在大约1:40左右回收内存,并注意到内存泄漏;GC集合的数量立即下降了近四倍。
如何预防
在这些情况下,解决方案很简单-提供hashCode()和equals()实现至关重要。
这里值得一提的工具是ProjectLombok-这通过注释提供了大量的默认实现,例如@EqualsAndHashCode。
3.如何在您的应用程序中查找泄漏源
诊断内存泄漏是一个漫长的过程,需要大量的实践经验,调试技能和应用程序的详细知识。
让我们看看哪些技术可以帮助您,除了标准的分析。
3.1。详细垃圾收集
识别内存泄漏的最快方法之一是启用详细的垃圾回收。
通过将-verbose:gc参数添加到我们应用程序的JVM配置中,我们启用了一个非常详细的GC跟踪。汇总报告显示在默认错误输出文件中,这可以帮助您了解如何管理内存。
3.2。做剖析
第二种技术是我们在本文中使用的技术-这是分析。最流行的分析器是VisualVM-这是开始移动过去的命令行JDK工具并进入轻量级分析的好地方。
在本文中,我们使用另一个Profiler-YourKit-与VisualVM相比,它具有一些其他更高级的功能。
3.3。查看您的代码
最后,这比处理内存泄漏的特定技术更为普遍的好做法。
简单地说-仔细检查您的代码,定期进行代码审查,并善用静态分析工具,帮助您了解代码和系统。
结论
在本教程中,我们对JVM中的内存泄漏情况进行了实际的了解。了解这些情况如何发生是处理这些情况的第一步。
然后,当发生泄漏时,使用技术和工具真正了解运行时发生的情况,这一点也是至关重要的。静态分析和仔细的代码重点审核只能做得如此之多,而且在一天结束的时候,运行时将会显示代码中不能立即识别的更复杂的泄漏。
最后,泄漏可能非常难以发现和复制,因为其中许多只发生在强烈的负载下,这通常发生在生产中。这就是您需要超越代码级分析并在两个主要方面进行工作-繁殖和早期检测。
重现内存泄漏的最好和最可靠的方法是通过一套良好的性能测试来尽可能地模拟生产环境的使用模式。
和早期检测是其中一个坚实的性能管理解决方案,甚至是早期检测解决方案可以使显著差异,因为它是有必要的洞察到生产应用程序的运行时的唯一途径。
本教程的完整实现可以在GitHub上找到。这是一个基于Maven的项目,所以它可以简单地导入和运行。
以上就是极悦注册机构小编介绍的“Java教程:在Java应用程序中发生内存泄漏”的内容,希望能够帮助到大家,更多精彩内容请继续关注极悦注册机构官网,每天会有精彩内容分享与你。
0基础 0学费 15天面授
Java就业班有基础 直达就业
业余时间 高薪转行
Java在职加薪班工作1~3年,加薪神器
工作3~5年,晋升架构
提交申请后,顾问老师会电话与您沟通安排学习