2017-06-02 40 views
9

我在玩jmap,發現簡單的「Hello World」Java程序創建了數千個對象。這裏是對象甲骨文JVM更新131在啓動時創建的截斷的列表:爲什麼我的Oracle JVM爲簡單的「Hello World」程序創建所有這些對象?

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 

我知道,從JAR文件的JVM加載類,希望看到java.lang.Classjava.lang.String[Ljava.lang.Object。對象也很清楚:這是Integer緩存。

但是java.lang.reflect.FieldHashtable?許多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

一個java.lang.Class有java.lang.Fields,所以爲什麼不會有Field實例?Hashtable用了很多因爲程序非常簡單,垃圾收集器可能甚至沒有運行過一次,所以你可以看到虛擬機啓動過程中創建的每一個*單個對象 – Durandal

回答

21

您可以通過使用-XX:+TraceBytecodes標誌運行應用程序自己找到答案。
該標誌位於debug builds of HotSpot JVM

下面是詳細的Flame Graph(可點擊的SVG),顯示了分配的對象來自的堆棧軌跡。

JDK start-up allocation

在我的情況下啓動時分配的主要來源是

  • 的URLClassLoader和擴展類加載器
  • 區域設置緩存
  • UsageTrackerClient
  • MetaIndex registry
  • 系統屬性
  • 字符集初始化

P.S. The script用於從TraceBytecodes輸出生成Flame Graph

+2

哇這是一個很酷的直方圖。少一點信息(但仍然通過觀察命令給出一些關於原因的粘合劑)是使用'-verbose:class'。 – eckes

+0

再次感謝!這個答案好多了。 –

+1

BTW:我想爲什麼Field構造函數不會顯示在ByteCode跟蹤中的一個原因是因爲它們是通過Class#getDeclaredFields0()在本機代碼中生成的。 – eckes

3

有很多維護數據結構。例如。每個初始化的JVM都有這些system properties,這是Hashtable的子類型,因此,解釋了Hashtable.Entry實例。

此外,像java.lang.Character這樣的核心類知道所有字符的Unicode屬性,同樣,你在你的統計中看到了Locale特定的類,因爲這些類必須在啓動時正確初始化。使這些示例如此有趣的是,它們正在從文件或嵌入式資源中加載這些信息,因此它們的初始化涉及I/O和緩存機制,這些機制在輸出中看到了它們的工件。

另外,在啓動過程中創建的其他對象可能還沒有被垃圾收集。有很多操作,比如處理它所指定的類路徑和jar文件,或者解析命令行選項,它們比最後要執行的「Hello World」程序更復雜。請注意,您可以創建堆轉儲而不是直方圖,因此您可以看到誰正在持有對現有對象的引用。

+0

謝謝!不是Hashtable已過時幾十年前:)?我想知道爲什麼它仍然被使用? –

+2

這是兼容性。 'System.getProperties()'被聲明爲返回類型爲'java.util.Properties',它是'java.util.Hashtable'的子類。返回類型和類層次結構都不能在不破壞向後兼容性的情況下進行更改。這不是唯一有這種遺產的地方。 – Holger

+0

關於'jmap'的觀點很奇怪。只有'jmap'的客戶端在Java中實現 - 它運行在不同的進程中,並且不影響在目標JVM中加載的類。在目標端,它使用HotSpot JVM的AttachListener/DiagnosticCommand API。收集和打印類直方圖的代碼是用C++編寫的,不會創建單個Java對象。 – apangin

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] 

所以這將是基準。當我現在在該文件上使用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沒有額外的類裝載(!):

無的 jmap -clstat
[Loaded java.lang.Class$MethodArray from 
[Loaded java.lang.Void from 
[Loaded java.lang.Shutdown from 
[Loaded java.lang.Shutdown$Lock from 

-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類負載線程,堆和應用程序快照。很有可能是因爲它總是打開進程的儀表板。

所以,現在,我們已經建立了這個我接過一看jmap -dump:format=b(非實時,非強制)堆與MAT轉儲,尋找字段你一直有興趣研究堆內容

的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和14 Class$ReflectionData無法到達HISTO,這可與115個字段吻合。

(我想我會回來檢查與維修性-dev的@對OpenJDK的,這不適合在一個評論,所以這是一個不完整的答案,但我打算加強它)

+0

這確實很有趣。也許,它連接到一個事實,即它只報告32'java.lang.Class'實例,而不是「400 ......這裏 – Holger

+0

入門主題:http://mail.openjdk.java.net/pipermail/serviceability-dev/2017 - 6月/ 021413.html – eckes

相關問題