When the class loader loads the class file into the JVM, it generates an object of type Klass (c++) called class description metadata, which is stored in the method area, i.e. the metadata area after jdk1.8. When you create an object using new, you create an object OOP based on the class description metadata Klass and store it in the heap. Every Java object has the same component, called the object header.

When learning about synchronized, it’s always difficult to understand how it works, because biased, lightweight, and heavyweight locks all involve object headers, so understanding Java object headers is a prerequisite for learning more about synchronized.

Look at the artifact of the object header

Jol -core is a tool kit for calculating the size of Java objects and viewing the memory layout of Java objects in your code. Just go to the Maven repository http://mvnrepository.com and search for the Java Object Layout, select the version you want to use, and add the dependencies to your project.

Use jol to calculate the size (in bytes) of an object:

ClassLayout.parseInstance(obj).instanceSize() 
Copy the code

Use jol to view the memory layout of an object:

ClassLayout.parseInstance(obj).toPrintable()
Copy the code

Use JOL to view the memory layout of the object

Network search a lot of information, on the 64 bit JVM Java object header layout is very vague, a lot of information is about 32 bit, and many are copied from some books, there will inevitably be mistakes, so the best way to learn is to verify themselves, look at the JVM source code. This article covers Java object headers for 64-bit JVMS in detail.

Take the User class as an example

public class User {
    private String name;
    private Integer age;
    private boolean sex;
}
Copy the code

View the memory layout of the User object through jol

User user = new User()
System.out.println(ClassLayout.parseInstance(user).toPrintable());
Copy the code

The output is as follows

  • OFFSET: OFFSET address, in bytes.
  • SIZE: indicates the memory SIZE, in bytes.
  • TYPE DESCRIPTION: Indicates the TYPE DESCRIPTIONobject headerIs the object header;
  • VALUE: corresponds to the VALUE currently stored in memory;

As you can see from the figure, the memory size used by the object header is 16*8bit=128bit. If you print it out yourself, you might get 96 bits, because I turned off pointer compression. In jdk8, pointer compression is enabled by default. You can configure VM parameters to disable pointer compression.

-XX:-UseCompressedOops
Copy the code

Now unconfigure pointer compression and, with pointer compression enabled, look at the memory layout of the User object.

Enabling pointer compression reduces the memory usage of an object. According to the layout information of User objects printed twice, when pointer compression is turned off, the name field and the age field occupy 8 bytes respectively because they are reference types, but after pointer compression is turned on, the two fields only occupy 4 bytes respectively. Therefore, turning on pointer compression can, in theory, save about 50 percent of memory. The JDK8 and later versions have enabled pointer compression by default.

From the memory layout of the User object printed twice, you can also see that the age field of type bool takes up only 1 byte, but is followed by a few bytes of waste, namely memory alignment. Memory alignment of the AGE field takes three bytes with pointer compression enabled and seven bytes with pointer compression disabled.

If we look at the memory layout of the User object with pointer compression enabled by default, the object header takes 12 bytes. What information is stored in the 12 bytes? We don’t look at the online data, but the JDK source code.

Download the openJDK source or view it online

Liverpoolfc.tv: openjdk.java.net/

I am currently using the JDK version is JDK1.8, can be viewed through the command line Java -version, can also be viewed through the following way, this you should be familiar with.

System.out.println(System.getProperties());
Copy the code

After opening the official website, click Groups on the left menu bar to find HotSpot, select Browsable Souce in the Source code of the opened page, and then jump to hg.Openjdk.java.net/, select jdk8u. Continue to select hotspot under jdK8U in the page after the jump. Transmission links: hg.openjdk.java.net/jdk8u/jdk8u… .

  • Click on thezipCan be downloaded source package;
  • Click on thebrowseCan be viewed online source;

Java object headers in detail

With pointer compression enabled, the object header of the User object takes 12 bytes. In this section, we look at the source code to see what information is stored in the object header.

Every time a new object is created while a Java program is running, the JVM accordingly creates an OOP object of the corresponding type and stores it in the heap. For example, new User() creates an instanceOopDesc with the base class oopDesc.

[instanceOop HPP files: the hotspot/SRC/share/vm/oops/instanceOop HPP]class instanceOopDesc : public oopDesc {
}
Copy the code

InstanceOopDesc provides only a few static methods, such as getting the object header size. So focus on the parent oopDesc.

[oop. HPP files: the hotspot/SRC/share/vm/oops/oop HPP]class oopDesc {
   friend class VMStructs;
 private:
  volatile markOop  _mark;
  union_metadata { Klass* _klass; narrowKlass _compressed_klass; } _metadata; . }Copy the code

We only care about the object header. The object header for a normal object (such as a User object, and we won’t talk about array types here) consists of a markOop and a combination of markOop and MarkWord. This union is a metadata pointer to the class, using _klass when pointer compression is off and _compressed_klass when pointer compression is on.

MarkOop with narrowKlass type definitions in the hotspot/SRC/share/vm/oops/oopsHierarchy HPP header file:

[oopsHierarchy HPP header file: / hotspot/SRC/share/vm/oops/oopsHierarchy HPP]typedef juint  narrowKlass;
typedef class markOopDesc* markOop;
Copy the code

Therefore, narrowKlass is a juint, and junit is defined in the globaldefinitions_viscp.hpp header, which is an unsigned integer of 4 bytes. So with pointer compression enabled, the size of the pointer to the Klass object is 4 bytes.

[/hotspot/src/share/vm/utilties/globalDefinitions_visCPP.hpp]
typedef unsigned int juint;
Copy the code

MarkOop is a pointer of type markOopDesc, which is a MarkWord. In case you’re wondering, on 64-bit JVMS, the markOopDesc pointer is 8 bytes, or 64bit. It’s exactly the size of MarkWord, but isn’t it pointing to an object? Let’s start with the markOopDesc class.

[markOop HPP files: the hotspot/SRC/share/vm/oops/markOop HPP]// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
class markOopDesc: public oopDesc {
 ......
}
Copy the code

The MarkWord 64-bit storage information is described in the markoop. HPP header. The markOopDesc class also inherits oopDesc. If you look at the source code of the markOopDesc class, you won’t find it. MarkOopDesc uses that field to store MarkWord. Also, what we know from various sources is that the first 8 bytes of the object header store information such as biased locking, lightweight locking, etc. (the whole text is 64-bit), so it should not be a pointer.

To answer this question, I first look at the source code for the markOopDesc class and find a method, for example, to get the age of a GC object, to see where the JVM is getting the data from.

class markOopDesc: public oopDesc {
public:
 // Get the age of the object
  uint  age(a) const { 
    return mask_bits(value() >> age_shift, age_mask); 
  }
  // Update the age of the object
  markOop set_age(uint v) const {
    return markOop((value() & ~age_mask_in_place) | (((uintptr_t)v & age_mask) << age_shift));
  }
  // Increase the age of the object
  markOop incr_age(a) const { 
    return age() == max_age ? markOop(this) : set_age(age() + 1); }}Copy the code

The value() method returns the 64-bit MarkWord.

class markOopDesc: public oopDesc {
 private:
  // Conversion
  uintptr_t value() const { return(uintptr_t) this; }}Copy the code

The value method returns a pointer to this. You can also see from the set_AGE and incr_age methods that whenever you modify MarkWord, a new markOop (markOopDesc*) is returned. No wonder markOopDesc* is defined as markOop, using markOopDesc* as an 8-byte integer. To understand this, we need a little bit of c++, so I wrote a demo.

Customize a class called oopDesc and provide only a Show method in addition to the constructor and destructor.

[.] HPP file#ifndef oopDesc_hpp
#define oopDesc_hpp

#include <stdio.h>

#include <iostream>using namespace std; Class oopDesc* oop typedef class oopDesc* oop; class oopDesc{ public: void Show(); };#endif /* oopDesc_hpp */[.] CPP file#include "oopDesc.hpp"

void oopDesc::Show(){
    cout << "oopDesc by wujiuye" <<endl;
}
Copy the code

Create an oopDesc* using OOP (pointer) and call the show method.

#include <iostream>
#include "oopDesc.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
    // 
    oopDesc* o = oop(0x200);
    cout << o << endl;
    o->Show();
    // 
    oopDesc* o1;
    o1->Show();
    return 0;
}
Copy the code

Test output

0x200
oopDesc by wujiuye
oopDesc by wujiuye
Program ended with exit code: 0
Copy the code

Therefore, with the class name (value), you can create an wild pointer object, assign the pointer to value, and use this as a MarkWord. If you add a field to oopDesc and provide a method access, the program will run with an error, so the object created this way can only call methods, not fields.

MarkWord

The lock state/gc Mark Word (64 bits) Biased or not lock Lock flag bit
unlocked unused:25 hashcode:31 unused:1 age:4 biased_lock:1 lock:2
Biased locking thread:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2
Lightweight lock ptr_to_lock_record:62 lock:2
Heavyweight lock ptr_to_heavyweight_monitor:62 lock:2
The gc logo lock:2

By judging the state of the current MarkWord by the last three bits, you can determine what the rest of the bits store.

[markOop HPP file]enum {  locked_value             = 0.// 0 00 lightweight lock
         unlocked_value           = 1.// 0 01 No lock
         monitor_value            = 2.// 0 10 heavyweight locks
         marked_value             = 3.// 0 11 GC flag
         biased_lock_pattern      = 5 // 1 01 bias lock
  };
Copy the code

Now, let’s look at the memory layout of the User object print.

64
MarkWord
32

As you can see from the figure, in the unlocked state, the Hashcode of the User object is 0x7A46a697. Since MarkWord is actually a pointer, it takes up 8 bytes under 64-bit JVMS. So MarkWordk is 0x0000007a46a69701, which is exactly the opposite of what you see in the figure. Here involves a knowledge point “big end storage and small end storage”.

  • Little-endian: The low byte is stored at the low address end of the memory, and the high byte is stored at the high address end of the memory.
  • Big-endian: The high byte is stored at the low address end of the memory, and the low byte is stored at the high address end of the memory.

Have learned assembly language friends, this knowledge should still remember. This article does not introduce in detail, the friend that is not very clear can look for information on the net to see.

Write a synchronized lock demo to analyze the lock status

Next, let’s take a look at the memory information of the User object using synchronized, and analyze the lock status using the object header.

Case 1

public class MarkwordMain {

    private static final String SPLITE_STR = "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =";
    private static User USER = new User();

    private static void printf(a) {
        System.out.println(SPLITE_STR);
        System.out.println(ClassLayout.parseInstance(USER).toPrintable());
        System.out.println(SPLITE_STR);
    }

    private static Runnable RUNNABLE = () -> {
        while(! Thread.interrupted()) {synchronized (USER) {
                printf();
            }
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}};public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(RUNNABLE).start();
            Thread.sleep(1000); } Thread.sleep(Integer.MAX_VALUE); }}Copy the code

Analyze the locking information from the object header, MarkWordk is 0x0000700009b96910, The binary value is 0xB00000000 00000000 01110000 00000000 00001001 10111001 01101001 00010000.

The third-to-last bit is “0”, indicating that the state is not biased lock, and the last-to-last two bits are “00”, therefore, the state is lightweight lock, so the first 62 bits are Pointers to the lock record in the stack.

Case 2

public class MarkwordMain {

    private static final String SPLITE_STR = "= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =";
    private static User USER = new User();

    private static void printf(a) {
        System.out.println(SPLITE_STR);
        System.out.println(ClassLayout.parseInstance(USER).toPrintable());
        System.out.println(SPLITE_STR);
    }

    private static Runnable RUNNABLE = () -> {
        while(! Thread.interrupted()) {synchronized(USER) { printf(); }}};public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            newThread(RUNNABLE).start(); } Thread.sleep(Integer.MAX_VALUE); }}Copy the code

Analyze the locking information from the object header, MarkWordk is 0x00007FF0C80053ea, The binary value is 0xB00000000 00000000 01111111 11110000 11001000 00000000 01010011 11101010.

The third digit from the bottom is “0”, indicating that the mutex state is not biased, and the last two digits are “10”. Therefore, the mutex state is heavy, so the first 62 digits are Pointers to the mutex.