JVM虚拟机对象探秘

对象的创建

Java是一门面向对象的编程语言,创建对象通常只是通过new关键字。

对象创建过程

当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到
一个类的符号引用,并且检查这个符号引用(类全名)代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的 类加载过程。
在类的检查通过过后,接下来虚拟机就会 为新生成对象分配内存 。对象所需要的内存大小在类加载的时候决定。(对象内存分配后面将有独立的一小段讲解)。

内存分配完成后,虚拟机会将这块分配到的 内存空间(不包括对象头)都初始化为零值 ,如果使用了TLAB的话,这一项工作也可以提前至TLAB分配时顺便进行。这步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型所对应的零值。(不同数据类型的默认值不同),保证不赋值可以使用---->0/NULL/false

接下来虚拟机还需要进行 对象进行初始化设置 ,比如元数据(对象是那个类的实例)、对象的哈希编码、对象的GC分代年龄、偏向锁状态等信息这些信息都用于存放到对象头(Object Header)中。

完成上述流程,其实已经完成了虚拟机中内存的创建,但是我们在Java执行new创建对象的角度才刚刚开始,我们还 需要调用构造方法初始化对象(可能还需要在此前后调用父类的构造方法、初始化块等) 。进行Java对象的初始化,即在.class 的角度是调用<init>()方法。如果构造方法中还有调用别的方法,那么别的方法也会被执行,当构造方法内的所有关联的方法都执行完毕后,才真正算是完成了Java对象的创建。
 

对象分配内存

为对象分配空间的任务实际上便等同于把一块确定大小的内存块从Java堆中划分出来。

 假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump The Pointer)。

如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。

那什么时候使用指针碰撞?什么时候使用空闲列表呢? 

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。因此,当使用Serial、ParNew等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效;而当使用CMS这种基于清除 (Sweep)算法的收集器时,理论上就只能采用较为复杂的空闲列表来分配内存。

对象分配内存过程属于非原子操作,并发环境高频率创建对象如果保证线程安全?

对象创建在虚拟机中是非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。解决这个问题 有两种可选方案: 
  • (1)对分配内存空间的动作进行同步处理——实际上虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性
  • (2)把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定

对象的内存布局

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例
数据(Instance Data)和对齐填充(Padding)

对象头结构

HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如
希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的最大限度,但对象头里的信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个有着动态定义的数据结构,以便在极小的空间内存储尽量多的数据,根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中,如对象未被同步锁锁定的状态下,Mark Word的32个比特存储空间中的25个比特用于存储对象哈希码,4个比特用于存储对象分代年龄,2个比特用于存储锁标志位,1个比特固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下图所示。

对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针
来确定该对象是哪个类的实例
。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身。此外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。

两个指针变量说明:

ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争时,JVM使用原子操作而不是OS互斥,这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的Mark Word中设置指向锁记录的指针。
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的 ptr_to_heavyweight_monitor设置指向Monitor的指针。

实例数据

实例数据部分是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(OrdinaryObject Pointers,OOPs),从以上默认的分配策略中可以看到,相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果HotSpot虚拟机的+XX:CompactFields参数值为true(默认就为true),那子类之中较窄的变量也允许插入父类变量的空隙之中,以节省出一点点空间。

对齐填充

对象的第三部分是对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作
用。
由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

对象的访问定位

对象成功创建后,如何访问定位到某一个对象?

创建对象自然是为了后续使用该对象,我们的Java程序会通过栈上的reference数据来操作堆上的具体对象。由于reference类型在《Java虚拟机规范》里面只规定了它是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

  • 如果使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息

  • 如果使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销

  • 使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference本身不需要被修改
  • 直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本


 

 


 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何设计微服务

一、序幕 最近在思考&#xff0c;自己哪些不足&#xff0c;需要学习点什么&#xff1f;看着Java基础知识&#xff0c;千遍一律&#xff0c;没有太大的动力需深挖&#xff0c;只能在写业务项目的时候边写边思考边夯实自己的基础。于是看了网上的一些资料&#xff0c;结合以前面试…

linux并发服务器 —— 多线程并发(六)

线程概述 同一个程序中的所有线程均会独立执行相同程序&#xff0c;且共享同一份全局内存区域&#xff1b; 进程是CPU分配资源的最小单位&#xff0c;线程是操作系统调度执行的最小单位&#xff1b; Linux环境下&#xff0c;线程的本质就是进程&#xff1b; ps -Lf pid&…

101序列检测器

本次所做设计&#xff0c;使用数字电路芯片实现的101序列检测器。电路图如下&#xff1a; 主要首先要根据需求画出状态转移方程&#xff0c;然后写出它的逻辑表达式。最后根据所选触发器种类确定电路图。序列由按键控制输入&#xff0c;按键按下&#xff0c;代表输入1 &#xf…

RT-Thread UART

UART 简介 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;通用异步收发传输器&#xff0c;UART 作为异步串口通信协议的一种&#xff0c;工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。 UART …

Error: Cannot find module ‘timers/promises‘

这个错误很要命 他会导致你本机所有的npm 命令全部报错 首先 这个错误是因为 npm 与 node版本不匹配导致的 最简单的办法就是 查一下你安装的这个npm 的版本适配那个版本的 node 然后将本地的node删除 控制面板写在node 然后去官方文档现在与本地npm 匹配的node.js 这样 你执行…

【LeetCode-中等题】146. LRU 缓存

文章目录 题目方法一&#xff1a;直接继承LinkedHashMap调用api方法二&#xff1a;自定义LinkedHashMap HashMap ListNode LinkedHashMap 题目 LRU缓存是什么&#xff1a;LRU缓存机制&#xff0c;你想知道的这里都有 实现 LRU 缓存算法 方法一&#xff1a;直接继承Linked…

彻底学会Unity从网上加载资源到场景

使用类WWW 该类实例化的对象可以存储多种多媒体资源&#xff0c;只需要在构造函数中附上可访问的资源链接 Unity 中&#xff0c;WWW 类用于实例化互联网上的资源&#xff0c;如文本、图像、音频和视频等。WWW 实例化的对象可以存储多种多媒体素材。以下是一些常见的例子&…

固定资产卡片乱怎么管理

固定资产卡片是记录公司固定资产信息的重要工具&#xff0c;如果管理不善&#xff0c;容易造成卡片混乱、数据错误等问题。 为了避免这种情况的发生&#xff0c;可以采取以下措施&#xff1a;  建立完善的资产管理制度&#xff0c;明确固定资产的分类、标准和使用情况&#x…

C# 中什么是重写(子类改写父类方法)

方法重写是指在继承关系中&#xff0c;子类重新实现父类或基类的某个方法。这种方法允许子类根据需要修改或扩展父类或基类的方法功能。在面向对象编程中&#xff0c;方法重写是一种多态的表现形式&#xff0c;它使得子类可以根据不同的需求和场景提供不同的方法实现。 方法重…

热点探测技术架构设计与实践

1. 概述 说到热点问题&#xff0c;首先我们先理解一下什么是热点&#xff1f; 热点通常意义来说&#xff0c;是指在一段时间内&#xff0c;被广泛关注的物品或事件&#xff0c;例如微博热搜&#xff0c;热卖商品&#xff0c;热点新闻&#xff0c;明星直播等等&#xff0c;所以…

You must install at least one postgresql-client-<version> package

使用主机上的映射端口来连接到 PostgreSQL 数据库。例如&#xff0c;使用以下命令连接到数据库&#xff1a; psql -h localhost -p 5432 -U postgres出现下面的问题&#xff1a; 分析&#xff1a; 如果您在运行 psql 命令时遇到错误消息 You must install at least one pos…

应用案例 | 3D视觉引导解决方案汽车零部件上下料

Part.1 行业背景 三维视觉引导技术在国内外汽车零部件领域得到了广泛应用。随着汽车制造业的不断发展和创新&#xff0c;对于零部件的加工和装配要求越来越高&#xff0c;而三维视觉引导技术能够帮助企业实现更精确、更高效的零部件上下料过程。 纵览国外&#xff0c;部分汽车…

若依 MyBatis改为MyBatis-Plus

主要内容&#xff1a;升级成mybatis-plus&#xff0c;代码生成也是mybatis-plus版本 跟着我一步一步来&#xff0c;就可完成升级&#xff01; 检查&#xff1a;启动程序&#xff0c;先保证若依能启动 第一步&#xff1a;添加依赖 这里需要在两个地方添加&#xff0c;一个是最…

软件游戏丢失d3dcompiler_47.dll怎么办?这个几个解决方法可修复

当我们在玩软件游戏时&#xff0c;有时候会出现丢失 d3dcompiler_47.dll 的问题&#xff0c;这让我们感到非常困扰。d3dcompiler_47.dll 是 DirectX 中的一个重要组件&#xff0c;如果它丢失了&#xff0c;那么很多游戏就无法正常运行。我将和大家分享一下我在解决软件游戏丢失…

计算机竞赛 基于深度学习的中文情感分类 - 卷积神经网络 情感分类 情感分析 情感识别 评论情感分类

文章目录 1 前言2 情感文本分类2.1 参考论文2.2 输入层2.3 第一层卷积层&#xff1a;2.4 池化层&#xff1a;2.5 全连接softmax层&#xff1a;2.6 训练方案 3 实现3.1 sentence部分3.2 filters部分3.3 featuremaps部分3.4 1max部分3.5 concat1max部分3.6 关键代码 4 实现效果4.…

小白学go基础03-了解Go项目的项目结构

我们先来看看第一个Go项目——Go语言自身——的项目结构是什么样的。Go项目的项目结构自1.0版本发布以来一直十分稳定&#xff0c;直到现在Go项目的顶层结构基本没有大的改变。 截至Go项目commit 1e3ffb0c&#xff08;2019.5.14&#xff09;&#xff0c;Go1.0 项目结构如下&am…

Ansible学习笔记15

1、roles&#xff1a;&#xff08;难点&#xff09; roles介绍&#xff1a; roles&#xff08;角色&#xff09;&#xff1a;就是通过分别将variables&#xff0c;tasks及handlers等放置于单独的目录中&#xff0c;并可以便捷地调用他们的一种机制。 假设我们要写一个playbo…

KaTex用法

KaTeX是一个用于数学公式渲染的JavaScript库&#xff0c;可以在网页上方便地显示数学符号和公式。下面是KaTeX的使用方法&#xff1a; 在网页中引入KaTeX的CSS和JS文件&#xff1a; <link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/Ka…

【真题解析】系统集成项目管理工程师 2023 年上半年真题卷(综合知识)

本文为系统集成项目管理工程师考试(软考) 2023 年上半年真题(全国卷),包含答案与详细解析。考试共分为两科,成绩均 ≥45 即可通过考试: 综合知识(选择题 75 道,75分)案例分析(问答题 4 道,75分)综合知识(选择题*75)1-10 题11-20 题21-30 题31-40 题41-50 题51-60 …

webpack(三)loader

定义 loader用于对模块的源代码进行转换&#xff0c;在imporrt或加载模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定文件中。 在webpack内部&#xff0c;任何文件都是模块&#x…