我正在玩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.Class
,java.lang.String
和[Ljava.lang.Object
。我也清楚了258 java.lang.Integer
个对象:这是Integer
缓存。
但是java.lang.reflect.Field
? Hashtable
?很多StringBuilder
s? 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。
答案 0 :(得分:21)
您可以通过使用-XX:+TraceBytecodes
标志运行应用程序来自行找到答案
此标志位于debug builds of HotSpot JVM。
下面是详细的Flame Graph(可点击的SVG),显示了分配对象来自的堆栈跟踪。
就我而言,启动分配的主要来源是
P.S。 The script用于从TraceBytecodes输出生成Flame Graph。
答案 1 :(得分:3)
有很多维护数据结构。例如。每个初始化的JVM都有system properties,这是Hashtable
的子类型,因此解释了Hashtable.Entry
个实例。
此外,像java.lang.Character
这样的核心类知道所有字符的Unicode属性,你也看到Locale
- 你的统计数据中的特定类,因为这些类必须在启动时正确初始化。这些示例如此有趣的原因是,它们是从文件或嵌入资源加载这些信息,因此它们的初始化涉及I / O和缓存机制,这些机制在输出中看到了它们。
此外,在启动过程中创建的其他对象可能尚未进行垃圾回收。有很多操作,比如处理类路径和由它指定的jar文件或解析命令行选项,这些操作比最后将执行的“Hello World”程序更复杂。请注意,您可以创建堆转储而不仅仅是直方图,这样您就可以看到谁拥有对现有对象的引用。
答案 2 :(得分:2)
检查工具是否加载其他类
我尝试了以下程序:
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]
所以这将是基线。当我现在在该文件上使用jstack
或jmap
并检查详细的类加载消息时,我可以看到它是否引入了新类(当然不确定实例)。
使用jstack -l
和jstack
,会加载以下额外的一个类:
[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 -F
或jstack -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
对于带有和不带-F选项的jmap -dump:format=b,file=ignore.hprof
以及带有和不带live
标志的情况,情况也是如此。
为了完整起见,如果我使用 jvisualvm 或 jconsole ,它将始终为线程,堆和应用程序快照触发大量JMX类加载。很可能是因为它总是打开流程的仪表板。
探索堆内容
现在我们已经建立了这个,我看了一下MAT的jmap -dump:format=b
(非实时,非强制)堆转储,寻找你感兴趣的Fields:
MAT 无法访问的对象直方图(显示在堆中找到但未连接到任何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
目前没有单一的实时Field
实例可见MAT,只有非常有限的Class
个实例。这看起来很像.hprof或MAT问题:类实例似乎没有在堆转储中显示它们的任何字段。我认为它们应该由Class#reflectionData : SoftReference<ReflectionData<T>>
引用(!)引用,但我认为这应该在堆转储中可见,而不会丢失115个字段。 (实时堆中没有Class$ReflectionData
,无法到达的组织中有Class$ReflectionData
。这可以很好地适应115 Fields。
(我想我会回来查看Serviceability-dev @ openjdk。这不适合评论,所以这是一个不完整的答案,但我打算加强它。)