How java objects are distributed in memory | java locking is originally a memory footprint, so easy

foreword

  • This chapter is the pioneering work of the java lock chapter, and his status is definitely the top priority. Objects created in java are created directly through keywords new. But how many bytes an object occupies in memory, and what is the function of each block of bytes, I believe that everyone pays little attention. If you want to learn java multithreading, I think it is necessary to understand the memory distribution of objects.
  • In order to visually analyze the memory of running Java objects, this article uses the openjdkprovided memory analysis tool; and our analysis is premised on the 64-bit hotspot virtual machine environment
 <dependency>
     <groupId>org.openjdk.jol</groupId>
     <artifactId>jol-core</artifactId>
     <version>0.9</version>  
 </dependency>

Object distribution

  • First of all, we have to clarify what kind of structure a Java object is in memory. Some people here may say what structure can there be. It is the binary storage of memory data. Such an answer seems to be said or not.
  • Here we use a demo case to explain in detail. First of all, there is such an object in our system
 @Data
 public class User {
     private int age;
 }
  • An Userobject has only one property of type Int. He is not just representing data of type int in memory. The figure should be kclass point

image-20211207105206289.png

  • For an ordinary Java object, the above data structure is stored in memory. In a 64-bit system, the markword station occupies 8 bytes; the klass point occupies 8 bytes without enabling pointer compression, otherwise it occupies 4 bytes; the content here is the attribute in our object, because the attributes in Java all have The type and the placeholder for each type are also different. For example, our int type above occupies 4 bytes; what does it mean to fill in the bytes? In 64-bit, an object must occupy a multiple of 8 bytes in memory.

image-20211207110314447.png

  • According to our formula for padding bytes, it can be calculated that the padding byte in memory for the User object is 0, because it occupies 16 bytes and is a multiple of 8. As for why it is a multiple of 8? The number of bytes occupied by an object in a 64-bit system must be a multiple of 8. Because 8B=64b; I organize the following table for byte units
1B 8b
1KB 1024B
1MB 1024KB
1GB 1024MB
  • And what we call a byte is B, and the smallest unit of a computer is a bit.
  • Here we look through jol

image-20211207111129194.png

  • Through JOL we can also measure how many bytes the Null object occupies. Everyone knows that it is 4 bytes, so I won't test it here.
  • 然后我们给User对象增加一个double属性。在看看内存分布

image-20211207111655882.png

  • 上面我们通过jol能够查看到对象在内存中分布情况。但是Java中还存在一种数据类型数组 。数组在内存中分布情况稍微不一样。

image-20211207112145287.png

  • 在对象头中除了markword和klass point以外还会存储数组长度。这里能够看到是4字节的。换句话说Java对象中数组长度最大是2^32(理论上)。
  • 我们知道数组中索引是int类型,而java中int类型最大值是(2^31)-1 。所以数组长度根本用不到2^32这个量级。另外针对数组长度JVM作出了限制最大是(2^31)-2 ;
  • 试想一下如果我们数组中存储的是基本单位最小长度就是4字节。2^31*4计算下来约等于16GB 。 在想想我们平时给JVM分配多大内存空间吧。所以数组长度限制在那个量级上足够我们使用。如果真的到达那个量级了也不是我们需要考虑的事情。首先虚拟机那关就挂了

markword

  • markword是对象内存模型中印出来的概念。由上我们得治在64位系统中markword占8字节。这8字节可是对象很重要的属性。包括对象HashCode、GC次数、锁标记等信息都是存储在markword中的。

image-20211207135807574.png

  • 从左到右依次是高位到低位的顺序。但是在我们的JOL输出的模型中是按照内存的顺序进行输出的,所以低位的反而是先输出。在加上我们是按字节为单位输出的。所以先输出低位字节。

image-20211207140055041.png

  • 比如说上面JOL输出内存模型。第一个输出的是01是十六进制表达式。而01对应的是8位。这8位对应的上图中末8位
  • 01后面的是00 , 他是第二个输出的字节,他对应的是倒数后16位到倒数后8位

image-20211207140936753.png

  • 一个普通对象markword就是上图所示。因为他既没有GC信息也没有产生hashcode ,更没有被锁住,所以内存是0000000000000001 。 注意这是高位到低位显示。下面我们试着调用hashcode试试

image-20211207143925519.png

  • 很是奇怪,在hashcode对应位并没有存储响应的hashcode , 上面已经打印出来hashcode为400c11fa 。 这是为什么呢?最终发现是又因为我的User类用的是lombok注解。修改下就可以了

image-20211207144108863.png

  • 另外我将user对象进行上锁,这时候我们在看看他的markword吧

image-20211207144238473.png

  • 通过观察最后三位,我们很清楚知道当前对象已经上锁且是轻量级锁。关于对象锁的生命周期我们后面详细说说。什么时间是偏向锁、如何转成轻量级锁、最终是重量级锁、还有什么叫自旋锁、无锁到底是不是锁等等问题我们下章见。

指针压缩

  • 上面对象内存分布中我们提到对象头这个概念。对象头=markword+klass point +array length;
  • 但是klass point所占字节是不固定的。如果开启了指针压缩那么他就小点为4字节;否则就是8字节
  • 为什么存在指针压缩?这就牵涉到32位系统和64位系统了

在32位到64位的转变中,我们能够直观的感受到内存容量的变化。在一个32位的系统中,内存地址的宽度就是32位,这就意味着,我们最大能获取的内存空间是2^32(也就是4G)字节。这个容量明显不够用!在一个64位的机器中,理论上,我们能获取到的内存容量是2^64字节,接下来,我们就谈谈compressed oops能帮我们做什么

image-20211207115857599

  • 通过上图我们能够得出几点结论

    1. 64位系统指针变大那么相同内存下存放的指针数量就变少了,同时存储的普通对象就会变少。很容易就触发了GC,可以理解因为内存被指针占用了
    2. 容器存储的指针数量变少了,就导致对用被引用的范围变小了。即CPU缓存命中率变低了
  • 针对上面存在的问题,64系统出现了指针压缩的这个概念;在JVM中我们可以通过-XX:-UseCompressedOops来设置指针压缩关闭。

  • 开启(-XX:+UseCompressedOops) 可以压缩指针。 关闭(-XX:-UseCompressedOops) 可以关闭压缩指针

  • 如果GC堆大小在 4G以下,直接砍掉高32位,避免了编码解码过程;
  • 如果GC堆大小在 4G以上32G以下,则启用 UseCompressedOop;
  • 如果GC堆大小 大于32G,压指失效,使用原来的64位(所以说服务器内存太大不好......)。

Guess you like

Origin juejin.im/post/7117060955175026695