Java JAR 内存使用 VS 类文件内存使用
Posted
技术标签:
【中文标题】Java JAR 内存使用 VS 类文件内存使用【英文标题】:Java JAR memory usage VS class file memory usage 【发布时间】:2015-06-26 17:54:18 【问题描述】:我最近将我的大型 Java 应用程序更改为以 JAR 而不是单个类文件的形式交付。我有 405 个 JARS,其中包含 5000 个类文件。我的问题是,当我将程序作为 JAR(类路径是获取所有 JAR 的通配符)运行时,Java 将不断使用越来越多的内存。我已经看到内存超过 2GB 并且似乎 Java 没有进行世界范围内的垃圾收集来保持较低的内存。如果我对分解的 JAR(仅类文件)运行完全相同的程序,Java 的内存使用率会保持低得多(
在类路径中有 JAR 文件
类路径中的类文件
编辑:我接受了@K Erlandsson 的回答,因为我认为这是最好的解释,而这只是 Java 的一个丑陋怪癖。感谢每一个人(尤其是@K Erlandsson)的帮助。
【问题讨论】:
这是堆使用量还是总体占用空间?它是否在 Web 容器中运行?它有自定义类加载机制吗? 我认为我正在查看堆/整体。我通过 jconsole 查看了内存使用情况,它不断增长堆。我也可以通过 Windows 任务管理器查看它,并且内存(私人工作集)不断增长。我没有使用自定义类加载机制。这是一个独立的 java 进程(没有 tomcat 等),从命令行运行。 如果堆在增长,我可能会在它膨胀时对其进行快照(例如visualvm
)并分析它。
很难从远处进行诊断,但是如果内存足够(并且-Xmx
足够高),full GC 的发生频率就会大大降低。很可能多次打开所有这些 JAR 文件会留下大量垃圾,而无需清理应用程序就会不断增长。最终会发生完整的 GC 并收集所有垃圾。如果是这种情况,这是一个很好的例子,用-Xmx
保持低最大堆是一件好事。
这是个问题吗? JVM 将在需要时进行垃圾收集,这就是为什么当您将堆限制为 128MB 时它可以正常工作的原因。但是如果你有一个大堆,而且它还没有全部用完,那么 JVM 没有停止它正在做的事情来做一个大的垃圾收集这一事实肯定是一件好事,而不是一件坏事,对吧?
【参考方案1】:
首先要注意的是,堆上完全使用了多少内存并不是很有趣,因为大部分已使用的内存可能是垃圾,将被下一次 GC 清除。
您需要关注的是 live 对象使用了多少堆。您在评论中写道:
我不知道这是否重要,但如果我使用 jvisualvm.exe 强制 GC (标记扫描)堆内存使用将下降清除几乎所有 堆内存。
这很重要。 很多。这意味着当您在使用 jar 时看到更高的堆使用率时,您会看到更多的 垃圾,而不是活动对象消耗的更多内存。当你执行 GC 时,垃圾被清除,一切都很好。
从 jar 文件加载类会比从类文件加载暂时消耗更多的内存。需要打开、查找和读取 jar 文件。这比简单地打开特定的.class
文件并读取它需要更多的操作和更多的临时数据。
由于大部分堆使用量都由 GC 清除,因此这种额外的内存消耗不是您需要非常关心的。
你也写:
Java 将不断使用越来越多的内存。我看过记忆 go > 2GB,看起来 Java 并没有做停止世界的垃圾 集合以降低内存。
这是典型的行为。 GC 仅在 JVM 认为有必要时运行。 JVM 将根据内存行为对此进行调整。
编辑:现在我们看到了您的 jConsole 图像,我们看到了已提交堆内存的差异(250 mb vs 680 mb)。已提交堆是堆的实际大小。这会有所不同(取决于您使用 -Xmx
设置的内容),具体取决于 JVM 认为将为您的应用程序带来最佳性能的内容。但是,它大部分会增加,几乎不会减少。
对于 jar 情况,JVM 为您的应用程序分配了更大的堆。可能是由于在初始类加载期间需要更多内存。然后 JVM 认为更大的堆会更快。
当您拥有更大的堆、更多提交的内存时,在运行 GC 之前就有更多的内存可供使用。这就是为什么您会看到两种情况下内存使用量的差异。
底线:您看到的所有额外使用都是垃圾,而不是活动对象,为什么您不需要担心这种行为,除非您遇到实际问题,因为内存将被回收下一个 GC。
【讨论】:
奇怪的是(我很快就会有 jconsole.exe 图片)JAR 类路径导致内存保持与类文件不同。对我来说,JAR 最初会导致更多垃圾确实是有道理的,但是当类类路径使用其中的一小部分( @Adam 但不仅是您在这两种情况下看到不同的 gc 行为吗?初始爆发将以不同方式调整 gc。 类文件垃圾收集看起来更像我预期的加速,收集回来开始然后重新开始。 JAR 只是随着较小的垃圾收集而不断增长,但影响不大。 @Adam 当您使用 jar 时,提交的堆可能更大。 jvm 会根据需要增加已提交的部分,并且在 jar 案例中可能会增加更多。但是如果没有 gc 日志或 jstat -gc 输出等更多信息,很难猜测 是的,我同意基本内存量肯定会有所不同,但我不明白(正如您希望在我最新编辑的图片中看到的那样)为什么 JAR 的内存会 保持比类文件运行的更大。【参考方案2】:从类路径加载资源是很常见的。当资源源自 jar 文件时,URL 对象将保留对 jar 文件条目的引用。这可能会增加一些内存消耗。可以通过禁用默认 url 缓存来禁用此缓存。
禁用默认 URL 缓存的 API 相当尴尬:
public static void disableUrlConnectionCaching()
// sun.net.www.protocol.jar.JarURLConnection leaves the JarFile instance open if URLConnection caching is enabled.
try
URL url = new URL("jar:file://valid_jar_url_syntax.jar!/");
URLConnection urlConnection = url.openConnection();
urlConnection.setDefaultUseCaches(false);
catch (MalformedURLException e)
// ignore
catch (IOException e)
// ignore
在应用程序启动时禁用默认 URL 缓存。
Tomcat 已经默认禁用 URL 缓存,因为它还会导致文件锁定问题并阻止更新正在运行的应用程序中的 jar 文件。
https://github.com/apache/tomcat/blob/5bbbcb1f8ca224efeb8e8308089817e30e4011aa/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java#L408-L423
【讨论】:
这段代码看起来会禁用将来加载的 JAR 的 URL 缓存,对吧?如果 JAR 在类路径上并且(我假设)已经缓存,这会起作用吗? 是的,就是这样。这仅在您的应用程序使用 ClassLoader/Class.getResource 从类路径加载资源时才有帮助。以上是关于Java JAR 内存使用 VS 类文件内存使用的主要内容,如果未能解决你的问题,请参考以下文章