Java JAR 内存使用 VS class 文件内存使用

Java JAR memory usage VS class file memory usage

我最近将我的大型 Java 应用程序更改为以 JAR 形式而不是单个 class 文件形式交付。我有 405 个 JARS,其中包含 5000 class 个文件。我的问题是,当我 运行 我的程序作为 JAR(classpath 是获取所有 JAR 的通配符)时 Java 将不断使用越来越多的内存。我已经看到内存超过 2GB,而且 Java 似乎没有执行停止世界垃圾收集以降低内存。如果我 运行 完全相同的程序针对展开的 JAR(仅 class 文件),Java 的内存使用率会低得多(< 256MB)并保持在那里。这发生在 Windows 7 (x64) 和 Windows 服务器 (x64) 上的 Oracle Java 8 中。为什么将我的应用程序打包为 JAR 会更改内存配置文件?我也有 运行 这个程序很长时间了,因为 JAR 的最大内存限制为 128MB,没有问题,所以我没有内存泄漏。

JAR 文件在 classpath 中

在 class 路径中有 class 个文件

编辑:我接受了@K Erlandsson 的回答,因为我认为这是最好的解释,而这只是 Java 的一个丑陋的怪癖。感谢大家(尤其是@K Erlandsson)的帮助。

首先要注意的是,堆上完全使用了多少内存在任何时候都不是很有趣,因为大部分使用的内存可能是垃圾,将被下一次 GC 清除。

这是您需要关注的 live 对象使用了多少堆。您在评论中写道:

I don't know if this matters, but if I use jvisualvm.exe to force a GC (mark sweep) the heap memory usage will drop clearing almost all the heap memory.

这很重要。 很多。这意味着当您在使用 jar 时看到更高的堆使用率时,您会看到更多 垃圾 ,而不是活动对象消耗的更多内存。当你执行 GC 时,垃圾被清除,一切都很好。

从 jar 文件加载 classes 会暂时比从 class 文件加载它们消耗更多的内存。需要打开、查找和读取 jar 文件。与简单地打开特定 .class 文件并读取它相比,这需要更多的操作和更多的临时数据。

由于大部分堆使用被 GC 清除,因此您不需要非常关心这种额外的内存消耗。

你还写:

Java will continually use more and more memory. I have seen the memory go > 2GB and it seems like Java is not doing stop-the-world garbage collections to keep the memory lower.

这是典型的行为。 GC 仅在 JVM 认为有必要时运行。 JVM 将根据内存行为对此进行调整。

编辑: 现在我们看到了您的 jConsole 图像,我们看到了 committed 堆内存的不同(250 MB 与 680 MB)。提交堆是堆的实际大小。这将有所不同(取决于您使用 -Xmx 设置的值),具体取决于 JVM 认为将为您的应用程序产生最佳性能的内容。但是,它主要会增加,几乎不会减少。

对于 jar 情况,JVM 已为您的应用程序分配了更大的堆。可能是由于初始 class 加载期间需要更多内存。然后 JVM 认为更大的堆会更快。

当您拥有更大的堆、更多的已提交内存时,在 运行 GC 之前有更多的内存可供使用。这就是为什么您会看到两种情况下内存使用情况的差异。

底线:您看到的所有额外用法都是垃圾,而不是实时对象,为什么您不需要担心这种行为,除非您有实际问题,因为内存将在下一次 GC 时回收。

从类路径加载资源是很常见的。当资源源自 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