JVM-对象布局

news2025/1/11 22:50:51

JVM中对象布局

  通过引入JOL工具,查看对象在JVM中的布局。

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

对象的在JVM中的基本信息

  普通对象:对象头,实例数据,对其填充。
  数组对象:对象头,实例数据,数组长度,对其填充

JOL使用

在代码中使用JOL提供的方法查看JVM信息:

System.out.println(VM.current().details());

jvm信息

查看对象信息

  创建一个普通类。

public class TestOne {
    private String name = "对象布局";
    private Boolean edit;
    private List<String> dd;
    String defaultName;
    protected String protectedName;
    public String publicName;
    private int sex;
    private long likes;
    private static String staticName;
    private final String finalName = "fire";


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Boolean getEdit() {
        return edit;
    }

    public void setEdit(Boolean edit) {
        this.edit = edit;
    }

    public List<String> getDd() {
        return dd;
    }

    public void setDd(List<String> dd) {
        this.dd = dd;
    }

    public String getDefaultName() {
        return defaultName;
    }

    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

    public String getProtectedName() {
        return protectedName;
    }

    public void setProtectedName(String protectedName) {
        this.protectedName = protectedName;
    }

    public String getPublicName() {
        return publicName;
    }

    public void setPublicName(String publicName) {
        this.publicName = publicName;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public long getLikes() {
        return likes;
    }

    public void setLikes(long likes) {
        this.likes = likes;
    }
}

  打印这个类的对象的布局。

    public static void main(String[] args) {
    /*    TestOne testOne = new TestOne();
        String publicName = testOne.publicName;

        TestPowerChildren testPowerChildren = new TestPowerChildren();
        String publicName1 = testPowerChildren.publicName;*/
      /* Integer a = 2256;
       Integer b = 2256;
       if (a == b) {
           System.out.println("正常");
       } else {
           System.out.println("卧槽");
       }*/
      //System.out.println(VM.current().details());

        TestOne testOne = new TestOne();
        System.out.println(ClassLayout.parseInstance(testOne).toPrintable());
    }

  结果如下(如果子类继承父类,父类的属性也会保存在子类的实例数据中):

Connected to the target VM, address: '127.0.0.1:54320', transport: 'socket'
com.song.owrntest.TestOne object internals:
 OFFSET  SIZE                TYPE DESCRIPTION                               VALUE
      0     4                     (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                     (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                     (object header)                           92 c3 00 f8 (10010010 11000011 00000000 11111000) (-134167662)
     12     4                 int TestOne.sex                               0
     16     8                long TestOne.likes                             0
     24     4    java.lang.String TestOne.name                              (object)
     28     4   java.lang.Boolean TestOne.edit                              null
     32     4      java.util.List TestOne.dd                                null
     36     4    java.lang.String TestOne.defaultName                       null
     40     4    java.lang.String TestOne.protectedName                     null
     44     4    java.lang.String TestOne.publicName                        null
     48     4    java.lang.String TestOne.finalName                         (object)
     52     4                     (loss due to the next object alignment)
Instance size: 56 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Disconnected from the target VM, address: '127.0.0.1:54320', transport: 'socket'

Process finished with exit code 0

  1. OFFSET:偏移地址,单位为字节。
  2. SIZE:占用内存大小,单位为字节。
  3. TYPE:Class(类)中定义的类型
  4. DESCRIPTION:类型描述,object header 标识对象头,alignment标识对齐填充。
  5. VALUE:对应内容中存储的值。

  通过结果,我们可以看的很清楚。有一个点就是我们定义的静态变量staticName并没有在对象头里面,它的大小是不计算在对象中的。因为静态变量属于类而不是属于某一个对象的

对象的头信息

  对象的头信息包含mark word,klass pointer。
  mark word:对象运行时的状态数据。
  klass pointer:对象所属类的引用指针。

扩展。synchronized锁升级

锁升级

对象头中的运行时数据mark word

对象头mark word
  锁的状态有后三位标识。
  001:无锁
  101:偏向锁
  00:轻量级锁
  10:重量级锁

  在jdk1.6之前,通过synchronized关键字枷锁时使用无差别的中重量级锁。所有线程串行执行,并且cpu在用户态和核心态之间频繁切换。
  后面随着对synchronized的不断优化,提出了锁升级的概念,并引入了偏向锁,轻量级锁,重量级锁。在mark word中,锁(lock)标志位占用2个bit,结合1个bit偏向锁(biased_lock)标志位,这样通过倒数的3位,就能用来标识当前对象持有的锁的状态。

无锁状态,偏向锁

  锁对象刚创建的时候,没有任务锁竞争,对象处于无锁状态。可以通过jol工具查看对象的内存布局。根据大小端(高位在低地址,地位在高地址)。看到锁的状态。001
在这里插入图片描述

  因为jdk中偏向锁存在延迟4秒启动。就是说JVM启动后,4秒后创建的对象才会开启偏向锁。可以先通过参数:
-XX:BiasedLockingStartupDelay=0,关闭这个设置。再打印对象的内存布局。101。表示当前对象
的锁没有被持有,并且处于可被偏向的状态。
在这里插入图片描述

  当不存在锁竞争的条件下,第一个获取对象锁的线程通过CAS将自己的threadId写入到锁对象的mark word中。若后续这个线程再次获取锁,需要比较当前线程的threadId和锁对象mark word中的threadId是否一致。如果一致,直接获取到锁。

总结

  偏向锁通过消除资源无竞争情况下的同步原语,提高了单线程下访问同步资源的运行性能。当出现多个线程竞争的时候,就会撤销偏向锁,升级为轻量级锁。研究表明,大部分情况下,都是同一个线程获取锁对象。

  锁过程就是,锁对象中通过CAS设置当前线程的threadId,设置锁对象中的锁状态为101,当前线程释放锁,锁对象mark word不会改变。直到出现不同的线程获取锁对象,比较锁对象的threadId不是当前线程。升级为轻量级锁。

轻量级锁

  当存在两个及以上线程获取锁对象的锁的时候,偏向锁升级为轻量级锁。
  如果锁对象处于无锁不可偏向状态,JVM首先将当前线程的栈帧中创建一条锁记录,用于存放锁对象的mark word的拷贝(displaced mark word) + 指向当前的锁对象的指针(owner)。在拷贝mark word完成后,首先会挂起线程。JVM使用CAS操作尝试将对象的mark word中的lock record指针指向栈帧中的锁记录,并将栈帧中的锁记录中的owner指针指向锁对象的mark word
在这里插入图片描述
  如果CAS替换成功,表示竞争锁对象成功。锁对象中的锁标识设置为00,标识锁对象处于轻量级锁状态。然后线程执行同步代码块中的逻辑。
  如果CAS替换失败,则判断当前锁对象的mark word是否指向当前线程的栈帧。
  (1)指向当前线程的栈帧,表示当前线程已经持有对象的锁,执行的是synchronized的锁重入过程,可以直接执行同步代码块逻辑。
  (2)指向的不是当前线程的栈帧,说明其他线程已经持有了该对象的锁。当前线程就会自旋一定次数,尝试获取锁。如果自旋一定次数后,还未获取到锁,轻量级锁需要升级为重量级锁,锁对象的标识为10。后面等待的线程将会进入阻塞状态

  轻量级锁释放同样使用了CAS操作,尝试将displaced mark word替换回锁对象的mark word。这时需要检查锁对象的mark word中lock record指针是否指向当前线程的栈帧所记录。
  (1)替换成功,表示没有锁竞争,整个同步过程完成。
  (2)替换失败,表示当前锁资源存在竞争,有可能其他线程在这段时间里尝试过获取锁失败,导致自身被挂起,并修改了锁对象的mark word升级为重量级锁,最后在执行重量级锁的解锁流程后唤醒被挂起的线程

public static void main(String[] args) throws InterruptedException {
    TestOne testOne=new TestOne();
    synchronized (testOne){
        System.out.println(ClassLayout.parseInstance(testOne).toPrintable());
    }
    Thread thread = new Thread(() -> {
        synchronized (testOne) {
            System.out.println("--THREAD--:"+ClassLayout.parseInstance(testOne).toPrintable());
        }
    });
    thread.start();
    thread.join();
    System.out.println("--END--:"+ClassLayout.parseInstance(testOne).toPrintable());
}

在这里插入图片描述
  偏向锁升级为轻量级锁,在执行完成同步代码后释放锁,变为无锁状态。当再次被线程获取到锁的时候,直接变为轻量级锁(就不会先偏向锁,再轻量级锁)。

总结

  轻量级锁是通过CAS来避免开销较大的互斥操作。轻量级锁的轻量是相对与重量级锁而言的。轻量级锁尝试利用CAS,在升级为重量级锁的之前进行补救,目的就是为了减少多线程进入互斥。jvm使用轻量级锁来保证同步,避免线程切换的开销,不会造成用户态与内核态的切换。但是如果过度自旋,会引起cpu资源的浪费,这种情况下轻量级锁消耗的资源可能反而会更多。

  加锁过程:当前线程在栈空间的栈帧中保存锁对象的mark word。当前线程挂起,JVM使用CAS操作尝试将对象的mark word中的lock record指针指向栈帧中的锁记录,并将栈帧中锁记录中的owner指针指向锁对象的mark word
  释放锁过程:JVM尝试将displaced mark word替换回锁对象的mark word,并校验锁对象中的lock record是否指向当前线程的锁记录。如果是,流程结束。如果不是,说明存在锁竞争,其他线程在自旋一定次数后还未获取到锁,升级为重量级锁,其他线程阻塞挂起。需要唤醒其他线程。

重量级锁

  当获取锁后,调用锁对象的wait()方法后,直接从偏向锁升级为重量级锁,释放锁后,锁状态变为无锁状态。wait()方法调用过程中依赖与重量级锁中与对象关联的monitor。在调用wait()方法后monitor会把线程变为WAITING状态,所以才会强制升级为重量级锁。调用hashCode方法时也会使偏向锁直接升级为重量级锁。

  重量级锁是依赖锁对象内部的monitor来实现的,而monitor又依赖与操作系统底层的Mutex lock(互斥锁)实现。这也就是为什么重量级锁比较重的原因。操作系统在实现线程之间的切换时,需要从用户态切换到内核态,成本非常高。

monitor

在这里插入图片描述
  ower:拥有该monitor的线程,初始时和锁被释放后都为null。
  cxq:竞争队列,所有竞争锁失败的线程都会首先被放入这个队列中。
  EntryList:候选者列表,当ower解锁时会将cxq队列中的线程移动到该队列中。
  OnDeck:将线程从cxq移动到EntryList时,回指定某个线程为Ready状态(即OnDeck),表明它可以竞争锁,如果竞争成功那么成为Owner线程,如果失败则放回EnTryList中。
  WaitSet:获取到锁后调用wait() 或 wait(time)方法而被阻塞的线程会被放入到该队列中。
  count:monitor的计数器,数值加1表示当前对象的锁被一个线程获取,线程释放monitor对象时减1
  recursions:线程重复次数

原理

  当升级为重量级锁,锁对象的mark word中指针不在指向线程栈中的lock record。而是指向堆中与锁对象关联的monitor对象。
  当多个线程尝试获取锁对象时,其实就是获取monitor对象的所有权。获取成功,执行同步代码块逻辑。获取失败,当前线程会被阻塞,等待其他线程释放锁后唤醒,再次竞争获取锁对象。
  在重量级锁的情况下,加解锁过程都涉及到操作系统Mutex Lock进行互斥操作,线程间的调度和线程的状态变更过程需要在用户态和内核态之间进行切换,会导致消耗大量的cpu资源,导致性能降低。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/627468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iconfont字体的引用

官网&#xff1a;giconfont-阿里巴巴矢量图标库iconfont-国内功能很强大且图标内容很丰富的矢量图标库&#xff0c;提供矢量图标下载、在线存储、格式转换等功能。阿里巴巴体验团队倾力打造&#xff0c;设计和前端开发的便捷工具https://www.iconfont.cn/1. 搜索图标&#xff0…

【正点原子STM32连载】 第三十章 ADC实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第三十…

2023最新项目管理软件排行榜!快来了解各大品牌优缺点

项目管理是一个复杂而又多样化的过程。在这个过程中&#xff0c;管理者需要处理大量的信息&#xff0c;规划资源&#xff0c;分配任务和监督进展。为了更好地管理项目&#xff0c;许多企业和组织开始采用项目管理软件。这些软件不仅可以帮助管理者更好地处理信息&#xff0c;还…

让你不再疑惑怎么转换音频格式

你是否有过这样的经历&#xff1f;听到了一首喜欢的歌曲&#xff0c;但是只能在特定的平台或设备上播放&#xff0c;因为它只存在于视频网站或某个应用程序中。或者你想要将一首歌曲作为铃声或者用于其他用途&#xff0c;但是音频文件的格式却不被平台所支持。这是&#xff0c;…

3D数字孪生技术在水利工程上面的应用价值

"农功今可济,水利更毋隳。”水利作为国民经济稳定和谐的重要部分&#xff0c;运用科技化手段对水利项目进行管理&#xff0c;能完美契合智慧水利灾害管理与防治所需。深圳华锐视点利用数字孪生和三维可视化技术搭建的智慧水利可视化管理平台&#xff0c;通过web3d开发建模…

软考A计划-系统架构师-官方考试指定教程-(10/15)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

传统ERP软件如何SaaS化?

01 ERP和SaaS ERP概念 企业资源计划 (ERP) 系统是许多企业的主干&#xff0c;助力管理整个企业内的会计、采购流程、项目等。对于许多 IT 部门而言&#xff0c;ERP 系统通常意味着大型、昂贵且耗时的部署&#xff0c;并可能需要进行大量硬件或基础设施投资。然而&#xff0c;云…

【算法基础】拓扑排序及实战

一 、概览 这里涉及到图的概念&#xff0c;感兴趣的同学请移驾 –>图<– 下面还有两个相关概念&#xff0c;大概说一下&#xff1a; 1.1 有向无环图 定义&#xff1a;在图论中&#xff0c;如果一个有向图从任意顶点出发无法经过若干条边回到该点&#xff0c;则这个图是…

测试者必知—如何做Web测试?常见测试点总结

目录 前言&#xff1a; 一、Web应用程序 二、功能测试 三、易用性测试&#xff08;界面测试&#xff09; 四、兼容性测试 五、安全性测试 六、性能测试 前言&#xff1a; Web测试是指对基于Web技术的应用程序进行测试&#xff0c;以测试其功能、性能、安全和稳定性等方面的表…

C++使用boost::serialization进行序列化

废话 发现一个比较好玩的东西&#xff0c;boost::serialization序列化操作&#xff0c;简单来说感觉像ofstream和ifstream的升级版&#xff0c;Boost.Serialization 库能够将c项目中的对象转换为一序列的比特&#xff08;bytes&#xff09;&#xff0c;用来保存和加载还原对象…

PPT里的逆天功能之-PPT扣图(可以秒杀PS的扣图功能)

PPT的扣图功能秒杀PS PPT其实非常强大,它里面有数不甚数的功能是我们之前从未使用过的。比如说PPT的扣图功能。比如说我们有一个背景是灰黑的。 而我们网上可以下到的一些logo很多是带有底色的,它会和我们的背景形成很大的反差。此时我们往往就会用PS工具。这就会让大都IT们…

leetcode数据库题第四弹

leetcode数据库题第四弹 619. 只出现一次的最大数字620. 有趣的电影626. 换座位627. 变更性别1045. 买下所有产品的客户1050. 合作过至少三次的演员和导演1068. 产品销售分析 I1070. 产品销售分析 III1075. 项目员工 I1084. 销售分析III小结 619. 只出现一次的最大数字 https:/…

MySQL的JDBC编程详解

1.JDBC编程概念 在实际项目中&#xff0c;我们对于数据库的操作大部分是通过代码来完成的&#xff0c;各种数据库在开发的时候&#xff0c;都会提供一组编程接口API。所以不同公司使用不同的数据库软件&#xff0c;那么程序员学习成本是非常大的&#xff0c;因为要学习不同数据…

超好用的5款软件,每一款都让你爱不释手

分享爱&#xff0c;分享时光&#xff0c;分享精彩瞬间&#xff0c;大家好&#xff0c;我是互联网的搬运工&#xff0c;今天继续给大家带来几款好用的软件。 1.GIF录制——LICEcap ​ LICEcap是一款用于录制屏幕为GIF动画的工具。它可以让你用一个可调整的窗口来捕捉你的屏幕上…

2023年前端面试高频考点HTML5+CSS3

目录 浏览器的渲染过程⭐⭐⭐ CSS 、JS 阻塞 DOM 解析和渲染 回流&#xff08;重排&#xff09;和重绘⭐⭐ 选择器 ID选择器、类选择器、标签选择器&#xff08;按优先级高到低排序&#xff09;⭐⭐ 特殊符号选择器&#xff08;&#xff1e;,,~&#xff0c;空格&#xff0…

【JavaEE】网络编程之TCP套接字

目录 1、TCP套接字 1.1、ServerSocket 常用API 1.2、Socket 常用API 2、基于TCP套接字实现一个TCP回显服务器 2.1、服务器端代码 2.2、客户端代码 2.3、解决服务器不能同时和多个客户端建立链接的问题 3、基于TCP socket 写一个简单的单词翻译服务器 1、TCP套接字 T…

【中文编程】青语言

【引言】 青语言主页&#xff1a;https://qingyuyan.cn 青语言文档&#xff1a;https://doc.qingyuyan.cn 青语言社区&#xff1a;https://forum.qingyuyan.cn 青语言仓库&#xff1a;https://gitee.com/NjinN/Qing 长久以来&#xff0c;中文编程一直是开发者社区中争议不断的…

opencv读写png

[1] 测试了怎么手动加 alpha 通道设置透明度后&#xff0c;用 PIL.Image 存 png&#xff0c;通道顺序是 RGBA。这里测试用 opencv 读、写 3、4 通道的 png。 png 可以只存 3 通道的&#xff0c;即不要 alpha&#xff0c;也可以加上 alpha。而无 alpha 时 opencv 的通道顺序是 …

微信视频号加强打击肖像授权侵权短视频

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 视频号安全中心发布公告称&#xff1a;视频号将打击肖像权和侵权的短视频&#xff0c;并在7月份上线“视频授权功能”。 5月份视频号已经下架了3万多条视频&#xff0c;1万多个帐号减少推荐。你看3…

Spring框架-面试题核心概念

目录 1.Spring框架的作用是什么&#xff1f; 2. 什么是DI&#xff1f; 3.什么是AOP&#xff1f; 4.Spring常用注解 5.Spring中的设计模式 6.Spring支持的几种bean的作用域 7.Spring中Bean的生命周期&#xff1f; 8.Spring中的事务管理 9.Spring中的依赖注入方式有几种 10.Sprin…