Java- a Java object re-learn how much memory is accounted for in the end

Memory is a programmer can not escape the topic, of course, because there are Java GC so that we do not have to manually apply and release the memory, but to understand Java memory allocation is to do memory optimization based on knowledge of Java do not know if the memory allocation may be biased with us memory direction of optimization. So in this article we have "accounted for how much memory an object" as the prelude to talk about Java memory allocation. Articles based on JDK version: 1.8.0_191

Issues raised by the article title is "an object in the end account for how much memory", it seems very simple, but not easy to make it clear, this article makes you want to explore the harvest.

Before the start I had decided to put a lingering question that bothers me a long time to understand the answer to this question helps us to understand what follows.

How to know in the Java virtual machine runtime type of memory to store each piece of data?

  • We know that in Java int 4 bytes, short 2 bytes, a reference type is four bytes on 64-bit machines (not open pointer compression is 8 bytes, the pointer compression is enabled by default), then how to know JVM runtime type of the value of a piece of memory is stored or short int or other basic type, also referenced or address? In such an example int, int stored for only 4 bytes data itself, and no extra space for storing the type of data!

Would like to answer this question, you need to start from the bytecode, we also need some knowledge of the Java Virtual Machine specification, look at a simple example

public class Apple extends Fruit{
    private int color;
    private String name;
    private Apple brother;
    private long create_time;

    public void test() {
        int color = this.color;
        String name = this.name;
        Apple brother = this.brother;
        long create_time = this.create_time;
    }
}
复制代码

Apple is simply a class inherits from Fruit, there is a test method, class members will be assigned to the variable method local variables, or the old routine, javac, javap view bytecode

javac Fruit.java Apple.java
javap -verbose Apple.class

// 输出Apple字节码
public class com.company.alloc.Apple extends com.company.alloc.Fruit
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#25         // com/company/alloc/Fruit."<init>":()V
   #2 = Fieldref           #8.#26         // com/company/alloc/Apple.color:I
   #3 = Fieldref           #8.#27         // com/company/alloc/Apple.name:Ljava/lang/String;
   #4 = Fieldref           #8.#28         // com/company/alloc/Apple.brother:Lcom/company/alloc/Apple;
   #5 = Fieldref           #8.#29         // com/company/alloc/Apple.create_time:J
   // 省略......
{
 // 省略......
  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=6, args_size=1
         0: aload_0
         1: getfield      #2                  // Field color:I
         4: iconst_1
         5: iadd
         6: istore_1
         7: aload_0
         8: getfield      #3                  // Field name:Ljava/lang/String;
        11: astore_2
        12: aload_0
        13: getfield      #4                  // Field brother:Lcom/company/alloc/Apple;
        16: astore_3
        17: aload_0
        18: getfield      #5                  // Field create_time:J
        21: ldc2_w        #6                  // long 3l
        24: lsub
        25: lstore        4
        27: return
        // 省略......
}

复制代码

We look at the focus test method Apple class, I've added comments

         // 加载Apple对象本身到栈
         0: aload_0
         // 获取字段,#2 对应常量池中的序列,
         // #2 = Fieldref           #8.#26         // com/company/alloc/Apple.color:I
         // 存储的类型是int类型
         1: getfield      #2                  // Field color:I
         // 加载1这个常量进栈
         4: iconst_1
         // 执行加法
         5: iadd
         // 将栈顶的值存到本地变量表1的位置
         6: istore_1
         // 加载Apple对象本身到栈
         7: aload_0
         // 获取字段,#3 对应常量池中的序列,
         8: getfield      #3                  // Field name:Ljava/lang/String;
         // 将栈顶的值存到本地变量表2的位置
        11: astore_2
         // .......
复制代码

Can be seen that the member variables for the object, there will be a constant pool, the index table stored in the object's class all the fields, in accordance with the constant pool can be queried type variable, the bytecode instructions for each type of operation has special instructions, such as storage int is istore, storage object is astore, storage long is lstore, so instruction is compile been identified, the virtual machine only according to the instruction execution on the line, do not care about the address of its operations is what type of , so there is no additional field to keep the type of answer we mentioned earlier question!

We began to come to the question, to say the memory allocation, we must first understand the object of our assignment, that assignment in Java objects what type it?

What Java data types

In Java data types are divided into two categories.

  • The underlying data type (primitive type)
  • Reference types (reference type)
Basic data types

In Java basic data types are eight kinds, namely byte (1), short (2), int (4), long (8), float (4), double (8), char (2), boolean (1) , parentheses is the number of bytes they occupy, so for basic data types, they occupy memory is determined, there is nothing to say, simply look at the number of bytes of memory required for each type can be stored.

Java, the underlying data type is assigned or allocated on the heap on the stack? We continue to go into it, the basic data types take up memory size is fixed, that is where the specific allocation of it, is still in the heap or stack method area? We may wish to think about it! To answer this question, first of all look at where this data type definitions, the following three conditions.

  • If the method is defined in the body, this time is allocated on the stack
  • If the variable is a member of the class, this time is allocated on the heap
  • If the variable is a static member of the class, allocated on the method area
Reference types

Reference types with different underlying data type, in addition to the object itself, there is also a reference to it (pointer), the pointer occupies memory on the virtual machine 64 8 bytes, the pointer is turned on if the compression is 4 bytes the default is to open up the. For convenience, or in code as an example

class Kata {
  // str1和它指向的对象 都在堆上
  String str1 = new String();
  // str2和它指向的对象都在方法区上
  static String str2 = new String();
  
  public void methodTest() {
     // str3 在栈上,它指向的对象在堆上(也有可能在栈上,后面会说明)
     String str3 = new String();
  }
}
复制代码

Java objects in the end how much of a memory?

The length of the pointer is fixed, it does not say it, and see how much memory it points to focus on objects accounted in memory. There are three main types of Java objects

  • Class object
  • An array of objects
  • Interface objects

Java Virtual Machine Specification defines the object type specification is stored in memory, since now basically a 64-bit virtual machine, so the following discussion is based on 64-bit virtual machine. Remember first equation object instance of the object header + data + padding padding bytes, virtual machine memory occupied by the object specifications must be a multiple of 8, padding is doing that

Object header

While the head by a Java object class pointer Markword + kclass (the pointer to the cell type in the type area method) composition.

Mark Word

Hotspot Virtual Machine Documentation "oops / oop.hp" definition of Markword field

  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)
复制代码

Here are several simple explain this object

  • normal object, the object is initially out of this new state
  • biased object, when an object is an object as synchronization lock, there will be a bias lock, in fact, hold the thread id stored synchronization lock, the lock of knowledge about the bias will not repeat them here, you can self-refer to the relevant data.
  • CMS promoted object and CMS free block in the end is what I do not know, but look at the name seems to be related with the CMS garbage collector, here we can also ignore them

We focus on normal object, Object Markword this type of a total 8 bytes (64 bits), where 25 is temporarily not used, the hash value stored in the object 31 (Note that the stored hash value calculated according to the object address out of the hash value, which is not the override hashcode method return value), the middle one is not used, as well as age 4 bit memory object (Age generational recovery object, the promotion of more than 15 years old), the last three It indicates identification biased locking and lock identification, is used primarily to distinguish the object lock state (unlocked biased locks, locks lightweight, heavyweight lock)

// 无其他线程竞争的情况下,由normal object变为biased object
synchronized(object)
复制代码

Markword biased object before the object header 54 bits to store the thread id hold the lock, so that no space to store the hashcode, so the object does not override the hashcode, if hashcode been calculated and stored in the object header, the when the object as synchronization lock, the lock will not enter biased state, as it has no place to keep bias thread id, so we choose to sync lock object, the best method to rewrite hashcode of the object, the biased locking can take effect.

kclass

kclass class of the object is stored in the address it belongs in the process area, it is a pointer, the default pointer Jvm is compressed, with the 4 bytes of storage, if not compressed is 8 bytes. About Compressed Oops knowledge, you can access relevant information to deepen their own understanding. Java Virtual Machine specification requires the size of the space occupied by the object must be a multiple of 8 bytes, reason why this provision is to improve the efficiency of allocated memory, we do illustrate by example

class Fruit extends Object {
     private int size;
}

Object object = new Object();
Fruit fruit = new Fruit();
复制代码

There is a Fruit class inherits Object class, we were to create a new object and fruit, that they were occupying much memory?

  • First look object object, by the above knowledge, it Markword is 8 bytes, kclass 4 bytes, add up to 12 bytes plus 4 bytes alignment padding, so that it takes up space is 16 bytes.
  • Let's look at fruit objects, the same, it's Markword is 8 bytes, kclass is 4 bytes, but it also has a member variable size, int type accounted for 4 bytes, add up to exactly 16 bytes alignment padding is not required.

Then how to validate our conclusion? After all, we still believe that seeing is believing! Luckily Jdk provides a tool jol-core will allow us to analyze the memory occupied by the object header information. jol is also very simple to use

// 打印对象头信息代码
System.out.println(ClassLayout.parseClass(Object.class).toPrintable());
System.out.println(ClassLayout.parseClass(Fruit.class).toPrintable());

// 输出结果
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

com.aliosuwang.jol.Fruit object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4    int Fruit.size                                N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
复制代码

We can see the output is 16 bytes, consistent with our previous results. In addition to the type of object classes and types of interfaces, Java there array type object, an array type field in addition to the expression above, there are 4 bytes in length is stored in the array (so the maximum length of the array is Integer.MAX) . So an array of memory occupied by the object is 8 + 4 + 4 = 16 bytes, of course, where the memory does not include members of the array. We also run to test.

String[] strArray = new String[0];
System.out.println(ClassLayout.parseClass(strArray.getClass()).toPrintable());

// 输出结果
[Ljava.lang.String; object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0    16                    (object header)                           N/A
     16     0   java.lang.String String;.<elements>                        N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
复制代码

The length of the output object header 16 is consistent with our analysis. Memory allocation here to object header part we know about it, see the next section examples of data objects.

Examples of data objects (member variables) allocation rules

For convenience, we create a new class that inherits Apple Fruit class above

public class Apple extends Fruit{
    private int size;
    private String name;
    private Apple brother;
    private long create_time;
}

// 打印Apple的对象分布信息
System.out.println(ClassLayout.parseClass(Apple.class).toPrintable());

// 输出结果
com.aliosuwang.jol.Apple object internals:
 OFFSET  SIZE                       TYPE DESCRIPTION                               VALUE
      0    12                           (object header)                           N/A
     12     4                       int Fruit.size                                N/A
     16     8                      long Apple.create_time                         N/A
     24     4                       int Apple.size                                N/A
     28     4          java.lang.String Apple.name                                N/A
     32     4   com.company.alloc.Apple Apple.brother                             N/A
     36     4                            (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
复制代码

Apple objects can be seen head 12 bytes, respectively, then the inherited property class Fruit size (although the size Fruit is private, or are inherited, and Apple's own size co-exist) , as well as their definition 4 properties, basic data types directly allocated object types are stored pointer 4 bytes (a pointer compression are enabled by default), the final is 40 bytes, so we have a new Apple objects, will take up direct stack 40 bytes of memory, a clear target memory allocation, let us know what's what when writing the code, it should be time conscious memory optimization! Here again leads to a small knowledge, the above it is already marked out.

Private member variable of whether the parent class inherited by subclasses?

The answer of course is yes, Apple class analysis above us, parent Fruit has a private member variable of type size, Apple itself has a size member variable, they can coexist. Note that the focus of the plan, private access to member variables of the class of controls private, but the compiler level restrictions, in real memory, whether private or public, are stored together by the rules, the virtual machine is not What is the difference!

New method inside an object on the heap or on the stack?

Our conventional understanding is that the object is allocated on the heap, there will be a reference point to the object (ie, stores its address) on the stack, in the end is not it, to a test! We create a loop in one hundred million Apple object and record the execution time of the cycle, as already calculated that an Apple object occupies 40 bytes, a total of 4GB of space.

public static void main(String[] args) {
     long startTime = System.currentTimeMillis();
     for (int i = 0; i < 100000000; i++) {
         newApple();
     }
     System.out.println("take time:" + (System.currentTimeMillis() - startTime) + "ms");
}

public static void newApple() {
     new Apple();
}
复制代码

We give the JVM add -XX: + PrintGC running configuration, so that during the execution of the compiler output in the GC log log

// 运行结果,没有输出任何gc的日志
take time:6ms
复制代码

100 million objects, 6ms to complete the assignment, and no GC, obviously if the object is allocated on the heap, then it is impossible, in fact, the above example code, Apple objects are all allocated on the stack, here to propose a concept pointer escape , newApple new method object Apple has not been used outside, so it is optimized to be allocated on the stack, we know that after the completion of the execution method stack frame will be empty, so there would be no GC. We can set the operating parameters of the virtual machine to test.

// 虚拟机关闭指针逃逸分析
-XX:-DoEscapeAnalysis
// 虚拟机关闭标量替换
-XX:-EliminateAllocations
复制代码

Adding the above two parameters in the VM options there, and run it again

[GC (Allocation Failure)  236984K->440K(459776K), 0.0003751 secs]
[GC (Allocation Failure)  284600K->440K(516608K), 0.0004272 secs]
[GC (Allocation Failure)  341432K->440K(585216K), 0.0004835 secs]
[GC (Allocation Failure)  410040K->440K(667136K), 0.0004655 secs]
[GC (Allocation Failure)  491960K->440K(645632K), 0.0003837 secs]
[GC (Allocation Failure)  470456K->440K(625152K), 0.0003598 secs]
take time:5347ms
复制代码

GC can see a lot of logs, and run a lot longer time than before, because this time Apple assign an object on the heap, and the heap is shared by all threads, so there must be time allocated synchronization mechanism, and trigger a large number of gc, so a lot of inefficiency. To summarize: the virtual machine pointer escape analysis is enabled by default, the object will not escape when the priority allocated on the stack, or allocated on the heap. Here, on the "how much memory an object accounted for?" This question has been able to answer fairly comprehensive. But after all, we analyzed only Hotspot virtual machine, we wish to extend it, look at the distribution of Android ART virtual machine above

Gets Android ART virtual machine on top of the object header size

We've used the tool to jol output object header, but this jol tool can only be used on the hotspot virtual machine, then how do we get the size of the object in the Android head above it?

Inspired approach

Measures must be some way that I presented here, the protagonist is inspired AtomicInteger , I was inspired by it, we know that this class is thread-safe wrapper class int. Its implementation is the use of the CAS package provides the ability Unsafe, may wish to look at its source code implementation

    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long VALUE;

    static {
        try {
            VALUE = U.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile int value;

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
    }
复制代码

We know that ordinary int ++ object operation is not atomic, AtomicInteger provides getAndIncrement () it can guarantee atomicity, this part of this knowledge is not knowledge we want to say, not to say them. Internal getAndIncrement () method calls Unsafe object getAndAddInt () method, the second parameter is VALUE, VALUE of this great mystery, which represents the member variables cheap address in the memory of the object , based on previous knowledge of the structure of ordinary objects is + + instance data object header byte aligned , then if we can obtain the offset data of the first instance, in fact, obtain the size in bytes of the object header.

How to get and use Unsafe

Because Unsafe class is not visible, and it has to check the current class loader during initialization, if not the system loader will complain. But the good news is, AtomicInteger defines a Unsafe objects, and static, we can get directly through reflection.

    public static Object getUnsafeObject() {
        Class clazz = AtomicInteger.class;
        try {
            Field uFiled = clazz.getDeclaredField("U");
            uFiled.setAccessible(true);
            return uFiled.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
复制代码

Got Unsafe, we will be able to get the offset memory address of the member variables by calling its objectFieldOffset static method.

    public static long getVariableOffset(Object target, String variableName) {
        Object unsafeObject = getUnsafeObject();
        if (unsafeObject != null) {
            try {
                Method method = unsafeObject.getClass().getDeclaredMethod("objectFieldOffset", Field.class);
                method.setAccessible(true);
                Field targetFiled = target.getClass().getDeclaredField(variableName);
                return (long) method.invoke(unsafeObject, targetFiled);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        return -1;
    }

    public static void printObjectOffsets(Object target) {
        Class targetClass = target.getClass();
        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName();
            Log.d("offset", name + " offset: " + getVariableOffset(target, name));
        }
    }
复制代码

We use Fruit and Apple before the above test print tool,

        Log.d("offset", "------start print fruit offset!------");
        Utils.printObjectOffsets(new Fruit());

        Log.d("offset", "------start print apple offset!------");
        Utils.printObjectOffsets(new Apple());

        // 输出结果 (Android 8.0模拟器)
        offset: ------start print fruit offset!------
        offset: size offset: 8
        offset: ------start print apple offset!------
        offset: brother offset: 12
        offset: create_time offset: 24
        offset: id offset: 20
        offset: name offset: 16
复制代码

Through the output, it is seen on Android8.0 ART virtual machine, the size of the object header is 8 bytes, unlike the virtual machine with the hotspot (12 bytes hotspot is enabled by default pointer compression), only the current output according to the result found from this difference, the number of bytes occupied by the various data types are the same, such as int 4 bytes, the pointer 4 bytes, long8 bytes, etc., are the same.

to sum up

We summarize the full text of the following knowledge points

  • Java Virtual Machine bytecode instructions operate through memory, so you can say it does not care about data types, it's just acting according to instructions, different types of data have different bytecode instructions.
  • Java basic data types and reference types of memory allocation knowledge, analyzes the reference type of object header, and introduced the use of tools JOL
  • Extends to the Android platform, introduces a method for obtaining an object in the header information of the object Android, and compare differences ART Hotspot virtual machine and the length of the object header.

In order to understand these are not loaded to force virtuoso, to be honest, nothing to write code to do good works filled with someone else's wheel, I know I will thank them not too late, so I put them in writing to others.

Finally, I reiterate: Only full understanding of Java's memory allocation mechanism, in order to do the right memory optimization! ! .

Guess you like

Origin juejin.im/post/5d0fa403f265da1bb67a2335