【jvm系列-06】深入理解对象的实例化、内存布局和访问定位

news2024/11/19 8:38:14

JVM系列整体栏目


内容链接地址
【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460
【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963
【三】运行时私有区域之虚拟机栈、程序计数器、本地方法栈https://blog.csdn.net/zhenghuishengq/article/details/129684076
【四】运行时数据区共享区域之堆、逃逸分析https://blog.csdn.net/zhenghuishengq/article/details/129796509
【五】运行时数据区共享区域之方法区、常量池https://blog.csdn.net/zhenghuishengq/article/details/129958466
【六】对象实例化、内存布局和访问定位https://blog.csdn.net/zhenghuishengq/article/details/130057210

对象实例化、内存布局和访问定位

  • 一,对象实例化、内存布局和访问定位
    • 1,对象的实例化
      • 1.1,创建对象的几种方式
      • 1.2,对象创建的步骤
        • 1.2.1,判断对象对应的类是否加载,验证,准备,解析和初始化
        • 1.2.2,为对象开辟空间,分配内存
        • 1.2.3,处理并发问题
        • 1.2.4,对象初始赋值
        • 1.2.5,设置对象的对象头
        • 1.2.6,执行init方法进行初始化
    • 2,对象的内存布局
      • 2.1,对象头(Header)
      • 2.2,实例数据(Instance Data)
      • 2.3,代码示例
    • 3,对象的访问定位
    • 4,直接内存初体验(了解)

一,对象实例化、内存布局和访问定位

1,对象的实例化

创建对象的方式和创建对象的步骤主要有以下几种方式
在这里插入图片描述

1.1,创建对象的几种方式

在日常开发中,创建对象的方式主要有以下几种:

  • 最常见的方式:new 加构造器,如果构造器私有,可以通过静态访问,如单例模式,或者通过工厂加载
//new 构造器 创建对象
Object object = new Object();
//构造器静态私有,如典型的单例模式
Object object = Object.getObject()//工厂加载,SpringBean,SqlSessionBean
Object object = ObjectFactory.getObject();
  • 反射的方式:类的newInstance或者构造器的newInstance·
public class Invoke {
    public static void main(String[] args) {
        try {
            Class<?> clazz1 = Class.forName("com.tky.jvm.Invoke");
            //通过类构造器获取对象
            Constructor<?> constructor = clazz1.getConstructor();
            Invoke invoke1 = (Invoke)constructor.newInstance();
            //通过类名获取
            Class<Invoke> clazz2 = Invoke.class;
            Invoke invoke2 = clazz2.newInstance();
            //通过对象获取
            Invoke in = new Invoke();
            Class<? extends Invoke> clazz3 = in.getClass();
            Invoke invoke3 = clazz3.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 克隆的方式:clone的方式,不调用任何构造器,当前类需要实现Cloneable接口以及clone方法
/**
 * @author zhenghuisheng
 * @date : 2023/4/10
 */
@Data
public class Clone implements Cloneable {
    private Long id;
    private String username;
    private String password;

    @Override
    protected Clone clone() throws CloneNotSupportedException {
        return (Clone)super.clone();
    }
}

class TestClone{
    public static void main(String[] args) {
        Clone clone1 = new Clone();
        clone1.setId(1L);
        clone1.setUsername("zhenghuisheng");
        clone1.setUsername("123456");
        try {
            Clone clone2 = clone1.clone();
            System.out.println(clone2.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 反序列化的方式:从文件或者网络中获取二进制流,将二进制流转换成对象
//对象序列化
Student s = new Student("1","zhenghuisheng","18");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));
objectOutputStream.writeObject(s);
objectOutputStream.close();

//对象反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
Student student = (Student) inputStream.readObject();
  • 第三方库Objenesis
//构建 Objenesis 对象  Objenesis需要对应的pom依赖对象
Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator<Student> instantiator = objenesis.getInstantiatorOf(Student.class);
Student student = instantiator.newInstance();

1.2,对象创建的步骤

这里主要从执行的角度来分析这个对象创建的步骤,如上图所示,主要分为六个步骤来创建对象
在这里插入图片描述

1.2.1,判断对象对应的类是否加载,验证,准备,解析和初始化

虚拟机在遇到一条new指令的时候,首先会去检查这个指令的参数是否能在元空间的常量池中定位到一个类的符号,并且检查这个类是否经历过了加载、验证、准备、解析和初始化这个几个步骤。如果没有,那么类加载器还在双亲委派的模式下,使用当前类加载器的以 ClassLoader + package + class 为key进行查找对应的.class文件,如果没有找到对应的文件,则会抛出 ClassNotFoundException 异常,如果找到,则进行类加载,并生成对应的Class对象

1.2.2,为对象开辟空间,分配内存

首先需要计算对象占用空间的大小,接着在堆中划分一块内存给新对象,如果实例成员变量时引用变量,那么仅分配引用变量空间即可,即四个字节大小。如根据不同的基本数据类型其所占用的字节数,从而得知每个变量占多大的空间,最后将这些变量所需要的空间全部叠加在一起,得到的就是这个总空间的字节数。

而内存如果是规整的,那么虚拟机将采用的是 指针碰撞 的方式来为对象分配内存。如下图,就是将用过的内存放在一边,空闲的内存放在另外一边,中间放着一个分界点的指示器,内存分配就是将指针向空闲那边挪动,挪动的距离就是对象所需要的大小 ,而指针碰撞这种方式,取决于虚拟机的垃圾回收算法是否具有压缩功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPmoI7G5-1681101135139)(img/1680846919641.png)]

如果内存内部不是规整的,虚拟机内部就得维护一个列表来管理已使用的内存和未使用的内存,其方式被称为 空闲列表 。如下图,虚拟机内部维护了一张表,记录的是哪块内存时可用的,哪块内存时不可用的,然后在分配的时候,就从列表中找到一块足够打的空间划分给对象实例,并更新表上的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-enJCEOZ8-1681101135140)(img/1680847513344.png)]

1.2.3,处理并发问题

由于对象是在堆中创建,而堆又是共享区域,因此避免不了会出现这个并发的问题,而在堆内部,主要采用了两种方式来保证实例的安全性。

一种是采用CAS比较与交换的方式,失败则重试,区域加锁来保证更新的原子性,另一种是 每个线程预先分配一个 TLAB。主要是通过这两种方式来解决并发安全的问题。

1.2.4,对象初始赋值

这里进行一个默认的初始化。这样所有属性都有一个默认值,保证对象实例字段在不赋值时就可以使用。因此在方法内部,静态变量在准备阶段就进行了初始赋值,实例变量在分配空间的时候也进行了初始赋值,因此这两个可以直接使用变量,其他的变量如果没有进行显示的初始化,那么会出现直接编译失败的情况。

1.2.5,设置对象的对象头

将对象所属的类、对象的hashCode、GC信息、年龄、锁信息等存储在对象的对象头中。

1.2.6,执行init方法进行初始化

这里就行一个显示初始化,初始化工作才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆对象的首地址赋值给引用对象。因此一般来说,new 指令之后会接着就是执行方法,将对象按照程序员的意愿进行初始化,这样真正可用的对象才算完整的创建出来。

2,对象的内存布局

对象的内存布局中,主要包括对象头、实例数据和对其填充

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehB4t1wc-1681101135141)(img/1680853091624.png)]

2.1,对象头(Header)

在对象头中,又可以分为两部分,一部分是运行时的元数据,另一部分就是类型指针。

运行时元数据包括哈希码、GC年龄分代、线程持有的锁、持有锁标志、线程id、线程时间戳

由下图可知,在对象的年龄分代为4bit,因此最大为1111,即15,又由于是从0开始,因此其最大年龄为15,所以在设置这个年龄的时候,只能往小设置。锁的标志位对应的字节码用01、10、11表示,其值分别对应着1、2、3,并且这段锁升级的过程是不可逆的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgRgzEzW-1681101135141)(img/1680854392412.png)]

而类型指针指向的是元数据InstanceKlass,确定该对象所属的类型。如果是数组,还需要记录数组的长度

2.2,实例数据(Instance Data)

对象真正存储的有效信息,包括代码中定义的各种类型的字段,以及父类继承下来的和本身拥有的字段 。并且在这些对象中,父类定义的变量会出现在子类之前,并且相同的字段总是会被分配在一起,如果CompactFields参数为true,子类的窄变量可能插入到父类变量的空隙。

2.3,代码示例

接下来分析一下以下这段代码

/**
 * @author zhenghuisheng
 * @date : 2023/4/7
 */
public class Customer {
    Integer id = 1001;
    String name = "zhenghuisheng";
    public Customer(){
        Account account = new Account();
    }
}
public class Test{
    public static void main(String[] args){
        Customer cust = new Customer(); 
    }
}

然后其对应的内存结构如下图所示,在这个main方法中,由于是静态方法,因此局部变量表的第一个slot不是this,而局部变量表中的第二个cust是引用着堆中 new Customer()的实例地址,该实例对象中,主要就是上面的运行时元数据、类型指针、对其填充等组成。运行时数据区就包括唯一地址哈希值、结果多次GC后的年龄、是否获得锁等标志;类型指针对应的就是Customer的Klass类元信息;实例数据就包括自身的属性以及父类属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wdzf03vJ-1681101135142)(img/1680855082757.png)]

3,对象的访问定位

创建对象主要是为了更好的去使用他,JVM内部主要是通过两种方式实现对象引用访问到内部对象的,一种是直接指针,一种是句柄访问

如下代码所示,一个创建对象需要涉及到堆,栈和方法区,因此对象的定位以及访问也需要设计这三个地方

//第一个User存在方法区,主要是存储类信息和运行时常量池
//第二个user在栈中,作为变量存储
//最后的 new User存储在堆中
User user = new User();

句柄访问 的方式如下,在Java堆中有一个句柄池,然后句柄池中保存指向堆中的实例的地址和指向方法区中保存类信息的地址,而在栈中只需保存句柄池的地址即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRULI2d8-1681101135142)(img/1680857098011.png)]

直接指针 就是不需要使用句柄池,在栈的局部变量表中直接保存堆中实例的地址,而在堆中会有一个指针去指向方法区中保留类信息的地址。在Hotspot虚拟机中,主要采用的是这种方式

在这里插入图片描述

句柄指针需要在堆空间中开辟空间存储句柄池,因此会有一定的空间浪费,并且效率相对较低,但是如果出现对象的位置发生改变,如出现垃圾回收的情况,或者使用标记整理算法的时候 ,这个栈中指向堆中的句柄池的指针可以不用发生改变,只需改变句柄池只向实例数据和方法区的指针。而这个直接指针的优缺点就是就和句柄指针相反。

4,直接内存初体验(了解)

在JDK8中,方法区的具体实现从永久代变成了元空间,而元空间使用的是本地内存,又名直接内存,这部分不属于运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域

在这里插入图片描述

在java代码中,可以直接通过这个 ByteBuffer.allocateDirect() 来进行本地内存空间的分配, 即直接通过这个NIO来进行操作,并且在通常直接内存的速度会直接优于Java堆,其读写性能相对较高。因此处于性能考虑,读写频繁的场合可以使用直接内存,并且Java的NIO库,也允许Java程序使用直接内存。

也可能会出现 OutOfMemoryError 异常,由于直接内存在Java堆之外,因此其大小不会受限于 -Xmx 指定的最大堆大小,但又由于系统的内存始终是有限的,因此堆和直接内存的总和依然受限于操作系统给出的最大内存,但是在直接内存中,也存在一定的缺点:分配回收成本较高,并且不受JVM内存回收管理

因此可以直接内存可以通过 MaxDirectMemorySize 进行大小的设置,如果未指定,那么默认和堆的最大值 -Xmx 参数值一致

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100 * 1024);

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

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

相关文章

Vue——组件基础

目录 定义一个组件​ 使用组件​ 传递 props​ 监听事件​ 通过插槽来分配内容​ 动态组件​ DOM 模板解析注意事项​ 大小写区分​ 闭合标签​ 元素位置限制​ 组件允许我们将 UI 划分为独立的、可重用的部分&#xff0c;并且可以对每个部分进行单独的思考。在实际应…

Learning to summarize from human feedback导读(1)

总结&#xff1a; &#xff08;1&#xff09;生成摘要等模型&#xff0c;虽然有评估方法&#xff0c;但是人类总结的质量依旧难以相比 总结&#xff1a; &#xff08;1&#xff09;在各种NLP任务中&#xff0c;大规模语言模型的预训练以及取得了很高的性能 &#xff08;2&am…

PHP快速入门09-正则相关,附一定要学会的20个高频使用案例

文章目录前言一、正则表达式介绍二、正则高频案例20个2.1 检查字符串是否以字母开头2.2 检查字符串是否以数字开头2.3 检查字符串是否包含特定字符2.4 检查字符串是否以特定字符结尾2.5 检查字符串是否为纯数字2.6 检查字符串是否为纯字母2.7 检查字符串是否为有效的电子邮件地…

Bean对象的作用域和生命周期

文章目录&#xff1a;一.Bean的作用域 (1)Bean作用域的含义 &#xff08;2)Bean的6种作用域 二.Bean的生命周期&#xff08;1&#xff09;开辟内存空间 &#xff08;2&#xff09; 属性注入 &#xff08;3&#xff09;初始化 &#xff08;4&#xff09;使用Bean &#xff08;…

【CSDN|每日一练】运输石油

目录 运行结果题目描述输入描述:输出描述:示例代码结语运行结果 题目描述 某石油公司需要向A、B两地运输石油。 两地的需求量不同,而一辆车只能装载一定量的石油。 经过计算A地需要a辆车,B地需要b辆车运输才能满足需求。 现在一共有n辆车分布在各地,每辆车前往A、B两地…

HFSS一些使用技巧总结

1. 快捷键&#xff1a; CTRLH&#xff0c;隐藏选择的object、face 字母E&#xff0c;选择edge&#xff08;线&#xff09; alt左键双击九个区域&#xff0c;切换9个不同的视角&#xff08;与789组合使用) 2. 复制&#xff1a; 这样的复制好处在于&#xff1a;复制完的物体相…

使用Excel打造一款个人日志系统

写在前面 我很多年前看过晨间日志的奇迹这一本书&#xff0c;我深受启发&#xff0c;这本书的中心思想就是通过九宫格的方式写连体日志&#xff0c;自己可以方便查找而有而且有激情去完成这个日志&#xff0c;书中推荐的方法是使用excel写日志。但是自己总感觉用excel过于麻烦…

Java 源码中的 <? extends U>与 <? super L>是什么?

目录 ? extends U ? super L 总结一下: ? extends U 其中extends意思为&#xff1a;扩大;扩展;延长&#xff0c;&#xff1f;我们可以把他看作一个通配符&#xff0c;匹配所有的接口&#xff0c;U就一个泛型占位符&#xff0c;所以连在一起可联想到&#xff0c;从U…

前后端分离下的-SpringSecurity

前后端分离下的SpringSecurity 项目创建 使用SpringBoot初始化器创建SpringBoot项目 修改项目依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2…

电容笔和Apple pencil的区别是什么?好用电容笔推荐

Apple Pencil与目前市场上常见的电容笔最大的不同之处在于&#xff0c;普通电容笔并不具备苹果Pencil特有的重力压感&#xff0c;而仅仅是一种倾斜的压感。不过&#xff0c;其在其它方面的表现也很出色&#xff0c;与Apple Pencil相似&#xff0c;而且价格仅为200元。现在&…

项目管理中的冲突是什么?

项目管理中的冲突可以采取多种不同的形式。团队成员在创意愿景上存在分歧&#xff0c;与高层管理人员就期望和时间表发生争执&#xff0c;甚至与第三方供应商发生争执&#xff0c;都是项目冲突的主要例子。 冲突的常见原因是什么&#xff1f; 基于项目的组织内部冲突的典型原因…

【记录】Git连接gitee、新建仓库

学习记录1.连接gitee2.新建仓库1.连接gitee https://www.cnblogs.com/cokefentas/p/14727592.html git安装与卸载 apt-get install git apt-get remove gitgit配置 配置用户名 git config --global user.name "your name" 配置邮箱 git config --global user.email…

2023都说测试行业饱和了,为什么我们公司新招的的测试开了15K?

其实每年都有人说测试行业饱和了&#xff0c;但依旧有很多人找到了薪资不错的工作。来说说我的看法吧&#xff0c;我认为不用担心测试会饱和的问题&#xff0c;我们人口基数大&#xff0c;任何一个行业都有竞争&#xff0c;这是非常正常的情况。而且在有技术能力的人面前永远没…

Vue3通透教程【十一】初探TypeScript

文章目录&#x1f31f; 写在前面&#x1f31f; TypeScript是什么&#xff1f;&#x1f31f;TypeScript 增加了什么&#xff1f;&#x1f31f;TypeScript 初体验&#x1f31f; 写在最后&#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue …

什么是进程,线程,协程

一.进程1.简介计算机的核心是CPU&#xff0c;它承担了所有的计算任务&#xff1b;而操作系统是计算机的管理者&#xff0c;它负责任务的调度、资源的分配和管理&#xff0c;统领整个计算机硬件&#xff1b;应用程序则是具有某种功能的程序&#xff0c;程序是运行于操作系统之上…

十分钟验证一个轻量化车联网解决方案

智能网联汽车在车联网的应用上&#xff0c;通常是以智能传感器、物联网、GIS技术为基础&#xff0c;结合大数据、人工智能技术&#xff0c;通过OT&#xff08;Operation tecnology&#xff09;和IT&#xff08;information tecnology&#xff09;融合的方式&#xff0c;实现智能…

使用Ubuntu22.04搭建k8s环境和一些k8s基础知识

minikube搭建 基本环境 我使用virtualBox构建的ubuntu&#xff0c;选择4核4G内存minikube是一个K8S集群模拟器&#xff0c;可以快速构建一个单节点的集群&#xff0c;用于在本地测试和开发首先使用官方脚本安装docker curl -fsSL https://test.docker.com -o test-docker.sh…

nacos源码服务注册

nacos服务注册序言1.源码环境搭建1.1idea运行源码1.2 登录nacos2.服务注册分析2.1 客户端2.1.1容器启动监听2.1.2注册前初始化2.1.3注册服务2.2 服务端2.2.1注册2.2.2重试机制3.注意事项序言 本文章是分析的是nacos版本2.2 这次版本是一次重大升级优化&#xff0c;由原来&#…

【MySQL | 基础篇】02、MySQL 函数详解

目录 一、字符串函数 1.1 concat : 字符串拼接 1.2 lower : 全部转小写 1.3 upper : 全部转大写 1.4 lpad : 左填充 1.5 rpad : 右填充 1.6 trim : 去除空格 1.7 substring : 截取子字符串 1.8 案例 二、数值函数 2.1 ceil&#xff1a;向上取整 2.2 floor&#xff…

【Java版oj】day34收件人列表、养兔子

目录 一、收件人列表 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 二、养兔子 &#xff08;1&#xff09;原题再现 &#xff08;2&#xff09;问题分析 &#xff08;3&#xff09;完整代码 一、收件人列表 …