【Java】JVM学习(四)

对象的分配

JVM中对象的创建过程

在这里插入图片描述

对象的内存分配

虚拟机遇到一条new指令时,首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程。

类加载就是把class加载到JVM的运行时数据区的过程。

1)检查加载

首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用(符号引用 :符号引用以一组符号来描述所引用的目标),并且检查类是否已经被加载、解析和初始化过。

符号引用 :符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,JAVA在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址(实际地址),就用符号引用来代替,而在类的解析阶段(后续JVM类加载会具体讲到)就是为了把这个符号引用转化成为真正的地址的阶段。

一个java类(假设为People类)被编译成一个class文件时,如果People类引用了Tool类,但是在编译时People类并不知道引用类的实际内存地址,因此只能使用符号引用(org.simple.Tool)来代替。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址。

2)分配内存

接下来虚拟机将为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

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

在这里插入图片描述
空闲列表
如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。
在这里插入图片描述
选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

如果是Serial、ParNew等带有压缩的整理的垃圾回收器的话,系统采用的是指针碰撞,既简单又高效。

如果是使用CMS这种不带压缩(整理)的垃圾回收器的话,理论上只能采用较复杂的空闲列表。

并发安全
除如何划分可用空间之外,还有另外一个需要考虑的问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

CAS机制
解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;

在这里插入图片描述

CAS(Compare And Swap)是一种并发编程中用于实现无锁算法的机制。在JVM中,CAS是一种原子操作,用于保证多个线程同时对某个共享变量进行读取、比较和更新时的一致性。

CAS操作包括三个步骤:比较当前值、如果相等则进行更新,否则重新尝试。这个过程是原子的,不会被其他线程的干扰打断。CAS操作通常使用在并发环境下对共享变量进行原子性的操作,比如对计数器、标志位等进行更新。

在JVM中,CAS操作借助于底层硬件的支持来实现。它使用了处理器提供的原子指令(例如compare-and-swap指令),能够在特定的CPU指令级别上实现原子性的读取、比较和更新操作。当多个线程同时进行CAS操作时,只有一个线程能够成功执行更新操作,其他线程需要重新尝试或采取其他策略。

CAS的优势在于它避免了使用锁的开销,提高了并发性能,并且没有死锁的风险。然而,CAS也存在一些问题。由于CAS操作是基于当前值进行比较的,因此在高并发情况下,如果多个线程同时进行CAS操作并竞争同一个共享变量,可能会导致大量的重试和自旋,降低效率。

为了解决CAS操作的ABA问题(即:在比较值的过程中,共享变量可能被其他线程修改为相同的值,从而导致误判),JVM提供了java.util.concurrent.atomic包下的原子类,例如AtomicInteger、AtomicLong等,它们提供了CAS操作的封装,能够更方便地进行原子性操作。

分配缓冲

另一种是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块私有内存,也就是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),JVM在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer,如果需要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer容量不够的时候,再重新从Eden区域申请一块继续使用。

TLAB的目的是在为新对象分配内存空间时,让每个Java应用线程能在使用自己专属的分配指针来分配空间,减少同步开销。

TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。当一个TLAB用满(分配指针top撞上分配极限end了),就新申请一个TLAB。

3)内存空间初始化

(注意不是构造方法)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(如int值为0,boolean值为false等等)。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

4)设置

接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息(Java classes在Java hotspot VM内部表示为类元数据)、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头之中。

5)对象初始化

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。所以,一般来说,执行new指令之后会接着把对象按照程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算完全产生出来。

对象的内存布局

在这里插入图片描述

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

如果对象是一个java数组,那么在对象头中还有一块用于记录数组长度的数据。

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对对象的大小必须是8字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。

对象的访问定位
建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。

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

使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改.

直接指针
如果使用直接指针访问, reference中存储的直接就是对象地址。

这两种对象访问方式各有优势,使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

对Sun HotSpot而言,它是使用直接指针访问方式进行对象访问的。

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

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

相关文章

低功耗蓝牙OM6621EM 兼容Nordic 51系列2.4G私有协议

OM6621EM是一个功率优化的系统(SOC).解决蓝牙低功耗和专有的2.4 ghz应用。它集成了一个高具有蓝牙基带和丰富外设的低功耗射频收发器I0扩展。OM6621EM还集成了电源管理单元(PMU)来提供高效的电源管理。它的目标是2.4GHz低功耗蓝牙系统,专有的2.4 ghz系统&#xff0c…

LLM大模型应用开发的本地环境搭建

尽管 ChatGPT 仍然很受欢迎,但泄露的 Google 内部文件表明开源社区正在迎头赶上并取得重大突破。 我们现在能够在消费级 GPU 上运行大型 LLM 模型。 因此,如果你是一名开发人员,想要在本地环境中尝试这些 LLM 并用它构建一些应用程序&#x…

与chagpt对话记录

每日chagpt对话记录 关注我一下 vscode 浏览器版本 c 函数 无法跳转 C/C IntelliSense, debugging, and code browsing. C/C IntelliSense、调试和代码浏览是指在使用VS Code进行C/C开发时的一些核心功能。下面是对这些功能的简要说明: IntelliSense(智能…

android的PopupWindow透明弹窗

1.要实现这种效果 2.可以使用这种方式 View v LayoutInflater.from(mContext).inflate(R.layout.ceshi_01, null);PopupWindow popupWindow new PopupWindow(v, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, true);popupWindow.showAsDropDo…

深入理解深度学习——GPT(Generative Pre-Trained Transformer):GPT-2与Zero-shot Learning

分类目录:《深入理解深度学习》总目录 相关文章: GPT(Generative Pre-Trained Transformer):基础知识 GPT(Generative Pre-Trained Transformer):在不同任务中使用GPT GPT&#x…

Unity 之 使用后处理的方式实现暗角效果

Unity 之 后处理URP工程实现边角压暗效果 一,URP工程配置二,代码调用三,实现原理 一,URP工程配置 在Hierarchy界面,创建空物体 GameObject,右键选择Volume菜单下的Global Volume。 创建后的结果&#xff1…

Cannot find declaration to go to 本地环境可以跳转至该页面,但是测试环境跳转不了,记录一下

错误示例: 且前台界面点击该页面,无反应 正确示例: 问题所在: 错误示例中用了 ,虽然本地环境可以运行,但是测试环境识别不了。应该用’引起来

Cortex-M内核知识点总结

总览 Cortex内核 基础 寄存器组 程序在经过编译后,生成可执行二进制文件,如上图,是截取某个函数在flash中存储的内容 (反汇编文件)可以看到以下信息: 指令的存储地址 ,指令的二进制内容 , 指令代表的汇编类…

centos版本的EDA虚拟机搭建3

文章目录 0、参考博客1、配置虚拟机与主机共享文件夹。2、安装unrar和rar3、EDA软件正式安装4、gtkwave与iverilog安装5、安装vscode6、安装wine软件7、notepad安装 0、参考博客 1、CentOS 7 下 rar unrar的安装 1、配置虚拟机与主机共享文件夹。 **前提,虚拟机关…

GLP-1爆火2023:神药显雏形,争夺引内卷

2023年过半,如果要从创新药角度做一份总结,什么赛道、哪类药物会是“当红炸子鸡”?答案一定是GLP-1类药物。 原本用于治疗二型糖尿病的药物,在国内社交媒体上,关于司美格鲁肽的减肥奇效,甚至引发了一股抢药…

css对盒模型的理解

面试碰到的一个问题,记录一下 ’ CSS3中的盒模型有以下两种:标准盒子模型、IE盒子模型 盒模型都是由四个部分组成的: 分别是margin、border、padding和content。 标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同&#…

谷歌Play应用商店下架具有内置自行下载APK能力的应用

近日有程序员在V2EX论坛发帖表示自己用开源框架uni-app开发的App被 Google Play下架,而根据这位网友的说法,下架原因疑似是uni-app自带的SDK包含违反Google Play政策的“内置自行下载APK能力”代码及广告相应的代码。 据悉,uni-app是一个使用…