I was playing around with jmap
and found that simple "Hello World" Java program creates thousands of objects. Here is truncated list of objects Oracle JVM update 131 creates on startup:
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
I know that the JVM loads classes from JAR files and expect to see java.lang.Class
, java.lang.String
and [Ljava.lang.Object
. 258 java.lang.Integer
objects are clear tp me too: this is the Integer
cache.
But java.lang.reflect.Field
? Hashtable
? Many StringBuilder
s? java.util.concurrent.ConcurrentHashMap
? Where does this come from?
The program is pretty simple:
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("Hello world");
System.in.read();
}
}
JVM details:
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.
JVM is a part of JRE (Java Runtime Environment). Java applications are called WORA (Write Once Run Anywhere). This means a programmer can develop Java code on one system and can expect it to run on any other Java-enabled system without any adjustment. This is all possible because of JVM.
Compile it by typing “javac HelloWorld.java” in the terminal window. Execute (or run) it by typing “java HelloWorld” in the terminal window. The below-given program is the most simple program of Java printing “Hello World” to the screen.
After loading the “ .class” file, JVM creates an object of type Class to represent this file in the heap memory. Please note that this object is of type Class predefined in java.lang package.
There is only one method area per JVM, and it is a shared resource. Heap area: Information of all objects is stored in the heap area. There is also one Heap Area per JVM. It is also a shared resource. Stack area: For every thread, JVM creates one run-time stack which is stored here.
You can find the answer yourself by running the application with -XX:+TraceBytecodes
flag.
This flag is available in debug builds of HotSpot JVM.
Here is the detailed Flame Graph (clickable SVG) showing the stack traces where the allocated objects come from.
In my case the main sources of start-up allocations were
P.S. The script used to generate the Flame Graph from TraceBytecodes output.
Checking if tools load additional classes
I tried the following program:
package test;
public class MainSleep {
public static void main(String[] args) throws InterruptedException {
synchronized (MainSleep.class) {
MainSleep.class.wait(5*1000);
}
}
}
When I run it with:
"c:\Program Files\Java\jdk1.8.0_131\bin\java" \
-verbose:class -cp target\classes test.MainSleep
I get verbose class loading messages, then a 5 sec pause and then the shutdown does load even more classes:
...
[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]
So this would be the baseline. When I now use jstack
or jmap
on that file and check for verbose class loading messages I can see if it introduces new classes (not sure about instances of course).
With jstack -l
and jstack
, the following additional one class is loaded:
[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
With jstack -F
or jstack -m
no(!) additional class is loaded:
[Loaded java.lang.Class$MethodArray from
[Loaded java.lang.Void from
[Loaded java.lang.Shutdown from
[Loaded java.lang.Shutdown$Lock from
None of jmap
-clstat
,-finalizerinfo
,-heap
, -histo
or -histo:live
loaded additional classes:
[Loaded java.lang.Class$MethodArray from
[Loaded java.lang.Void from
[Loaded java.lang.Shutdown from
[Loaded java.lang.Shutdown$Lock from
The same is true for jmap -dump:format=b,file=ignore.hprof
with and without the -F option as well as with and without the live
flag.
Just for completeness, if I use jvisualvm or jconsole it will always trigger a lot of JMX class loads for thread, heap and application snapshots. Most likely because it always opens the dashboard for the process.
Exploring Heap Content
So now that we have established this I took a look at the jmap -dump:format=b
(non-live, non-forced) heap dump with MAT, looking for the Fields you have been interested in:
The MAT unreachable objects histogram (which shows instances found in the heap but not connected to any GC root, which is basically all not-yet collected garbage) has 3038 objects, and the top 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
There is currently no single live Field
instance visible with MAT and only very limited of Class
instances. This looks much like a .hprof or MAT problem: the Class instances seems to not show any of their fields in the heap dump. I think they should be softly(!) referenced by Class#reflectionData : SoftReference<ReflectionData<T>>
, but I think this should be visible in the heap dump and not losing 115 fields. (There is no Class$ReflectionData
in the live heap and 14 Class$ReflectionData
in the unreachable histo. That can fit well with 115 Fields.
(I guess I will check back with Serviceability-dev@openjdk on that. This does not fit in a comment, so this is an incomplete answer but I intend to enhance it).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With