Recently, I took some time to read JDK source code, mainly GC and Safepoint related source code, and found that I had a lot of misunderstanding about the underlying principle because I did not read the source code when I read various JVM principle works on the Internet. Sure enough, when 100 people read the outlaws of the Marsh, there are 100 kinds of outlaws of the marsh. Still, a deeper understanding of the source code is needed to better understand the JVM and tune it. This series will cover the various principles of Java GC, combined with the corresponding source code analysis, and the source code address. Because the JVM source code update is still very fast, especially GC, but the basic principles, should remain the same, attached source address, to keep readers abreast of the latest implementation of these principles. At the time of writing, this article was updated with the latest version of the JDK.
Garbage Collection (GC) is an inherent feature of many programming languages, such as Java and Python. GC is a nice feature that lets programmers programming in this language not worry about memory reclamation and reduces the probability of memory leaks and spills.
To understand Java GC, you need to know exactly how Java’s most basic objects are stored in memory. We focus on the HotSpot VIRTUAL machine implementation to elaborate on the object storage structure. First let’s take a look at the concept of OOPs (Ordinary Object Pointers), the main part of the Object header.
1. OOPs, Ordinary Object Pointers
Object pointer implementation, see oop. HPP:
class oopDesc {
private:
volatile markWord _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;
}
Copy the code
As can be seen from the source code, object Pointers include:
1. Mark Word: A set of tags that describe the state of an object, including the default hash value of the object (which is recorded in the tag after the hashCode () method is called if the default hashcode() method is not overridden), the shape of the object (whether it is an array or not), the lock state (lock information such as biased locks), It is worth mentioning that Biased Locking is deprecated in Java 15: Disable and Deprecate Biased Locking), and array length (if the flag shows that this object is an array, describes the array length). The implementation of the tag word consists of just one uintptr_t type, so the size is 4 bytes and 8 bytes respectively on 32-bit and 64-bit virtual machines. Can refer to the source code: markword.hpp. We are only talking here about 64-bit JVMS, which are tagged with 8 bytes.
class markWord {
private:
uintptr_t _value;
}
Copy the code
2. Class Word: A type Word is a pointer to the Class of an object’s implementation. In Java 8, the Metaspace was deprecated, and the Metaspace was introduced.
This pointer may be Compressed, called a Compressed pointer (Compressed OOPs). Object compression takes up 4 bytes when enabled (by default for the JVM) and 8 bytes when disabled
1.1. Specific structure of tag words
For 64-bit virtual machine environments, the tag word size is 8 bytes. First, the tag word structure is given:
(The image above is from:www.cnblogs.com/helloworldc…
Let’s start with the JoL (Java Object Layout) tool and the next few examples to take a step-by-step look at the markup word structure in each state. Add dependencies:
<! -- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core --> <dependency> <groupId>org.openjdk.jol</groupId> < artifactId > jol - core < / artifactId > < version > 0.13 < / version > < / dependency >Copy the code
1.1.1. How many times are hashes computed? Where can I find a hash?
If the class does not override hashcode(), then the implementation of hashcode() is a native method.
@HotSpotIntrinsicCandidate
public native int hashCode();
Copy the code
Synchronizer.cpp: synchronizer.cpp
intptr_t ObjectSynchronizer::FastHashCode(Thread* self, Oop obj) {UseBiasedLocking (UseBiasedLocking) {UseBiasedLocking (UseBiasedLocking) {UseBiasedLocking (UseBiasedLocking) {UseBiasedLocking (obj->mark().has_bias_pattern()) { And next time to get the lock, directly Handle hobj(self, obj) from the light lock; if (SafepointSynchronize::is_at_safepoint()) { BiasedLocking::revoke_at_safepoint(hobj); } else { BiasedLocking::revoke(hobj, self); } obj = hobj(); assert(! obj->mark().has_bias_pattern(), "biases should be revoked by now"); } } while (true) { ObjectMonitor* monitor = NULL; markWord temp, test; intptr_t hash; markWord mark = read_stable_mark(obj); // Cannot be in biased lock state assert(! mark.has_bias_pattern(), "invariant"); If (mark.is_neutral()) {hash = mark.hash(); if (hash ! = 0) {// Return hash if the hash has already been calculated; } hash = get_next_hash(self, obj); // Otherwise, compute a new hash value, this method is analyzed in detail in another article temp = mark.copy_set_hash(hash); // set the hash value test = obj->cas_set_mark(temp, mark); If (test == mark) {// If the update is successful, return hash; } // If the setting fails, lock inflation may have occurred, or multiple threads called hashcode at the same time, } else if (mark.has_monitor()) {monitor = mark.monitor();} else if (mark.has_monitor()) {monitor = mark.monitor(); Header temp = monitor->header(); assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value()); Hashcode hash = temp.hash(); // Use the tag if monitor has a hash and has not continued Async monitor downgrade, see: https://wiki.openjdk.java.net/display/HotSpot/Async+Monitor+Deflation, which is in the Java after 15 began to introduce the new features of the if (hash! = 0) { // It has a hash. // Separate load of dmw/header above from the loads in // is_being_async_deflated(). if (support_IRIW_for_not_multiple_copy_atomic_cpu) { // A non-multiple copy atomic (nMCA) machine needs a bigger // hammer to separate the load above and the loads below. OrderAccess::fence(); } else { OrderAccess::loadload(); } if (monitor->is_being_async_deflated()) { // But we can't safely use the hash if we detect that async // deflation has occurred. So we attempt to restore the // header/dmw to the object's header so that we only retry // once if the deflater thread happens to be slow. monitor->install_displaced_markword_in_object(obj); continue; } return hash; }} else if (self->is_lock_owned((address)mark.locker())) { Temp = mark.displaced_mark_helper(); temp = mark.displaced_mark_helper(); assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value()); hash = temp.hash(); if (hash ! = 0) { // if it has a hash, just return it return hash; } // If the lock is not inflate(self, obj, inflate_cause_hash_code), the lock is inflate() directly. Mark = monitor->header(); assert(mark.is_neutral(), "invariant: header=" INTPTR_FORMAT, mark.value()); hash = mark.hash(); // If it does not have a hash hash = get_next_hash(self, obj); // If it does not have a hash hash = get_next_hash(self, obj); // get a new hash temp = mark.copy_set_hash(hash); // merge the hash into header assert(temp.is_neutral(), "invariant: header=" INTPTR_FORMAT, temp.value()); uintptr_t v = Atomic::cmpxchg((volatile uintptr_t*)monitor->header_addr(), mark.value(), temp.value()); test = markWord(v); If (test! = mark) { // The attempt to update the ObjectMonitor's header/dmw field // did not work. This can happen if another thread managed to // merge in the hash just before our cmpxchg(). // If we add any new usages of the header/dmw field, this code // will need to be updated. hash = test.hash(); assert(test.is_neutral(), "invariant: header=" INTPTR_FORMAT, test.value()); assert(hash ! = 0, "should only have lost the race to a thread that set a non-zero hash"); } // If Async monitor continuing, skip, re-judge // Async monitor continuing, see: https://wiki.openjdk.java.net/display/HotSpot/Async+Monitor+Deflation, New feature introduced after Java 15 if (monitor-> IS_being_async_deflated ()) {// If we detect that async sound has occurred, then we // attempt to restore the header/dmw to the object's header // so that we only retry once if the deflater thread happens // to be slow. monitor->install_displaced_markword_in_object(obj); continue; } } // We finally get the hash. return hash; }}Copy the code
As you can see, hashcode:
- Try to calculate only once, after calculation, for the unlocked object, save in the object Markword Markword.
- All lock states (except light locks) modify and occupy Markword, resulting in the need to cache the calculated HashCode somewhere else, in the case of weight locks in the corresponding Monitor, and in the case of light locks in the pointer to the lock record to which it is pointing.
- For biased locks, there is no hash value, so as long as the hash value is calculated, it will not enter the biased lock state, but directly from the light lock.
- For Async Monitor degradation introduced after JDK 15, it is necessary to read the Hashcode cache of a Monitor object when the process is complete or not begun. For detailed explanation of this feature, see Async Monitor Continuing
The hash value is computed according to the value of the global hashcode variable (set by -xx: hashcode =). The default hash value is calculated using the hashcode=5 method. The default hash value is calculated using the hashcode=5 method
Let’s take a look at these features with a snippet of code and output
public static void main(String[] args) throws Exception { A a = new A(); B b = new B(); System.out.println("------After Initialization------\n" + ClassLayout.parseInstance(a).toPrintable() + "\n" + ClassLayout.parseInstance(b).toPrintable()); System.out.println("a.hashcode: " + a.hashCode()); System.out.println("b.hashcode: " + b.hashCode()); System.out.println("------After call hashcode------\n" + ClassLayout.parseInstance(a).toPrintable() + "\n" + ClassLayout.parseInstance(b).toPrintable()); } public static class A {long d; } public static class B {long d; @Override public int hashCode() { return (int) 5555; }}Copy the code
Output:
------After Initialization------ com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total com.hashjang.jdk.TestObjectAlign$B object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 8a ce 00 20 (10001010 11001110 00000000 00100000) (536923786) 12 4 (alignment/padding gap) 16 8 long B.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total a.hashcode: 2124046270 b.hashcode: 5555 ------After call hashcode------ com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 be 5f 9a (00000001 10111110 01011111 10011010) (-1705001471) 4 4 (object header) 7e 00 00 00 (01111110 00000000 00000000 00000000) (126) 8 4 (object header) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total com.hashjang.jdk.TestObjectAlign$B object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 8a ce 00 20 (10001010 11001110 00000000 00100000) (536923786) 12 4 (alignment/padding gap) 16 8 long B.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes totalCopy the code
As you can see, for class A that does not override the hashcode() method, after calling hashcode(), the hash value exists in the object header. The hash value is 2124046270, converted to binary: 1111110 10011010 01011111 10111110. In Windows, the Header is printed in reverse, so you can see that the hash value is stored in the Header:
00000001 10111110 01011111 10011010 01111110 00000000 00000000 00000000
Other features are detailed in the following section 1.1.3. Bias locks, lightweight locks, and weight locks.
1.1.2. Generational age
Generational age in object header for generational GC. Generational GC is covered in more detail in a later section, but this is just a look at some features.
There are 4 bits to record generational ages, so the maximum is 2^4-1 = 15. Therefore, configure the maximum generation age -xx :MaxTenuringThreshold=n. The value of n cannot be greater than 15 or less than 0. If it’s 0, it goes straight to the old age. If it is 16, it will never enter the old age, which is not in compliance with the JVM specification, so it cannot be greater than 15 (thanks CSDN @jonsonjiao for pointing it out). The default is 15.
In the event of a Young GC, or rather replication in a Survivor region, the generational age of surviving objects is increased by 1. Since the compiler optimizes the code and the system.gc () call does not immediately trigger gc and is Full GC, it may cause the object to age directly, so we can use volatile to help us actually create the object. Avoid compiler optimizations:
static volatile Object consumer; Public static void main(String[] args) throws Exception {Object instance = new Object(); long lastAddr = VM.current().addressOf(instance); for (int i = 0; i < 10000; Long currentAddr = vm.current ().addressof (instance); long currentAddr = vm.current (). if (currentAddr ! = lastAddr) {/ / address changes, print the object structure ClassLayout layout = ClassLayout. ParseInstance (instance); System.out.println(layout.toPrintable()); lastAddr = currentAddr; } for (int j = 0; j < 10000; J++) {// keep creating new objects // because volatile property updates are not optimized by the compiler consumer = new Object(); }}}Copy the code
This can be observed in conjunction with GC logging. For JVM logging configuration, see this article: OpenJDK 11 JVM logging parameters Parsing and Using
First we run the program with this argument -xmx128m -xlog :gc=info.
[0.016s][info][gc] Using G1
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
[2.540s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 24M->1M(128M) 2.600ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 0d 00 00 00 (00001101 00000000 00000000 00000000) (13)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.627s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.273ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 15 00 00 00 (00010101 00000000 00000000 00000000) (21)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.675s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.063ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 1d 00 00 00 (00011101 00000000 00000000 00000000) (29)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.724s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.068ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 25 00 00 00 (00100101 00000000 00000000 00000000) (37)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.772s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.212ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 2d 00 00 00 (00101101 00000000 00000000 00000000) (45)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.821s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.202ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.869s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.143ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 3d 00 00 00 (00111101 00000000 00000000 00000000) (61)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.917s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.313ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 45 00 00 00 (01000101 00000000 00000000 00000000) (69)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[2.969s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.473ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 4d 00 00 00 (01001101 00000000 00000000 00000000) (77)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.021s][info][gc] GC(9) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.283ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 55 00 00 00 (01010101 00000000 00000000 00000000) (85)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.072s][info][gc] GC(10) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.648ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5d 00 00 00 (01011101 00000000 00000000 00000000) (93)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.122s][info][gc] GC(11) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.585ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 65 00 00 00 (01100101 00000000 00000000 00000000) (101)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.173s][info][gc] GC(12) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.130ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 6d 00 00 00 (01101101 00000000 00000000 00000000) (109)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.224s][info][gc] GC(13) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.078ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 75 00 00 00 (01110101 00000000 00000000 00000000) (117)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.273s][info][gc] GC(14) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.135ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 7d 00 00 00 (01111101 00000000 00000000 00000000) (125)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.322s][info][gc] GC(15) Pause Young (Normal) (G1 Evacuation Pause) 75M->1M(128M) 2.467ms
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 7d 00 00 00 (01111101 00000000 00000000 00000000) (125)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 02 00 20 (00000000 00000010 00000000 00100000) (536871424)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[3.404s][info][gc] GC(16) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.556ms
[3.485s][info][gc] GC(17) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.303ms
[3.566s][info][gc] GC(18) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.288ms
[3.647s][info][gc] GC(19) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.317ms
[3.727s][info][gc] GC(20) Pause Young (Normal) (G1 Evacuation Pause) 76M->1M(128M) 0.286ms
Copy the code
As you can see, by the 15th GC, the object is in the old age and the memory address does not change as the Young GC progresses.
Readers can set -xx :MaxTenuringThreshold= to 0 and 16 to see the effect.
1.1.3. Bias lock, lightweight lock, weight lock
Let’s write the test code:
A a = new A(); System.out.println("------After Initialization------\n" + ClassLayout.parseInstance(a).toPrintable()); Println ("------After Lock------\n" +) {system.out. println("------After Lock------\n" + ClassLayout.parseInstance(a).toPrintable()); } System.out.println("------After Released Lock------\n" + ClassLayout.parseInstance(a).toPrintable()); System.out.println("a.hashcode: " + a.hashCode()); System.out.println("------After call hashcode------\n" + ClassLayout.parseInstance(a).toPrintable()); // Since hashcode was called, Println ("------After touchlock ------\n" +) {system.out.println ("------After touchlock ------\n" + ClassLayout.parseInstance(a).toPrintable()); } System.out.println("------After Released Lock------\n" + ClassLayout.parseInstance(a).toPrintable()); Runnable r = () -> {synchronized (a) {system.out.println ("------After "+ thread.currentThread () +" lock is fetched------\n" + ClassLayout.parseInstance(a).toPrintable()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }}}; Thread [] threads = new Thread[2]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(r); threads[i].start(); } for (int i = 0; i < threads.length; i++) { threads[i].join(); } System.out.println("------After Released Lock------\n" + ClassLayout.parseInstance(a).toPrintable());Copy the code
The output is (we omit the output we don’t care about here) :
------After Initialization------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) ------After Fetched Lock------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 8a 5e (00000101 10010000 10001010 01011110) (1586139141) 4 4 (object header) c7 02 00 00 (11000111 00000010 00000000 00000000) (711) ------After Released Lock------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 90 8a 5e (00000101 10010000 10001010 01011110) (1586139141) 4 4 (object header) c7 02 00 00 (11000111 00000010 00000000 00000000) (711) a.hashcode: 929776179 ------After call hashcode------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 33 42 6b (00000001 00110011 01000010 01101011) (1799500545) 4 4 (object header) 37 00 00 00 (00110111 00000000 00000000 00000000) (55) ------After Fetched Lock------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 78 f2 0f 53 (01111000 11110010 00001111 01010011) (1393554040) 4 4 (object header) ee 00 00 00 (11101110 00000000 00000000 00000000) (238) ------After Released Lock------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 33 42 6b (00000001 00110011 01000010 01101011) (1799500545) 4 4 (object header) 37 00 00 00 (00110111 00000000 00000000 00000000) (55) ------After Thread[thread0,5,main] lock is touch------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (Object header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000 00000000) (617) ------After Thread[thread-1,5,main] lock is touch------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object) header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000 00000000) (617) ------After Released Lock------ OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 02 3e e0 b0 (00000010 00111110 11100000 10110000) (-1327481342) 4 4 (object header) 69 02 00 00 (01101001 00000010 00000000, 00000000) (617).Copy the code
We use the last two bits of the first byte to determine the lock status:
- After the object is created, the initial state is unlocked: the first byte is 00000101, and 01 indicates that the object is unlocked. At the same time, the bias lock is on, because 00000101, the third from last is 1, which can be known as the bias lock mark according to the previous structure diagram.
- The first time the main thread obtains the lock, there is no contention, and the bias lock is not closed in the startup parameter, so the bias lock is adopted: the first byte is 00000101, indicating that the state is in the bias lock, followed by the pointer to the thread.
- The first time the main thread releases the lock, since no other contention is available, the biased lock state is held. JVM – SafePoint and Stop The World full solution (based on OpenJDK 11).
- To call hashcode, according to the previous source code analysis, we need to cancel the biased lock and write hashcode to the header: the first byte is 00000001, indicating that the lock is unlocked, the biased lock is closed, and the third to last bit is 0.
- After calling Hashcode, the main thread acquires the lock, starting directly from the light lock due to partial lock closure: the first byte is 01111000, where 00 represents the light lock. A pointer to the lock record is saved at the end.
- After calling Hashcode, the main thread releases the lock, releases the lightweight lock, and the lock record is reclaimed, so hashcode goes back to the header to save.
- After multithreading causes the object to be upgraded to a heavyweight lock: the first byte is 00000010 and 10 represents the heavyweight lock. Since monitor is generated and exists forever, the object header will always hold the pointer to Monitor, and hashcode will also be stored on Monitor.
- The header does not change after the lock is released.
1.2. Type word compression pointer and JVM maximum memory
The compressed pointer property is turned on by default and can be turned off with -xx: -usecompressedoops.
Why do we need to compress Pointers in the first place? 32-bit memory, how much memory can you describe? Assuming that each 1 represents a byte, we can describe the 2^32 bytes from 0 to 2^32-1, which is 4 GB of memory.
However, Java defaults to 8-byte aligned memory, that is, the amount of space an object occupies, which must be an integer multiple of 8 bytes, or fill up to an integer multiple of 8 bytes if not. In fact, when describing memory, you don’t need to start from 0 to 8 (that is, you don’t need to locate 1,2,3,4,5,6,7 at all) because the object must start and end at multiples of 8. So, 2^32 bytes if a 1 stands for 8 bytes, then at most you can describe 2^32 * 8 bytes which is 32 GB of memory.
That’s how you compress Pointers. If the configured maximum heap memory exceeds 32 GB (when the JVM is 8-byte aligned), then the compression pointer is invalidated. However, the size is 32 GB and byte alignment, namely – XX: ObjectAlignmentInBytes configuration size (the default is 8 bytes, Java is the default is 8 bytes aligned). – XX: ObjectAlignmentInBytes integer times can be set to 8, 128. , that is, if the configuration – XX: ObjectAlignmentInBytes is 24, then configure the more than 96 GB of memory compression pointer will fail.
Write procedures to test:
A a = new A();
System.out.println("------After Initialization------\n" + ClassLayout.parseInstance(a).toPrintable());
Copy the code
First of all, in order to launch parameters: – XX: ObjectAlignmentInBytes = 8 – Xmx16g perform:
------After Initialization------ com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472) 12 4 (alignment/padding gap) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes totalCopy the code
You can see that the type word size is 4 bytes 48 72 06 00 (01001000 01110010 00000110 00000000) (422472). The compressed pointer takes effect.
First of all, in order to launch parameters: – XX: ObjectAlignmentInBytes = 8 – Xmx32g perform:
------After Initialization------ com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584) 12 4 (object header) b4 02 00 00 (10110100 00000010 00000000 00000000) (692) 16 8 long A.d 0 Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes totalCopy the code
You can see that the type word size is 8 bytes and the compressed pointer is invalid:
a0 5b c6 00 (10100000 01011011 11000110 00000000) (12999584)
b4 02 00 00 (10110100 00000010 00000000 00000000) (692)
Copy the code
Modify alignment size is 16 bytes, that is, to – XX: ObjectAlignmentInBytes = 16 – Xmx32g perform:
------After Initialization------ com.hashjang.jdk.TestObjectAlign$A object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 48 72 06 00 (01001000 01110010 00000110 00000000) (422472) 12 4 (alignment/padding gap) 16 8 long A.d 0 24 8 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 4 bytes internal + 8 bytes external = 12 bytes totalCopy the code
You can see that the type word size is 4 bytes 48 72 06 00 (01001000 01110010 00000110 00000000) (422472). The compressed pointer takes effect.
conclusion
- Object Pointers include marker words and type words.
- The tag word saves: Object default hash value (the hash value is recorded in the tag after the hashCode () method is called if the default hashcode() method is not overridden), object shape (whether array or not), lock state (lock information such as biased locks, it is worth pointing out that biased locks are deprecated in Java 15: Disable and Deprecate Biased Locking), array length (if the flag shows that this object is an array, describes the array length). The implementation of the tag word consists of just one uintptr_t type, so the size is 4 bytes and 8 bytes respectively on 32-bit and 64-bit virtual machines. Can refer to the source code: markword.hpp
- The type word holds a pointer to the Class of the object implementation. Type words are compressed by default, and that’s what the compression pointer refers to.
- The default hash calculation has an effect on whether biased locking is in effect. The default hash and bias locks do not coexist.
- The default hash is cached: no lock is cached for the tag; The light lock is cached in the lock record, and there is a pointer to the lock record in the tag word. After the light lock is released, the hash value in the lock record is copied to the tag word. The weight lock is cached in the Monitor object, and the pointer in the tag points to the monitor object. After release, the hash value is still cached in the Monitor object.
- The default hash calculation needs to take account of asynchronous Monitor degradation, which is new in Java 15: Async Monitor Continuing
- Generational age +1 after each Young GC replication, maximum is
-XX:MaxTenuringThreshold=n
If the value is greater than this value, it enters the old age. - Whether the compressed pointer is enabled to align bytes with Java (
-XX:ObjectAlignmentInBytes
, the default is 8, which is 8 byte alignment) and the maximum stack size.