java对象在内存中如何分布 | java上锁原来就是内存占位

本文探讨了Java对象在内存中的布局,包括markword、klasspoint、数组长度等组成部分,以及在64位系统中的指针压缩。通过JOL工具展示了对象和数组的内存分布,并解释了markword中的锁状态和hashCode。此外,文章还讨论了指针压缩在64位系统中的作用和使用条件,以及其对内存使用和GC的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

new
openjdk
 <dependency>
     <groupId>org.openjdk.jol</groupId>
     <artifactId>jol-core</artifactId>
     <version>0.9</version>  
 </dependency>

对象分布

  • 首先我们得明确一个Java对象在内存中是什么样的结构。这里有人可能会说能有啥结构。就是内存数据二进制存储呗。这样的回答好像说了又好像没说。
  • 这里我们通过一个demo案例来具体讲解。首先我们系统中存在这么一个对象
 @Data
 public class User {
     private int age;
 }
  • 一个 User 对象仅有一个Int类型的属性。他在内存中不仅仅是表述int类型的数据。图中应为kclass point

  • 对于一个普通的Java对象就是上述的数据结构存储在内存中的。在64位系统中markword站8字节;klass point在不开启指针压缩的前提下占8字节,否则占位4字节;这里的内容就是我们对象中的属性,因为Java中属性都是有类型的而每种类型占位也是不一样的,比如上面我们int类型是占位4字节;至于补齐字节是啥意思呢?在64位中要求一个对象在内存中所占字节必须是8的倍数。

  • 按照我们补齐字节的公式,能够计算出针对User这个对象在内存中补齐字节是0 ,因为他本身占16字节是8 的倍数。至于为什么是8的倍数呢?一个对象在64为系统中占用字节数必须是8的倍数。因为8B=64b;针对字节单位我整理如下表格
1B8b
1KB1024B
1MB1024KB
1GB1024MB
  • 而我们所说的字节就是B , 计算机最小单元是位就是b。
  • 这里我们通过jol来看下

  • 通过JOL我们也可以测测Null对象占多少字节。大家都知道是4字节这里就不测试了
  • 然后我们给User对象增加一个double属性。在看看内存分布

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

  • 在对象头中除了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中的。

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

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

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

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

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

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

指针压缩

markword+klass point +array length
klass point

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值