【问题标题】:Why does my Oracle JVM create all these objects for a simple 'Hello World' program?为什么我的 Oracle JVM 会为一个简单的“Hello World”程序创建所有这些对象?
【发布时间】:2017-06-02 16:12:51
【问题描述】:

我在玩jmap,发现简单的“Hello World”Java 程序创建了数千个对象。这是 Oracle JVM 更新 131 在启动时创建的对象的截断列表:

 num     #instances         #bytes  class name
----------------------------------------------
   1:           402        4903520  [I
   2:          1621         158344  [C
   3:           455          52056  java.lang.Class
   4:           194          49728  [B
   5:          1263          30312  java.lang.String
   6:           515          26088  [Ljava.lang.Object;
   7:           115           8280  java.lang.reflect.Field
   8:           258           4128  java.lang.Integer
   9:            94           3760  java.lang.ref.SoftReference
  10:           116           3712  java.util.Hashtable$Entry
  11:           126           3024  java.lang.StringBuilder
  12:             8           3008  java.lang.Thread
  13:            74           2576  [Ljava.lang.String;
  14:            61           1952  java.io.File
  15:            38           1824  sun.util.locale.LocaleObjectCache$CacheEntry
  16:            12           1760  [Ljava.util.Hashtable$Entry;
  17:            53           1696  java.util.concurrent.ConcurrentHashMap$Node
  18:            23           1472  java.net.URL
  19:            14           1120  [S
  20:             2           1064  [Ljava.lang.invoke.MethodHandle;
  21:             1           1040  [Ljava.lang.Integer;
  22:            26           1040  java.io.ObjectStreamField
  23:            12           1024  [Ljava.util.HashMap$Node;
  24:            30            960  java.util.HashMap$Node
  25:            20            800  sun.util.locale.BaseLocale$Key

我知道 JVM 从 JAR 文件中加载类并期望看到 java.lang.Classjava.lang.String[Ljava.lang.Object。 258 个java.lang.Integer 对象也很清楚,这就是Integer 缓存。

但是java.lang.reflect.Field? Hashtable?很多StringBuilders? java.util.concurrent.ConcurrentHashMap?这是哪里来的?

程序很简单:

public class Test {
    public static void main(String[] args) throws IOException {
        System.out.println("Hello world");
        System.in.read();
    }
}

JVM 详细信息:

java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Ubuntu 16.04。

【问题讨论】:

  • 一个 java.lang.Class 有 java.lang.Fields,那为什么没有 Field 实例呢? Hashtable 在内部被大量使用,在你不直观地期望它的地方。由于程序如此简单,垃圾收集器可能甚至没有运行一次,所以您会看到在 VM 启动期间创建的每个单个对象。

标签: java jvm


【解决方案1】:

您可以通过运行带有-XX:+TraceBytecodes 标志的应用程序自己找到答案。
此标志在debug builds of HotSpot JVM 中可用。

这里是详细的火焰图(可点击的 SVG),显示了分配对象的来源。

就我而言,启动分配的主要来源是

  • URLClassLoader 和扩展类加载器
  • 语言环境缓存
  • UsageTrackerClient
  • MetaIndex registry
  • 系统属性
  • 字符集初始化

附: The script 用于从 TraceBytecodes 输出中生成 Flame Graph

【讨论】:

  • 哇,这是一个很酷的直方图。信息量少一点(但仍然通过观察顺序给出了一些关于原因的胶水)是使用-verbose:class
  • 再次感谢!这个答案要好得多。
  • 顺便说一句:我认为字段构造函数没有出现在字节码跟踪中的一个原因是因为它们是由 Class#getDeclaredFields0() 在本机代码中生成的。
  • @eckes 这是真的。可以使用 JVM TI VMObjectAlloc 事件跟踪 JVM 内部分配的对象。为简单起见,我省略了这一点,因为 getDeclaredFields0 已经给出了这些对象的来源。
  • 我在this answer中总结了跟踪对象分配的方法。
【解决方案2】:

有很多维护数据结构。例如。每个初始化的 JVM 都有这些 system properties,它是 Hashtable 的子类型,因此解释了 Hashtable.Entry 实例。

此外,像 java.lang.Character 这样的核心类知道所有字符的 Unicode 属性,此外,您会在统计信息中看到 Locale 特定类,因为这些类必须在启动时正确初始化。这些示例之所以如此有趣,是因为它们从文件或嵌入式资源中加载这些信息,因此它们的初始化涉及 I/O 和缓存机制,您可以在输出中看到其工件。

另外,在启动过程中创建的其他对象可能还没有被垃圾回收。有很多操作,比如处理类路径和它指定的jar文件或者解析命令行选项,这些操作比最后执行的“Hello World”程序要复杂。请注意,您可以创建堆转储而不仅仅是直方图,这样您就可以看到谁持有对现有对象的引用。

【讨论】:

  • 谢谢。几十年前Hashtable不是已经过时了吗:)?不知道为什么还在用?
  • 那是兼容性。 System.getProperties() 已声明为返回类型为java.util.Properties,它是java.util.Hashtable 的子类。无论是返回类型还是类层次结构,都不能在不破坏向后兼容性的情况下进行更改。它不是唯一拥有这种遗产的地方。
  • 你关于jmap 的观点很奇怪。只有jmap 的客户端是用Java 实现的——它在不同的进程中运行并且不影响目标JVM 中加载的类。在目标端,它使用 HotSpot JVM 的 AttachListener / DiagnosticCommand API。收集和打印类直方图的代码是用 C++ 编写的,不会创建单个 Java 对象。
  • @eckes Dynamic Attach Mechanism 是 JVM 内置功能,与 RMI 无关。
  • @eckes:我可以通过使用本地 JMX bean 来避免附加,但这仍然可能会加载 JMX 相关的类,而这些类在普通启动时不会加载。顺便说一句,你混淆了jmapjstack
【解决方案3】:

检查工具是否加载其他类

我尝试了以下程序:

package test;
public class MainSleep {
    public static void main(String[] args) throws InterruptedException {
        synchronized (MainSleep.class) {
            MainSleep.class.wait(5*1000);
        }
    }
}

当我运行它时:

"c:\Program Files\Java\jdk1.8.0_131\bin\java" \
  -verbose:class -cp target\classes test.MainSleep

我收到详细的类加载消息,然后暂停 5 秒,然后关机确实加载了更多类:

...
[Loaded sun.misc.PerfCounter from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.misc.Perf$GetPerfAction from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.misc.Perf from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.misc.PerfCounter$CoreCounters from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.nio.ch.DirectBuffer from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.nio.MappedByteBuffer from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.nio.DirectByteBuffer from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.nio.LongBuffer from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.nio.DirectLongBufferU from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.security.PermissionCollection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.security.Permissions from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.net.URLConnection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.net.www.URLConnection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.net.www.protocol.file.FileURLConnection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded sun.net.www.MessageHeader from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.io.FilePermission from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.io.FilePermission$1 from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.io.FilePermissionCollection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.security.AllPermission from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.security.UnresolvedPermission from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.security.BasicPermissionCollection from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded test.MainSleep from file:/D:/ws/BIS65/test-java8/target/classes/]
[Loaded sun.launcher.LauncherHelper$FXHelper from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.Void from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
...
[Loaded java.lang.Shutdown from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from c:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar]

所以这将是基线。当我现在在该文件上使用 jstackjmap 并检查详细的类加载消息时,我可以查看它是否引入了新类(当然不确定实例)。

使用jstack -ljstack,将加载以下额外的一个类:

[Loaded java.lang.Class$MethodArray from
[Loaded java.lang.Void from
[Loaded java.util.concurrent.locks.AbstractOwnableSynchronizer ...
[Loaded java.lang.Shutdown from
[Loaded java.lang.Shutdown$Lock from

使用jstack -Fjstack -m 没有(!)加载额外的类:

[Loaded java.lang.Class$MethodArray from
[Loaded java.lang.Void from
[Loaded java.lang.Shutdown from
[Loaded java.lang.Shutdown$Lock from

jmap-clstat-finalizerinfo-heap-histo-histo:live 均未加载其他类:

[Loaded java.lang.Class$MethodArray from
[Loaded java.lang.Void from
[Loaded java.lang.Shutdown from
[Loaded java.lang.Shutdown$Lock from

jmap -dump:format=b,file=ignore.hprof 使用和不使用 -F 选项以及使用和不使用 live 标志都是如此。

为了完整起见,如果我使用 jvisualvmjconsole,它总是会为线程、堆和应用程序快照触发大量 JMX 类加载。很可能是因为它总是打开进程的仪表板。

探索堆内容

既然我们已经建立了这个,我用 MAT 查看了jmap -dump:format=b(非实时、非强制)堆转储,寻找您感兴趣的字段:

MATunreachable objects histogram(显示在堆中找到但未连接到任何 GC 根的实例,基本上都是尚未收集的垃圾)有 3038 个对象,前 10 个:

Class Name                               | Objects | Shallow Heap
------------------------------------------------------------------
char[]                                   |   1.026 |      113.848
java.lang.String                         |     599 |       14.376
int[]                                    |     423 |        7.664
java.lang.Object[]                       |     220 |       14.192
java.lang.StringBuilder                  |     137 |        3.288
java.lang.reflect.Field                  |     115 |        8.280
java.lang.ProcessEnvironment$CheckedEntry|      66 |        1.056
java.io.File                             |      59 |        1.888
java.lang.Class                          |      32 |            0
java.lang.StringBuffer                   |      30 |          720

目前在 MAT 中没有可见的单个实时 Field 实例,并且只有非常有限的 Class 实例。这看起来很像 .hprof 或 MAT 问题:Class 实例似乎没有在堆转储中显示它们的任何字段。我认为它们应该被Class#reflectionData : SoftReference<ReflectionData<T>> 轻轻引用(!),但我认为这应该在堆转储中可见并且不会丢失 115 个字段。 (在 live heap 中没有 Class$ReflectionData,在 unreachable histo 中没有 14 Class$ReflectionData。这可以很好地适应 115 Fields。

(我想我会在 Serviceability-dev@openjdk 上查看。这不适合评论,所以这是一个不完整的答案,但我打算加强它)。 p>

【讨论】:

猜你喜欢
  • 2017-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
相关资源
最近更新 更多