Why is there internal fragmentation in a Java object even if every field is 4-byte aligned?
Asked Answered
A

2

7

Intro:

I used the JOL (Java Object Layout) tool to analyze the internal and external fragmentation of Java objects for research purpose.

While doing so, I stumbled across the following:

x@pc:~/Util$ java -jar jol-cli-0.9-full.jar internals sun.reflect.DelegatingClassLoader
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Instantiated the sample instance via sun.reflect.DelegatingClassLoader(java.lang.ClassLoader)

sun.reflect.DelegatingClassLoader object internals:
 OFFSET  SIZE                                     TYPE DESCRIPTION                               VALUE
      0     4                                          (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                                          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                                          (object header)                           3b 13 00 f8 (00111011 00010011 00000000 11111000) (-134212805)
     12     4                    java.lang.ClassLoader ClassLoader.parent                        null
     16     4   java.util.concurrent.ConcurrentHashMap ClassLoader.parallelLockMap               null
     20     4                            java.util.Map ClassLoader.package2certs                 (object)
     24     4                         java.util.Vector ClassLoader.classes                       (object)
     28     4           java.security.ProtectionDomain ClassLoader.defaultDomain                 (object)
     32     4                            java.util.Set ClassLoader.domains                       (object)
     36     4                        java.util.HashMap ClassLoader.packages                      (object)
     40     4                         java.util.Vector ClassLoader.nativeLibraries               (object)
     44     4                         java.lang.Object ClassLoader.assertionLock                 (object)
     48     4                            java.util.Map ClassLoader.packageAssertionStatus        null
     52     4                            java.util.Map ClassLoader.classAssertionStatus          null
     56     8                                          (alignment/padding gap)                  
     64     1                                  boolean ClassLoader.defaultAssertionStatus        false
     65     7                                          (loss due to the next object alignment)
Instance size: 72 bytes
Space losses: 8 bytes internal + 7 bytes external = 15 bytes total

Question:

What's bothering me in this case is that every field is 4 byte aligned (see OFFSET column), but still at offset 56 an alignment gap gets added (56 8 (alignment/padding gap)). I did the same test in Java 9, and there the object layout changed a little bit, the alignemnt/padding gap still exists, but is even 12 bytes large.

Why is this happening? And why is it 8 bytes large, all other objects I saw are 4-byte aligned interally? I couldn't find an explanation myself.

My system:

openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-0ubuntu0.16.04.2-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)

Using default settings (ParallelOldGC with compressed oops)

Agglutinate answered 29/11, 2017 at 8:41 Comment(4)
@Oleg: No, this is not a duplicate, since that would be external fragmentation, but I am talking about internal fragmentation. Also, it would not explain a 12 byte internal gap in Java 9.Agglutinate
@Oleg Are you now talking about bits or bytes? This objects has internal AND external fragmentation. External fragmentation to be 8-byte aligend since it is a 64-bit VM, I am fine with that. But I don't understand the internal fragmentation here.Agglutinate
@Oleg: I really think you mixed up bytes and bits and stuff. But I already got the correct answer by apangin.Agglutinate
Yes, I did, sorry.Elapse
S
8

Sometimes HotSpot injects internal VM-specific fields that are not visible on Java level. In ClassLoader case this is a pointer to ClassLoaderData:

#define CLASSLOADER_INJECTED_FIELDS(macro)                            \
  macro(java_lang_ClassLoader, loader_data,  intptr_signature, false)

This is one field of intptr_t type, i.e. exactly 8 bytes long.

Here is how HotSpot sources describe ClassLoaderData:

// A ClassLoaderData identifies the full set of class types that a class
// loader's name resolution strategy produces for a given configuration of the
// class loader.
// Class types in the ClassLoaderData may be defined by from class file binaries
// provided by the class loader, or from other class loader it interacts with
// according to its name resolution strategy.
//
// Class loaders that implement a deterministic name resolution strategy
// (including with respect to their delegation behavior), such as the boot, the
// extension, and the system loaders of the JDK's built-in class loader
// hierarchy, always produce the same linkset for a given configuration.
//
// ClassLoaderData carries information related to a linkset (e.g.,
// metaspace holding its klass definitions).
// The System Dictionary and related data structures (e.g., placeholder table,
// loader constraints table) as well as the runtime representation of classes
// only reference ClassLoaderData.
//
// Instances of java.lang.ClassLoader holds a pointer to a ClassLoaderData that
// that represent the loader's "linking domain" in the JVM.
//
// The bootstrap loader (represented by NULL) also has a ClassLoaderData,
// the singleton class the_null_class_loader_data().
Stadium answered 29/11, 2017 at 9:5 Comment(3)
Thank you @apangin. I am always surprised again on how you know all that JVM / HotSpot stuff. Now it all make total sense.Agglutinate
@MarkusWeninger hard to beat a guy that worked at SUN for so many years and is one of the most accurate users for the JVM tag...Fridafriday
@MarkusWeninger and btw that's not 4 bytes aligned, 4 bytes is the size of the reference in your exampleFridafriday
F
1

You have given me a perfect opportunity to post something that I have seen interesting too (this should be a comment, but it's too long):

  System.out.println(ClassLayout.parseInstance(Class.class).toPrintable());

Running this will give you:

 OFFSET  SIZE            TYPE DESCRIPTION                    VALUE
  0     4                 (object header)                01 27 2b fd (00000001 00100111 00101011 11111101) (-47503615)
  4     4                 (object header)                5f 00 00 00 (01011111 00000000 00000000 00000000) (95)
  8     4                 (object header)                df 03 00 f8 (11011111 00000011 00000000 11111000) (-134216737)
 12     4     Constructor Class.cachedConstructor        null
 16     4           Class Class.newInstanceCallerCache   null
 20     4          String Class.name                     (object)
 24     4                 (alignment/padding gap)        N/A
 28     4   SoftReference Class.reflectionData           (object)
 32     4 ClassRepository Class.genericInfo              null
 36     4        Object[] Class.enumConstants            null
 40     4             Map Class.enumConstantDirectory    null
 44     4  AnnotationData Class.annotationData           (object)
 48     4  AnnotationType Class.annotationType           null
 52     4   ClassValueMap Class.classValueMap            null
 56    32                 (alignment/padding gap)        N/A
 88     4             int Class.classRedefinedCount      0
 92   556                 (loss due to the next object alignment)

Instance size: 648 bytes Space losses: 36 bytes internal + 556 bytes external = 592 bytes total

Or a total space loss of 556 bytes; I found this pretty impressive.

Fridafriday answered 29/11, 2017 at 11:28 Comment(6)
Yeah, I also saw this phenomenon, do you have an explanation for this? I nearly wanted to start a new question on SO for it :PAgglutinate
@MarkusWeninger I don't know the exact details like apangin does - but judging from his answer these will be VM-Specific fields again.Fridafriday
@MarkusWeninger btw it's probably here : hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/… or here : hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/…Fridafriday
JOL relies on Instrumentation.getObjectSize, which in turn calls JVMTI GetObjectSize function. This function actually cheats for java.lang.Class instances - it treats them specially and returns the size of VM's internal representation of Klass rather than the size of Java instance in heap.Stadium
So, JOL thinks java.lang.Class instance takes 600+ bytes (which is the size of VM Klass structure), but in fact Java instance is only 96 bytes long.Stadium
@Stadium very interesting, I was not aware of this. thank u!Fridafriday

© 2022 - 2024 — McMap. All rights reserved.