【个人学习】JVM(8): 对象的实例化、内存布局、访问定位

news2024/11/15 13:49:38

对象的实例化内存布局与访问定位

对象的实例化

对象创建的方式

  1. new:最常见的方式、单例类中调用getInstance的静态类方法,XXXFactory的静态方法
  2. Class的newInstance方法:在JDK9里面被标记为过时的方法,因为只能调用空参构造器,并且权限必须为 public
  3. Constructor的newInstance(Xxxx):反射的方式,可以调用空参的,或者带参的构造器
  4. 使用clone():不调用任何的构造器,要求当前的类需要实现Cloneable接口中的clone方法
  5. 使用序列化:从文件中,从网络中获取一个对象的二进制流,序列化一般用于Socket的网络传输
  6. 第三方库 Objenesis

对象创建的步骤

从字节码看待对象的创建过程

public class ObjectTest {
    public static void main(String[] args) {
        Object obj = new Object();
    }
}
 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup           
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 9: 0
        line 10: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1   obj   Ljava/lang/Object;
}

1、判断对象对应的类是否加载、链接、初始化

  1. 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。(即判断类元信息是否存在)。
  2. 如果该类没有加载,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的.class文件,如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class对象。

2、为对象分配内存

  1. 首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小
  2. 如果内存规整:采用指针碰撞分配内存
    • 如果内存是规整的,那么虚拟机将采用的是指针碰撞法(Bump The Point)来为对象分配内存。
    • 意思是所有用过的内存在一边,空闲的内存放另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针往空闲内存那边挪动一段与对象大小相等的距离罢了。
    • 如果垃圾收集器选择的是Serial ,ParNew这种基于压缩算法的,虚拟机采用这种分配方式。一般使用带Compact(整理)过程的收集器时,使用指针碰撞。
    • 标记压缩(整理)算法会整理内存碎片,堆内存一存对象,另一边为空闲区域
  3. 如果内存不规整
    • 如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表来为对象分配内存。
    • 意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为了 “空闲列表(Free List)”
    • 选择哪种分配方式由Java堆是否规整所决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定
    • 标记清除算法清理过后的堆内存,就会存在很多内存碎片。

3、处理并发问题

  1. 采用CAS+失败重试保证更新的原子性
  2. 每个线程预先分配TLAB - 通过设置 -XX:+UseTLAB参数来设置(区域加锁机制)
  3. 在Eden区给每个线程分配一块区域

4、初始化分配到的空间

  • 所有属性设置默认值,保证对象实例字段在不赋值可以直接使用

  • 给对象属性赋值的顺序:

  1. 属性的默认值初始化
  2. 显示初始化/代码块初始化(并列关系,谁先谁后看代码编写的顺序)
  3. 构造器初始化

5、设置对象的对象头

将对象的所属类(即类的元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于JVM实现。

6、执行init方法进行初始化

  1. 在Java程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量

  2. 因此一般来说(由字节码中跟随invokespecial指令所决定),new指令之后会接着就是执行init方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完成创建出来。

从字节码角度看 init 方法

/**
 * 测试对象实例化的过程
 *  ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题  - ④ 属性的默认初始化(零值初始化)
 *  - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
 *
 *
 *  给对象的属性赋值的操作:
 *  ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
 */

public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客户";
    }
    public Customer(){
        acct = new Account();
    }

}
class Account{

}

Customer类的字节码

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>>
 4 aload_0
 5 sipush 1001
 8 putfield #2 <com/atguigu/java/Customer.id>
11 aload_0
12 ldc #3 <匿名客户>
14 putfield #4 <com/atguigu/java/Customer.name>
17 aload_0
18 new #5 <com/atguigu/java/Account>
21 dup
22 invokespecial #6 <com/atguigu/java/Account.<init>>
25 putfield #7 <com/atguigu/java/Customer.acct>
28 return
  • init() 方法的字节码指令:

    • 属性的默认值初始化:id = 1001;
    • 显示初始化/代码块初始化:name = "匿名客户";
    • 构造器初始化:acct = new Account();

对象的内存布局

内存布局总结

public class Customer{
    int id = 1001;
    String name;
    Account acct;

    {
        name = "匿名客户";
    }
    public Customer(){
        acct = new Account();
    }
	public static void main(String[] args) {
        Customer cust = new Customer();
    }
}
class Account{

}

图解内存布局

对象的访问定位

JVM是如何通过栈帧中的对象引用访问到其内部的对象实例呢?

定位,通过栈上reference访问

对象的两种访问方式:句柄访问和直接指针

1、句柄访问

  1. 缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低
  2. 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改

2、直接指针(HotSpot采用)

  1. 优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据
  2. 缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改 reference 的值

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

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

相关文章

Ignition Gateway配置

Config-System backup和restore&#xff1a; backup可以直接备份整个gateway配置&#xff0c;包括所有项目。 restore可以恢复gateway配置&#xff0c;包括所有项目。

pytorch基础学习

环境安装 mac安装conda&#xff08;为什么安装conda? conda类似沙箱&#xff0c;将一个一个环境隔离起来&#xff0c;解决Python工程之前的包冲突问题&#xff09; 下载Miniconda安装器:https://docs.conda.io/en/latest/miniconda.html 执行dmg安装。 安装完成后&#xff0c…

C++对象初始化

背景 最近在编译一个库的时候发现有个编译错误&#xff0c;最后发现是初始化对象的时候出了问题&#xff0c;这里简单记录一下&#xff1a; #include <iostream>class A { public:int m_a; }; class C { public:int m_c;operator A(){}; }; class B { public:B(){};B (…

探寻孩子自闭症之因:为 “星星的孩子” 寻找答案

在这个丰富多彩的世界里&#xff0c;有一群特殊的孩子&#xff0c;他们仿佛来自遥远的星球&#xff0c;沉浸在自己的独特世界中&#xff0c;难以与外界进行有效的沟通和互动。他们是自闭症儿童&#xff0c;也被称为 “星星的孩子”。那么&#xff0c;为什么孩子会患上自闭症呢&…

大语言模型私有化部署和个性化调优的技术实践

简介 本文介绍如何在不依赖任何三方服务的情况下&#xff0c;私有化部署和使用大语言模型&#xff0c;以及如何以较低成本让大语言模型使用自己的数据来产生个性化输出。 本文偏技术向&#xff0c;读者需要具备一定技术背景&#xff0c;如有不懂之处&#xff0c;欢迎留言交流…

ARM——驱动——inmod加载内核模块

在上一篇文章的代码上添加出错处理 #include <linux/init.h> // 包含初始化宏和函数 #include <linux/kernel.h> // 包含内核函数和变量 #include <linux/fs.h> // 包含文件操作的结构和函数 #include <linux/kdev_t.h> /…

记忆化搜索与状态压缩:优化递归与动态规划的利器

记忆化搜索是解决递归和动态规划问题的一种高效优化技术。它结合了递归的灵活性和动态规划的缓存思想&#xff0c;通过保存已经计算过的子问题结果&#xff0c;避免了重复计算&#xff0c;大幅提升了算法的效率。当问题状态复杂时&#xff0c;状态压缩技术可以进一步优化空间使…

密码生成器(HTML+CSS+JavaScript)

&#x1f30f;个人博客主页&#xff1a;心.c ​ 前言&#xff1a;前两天写了密码生成器&#xff0c;现在跟大家分享一下&#xff0c;大家如果想使用随便拿&#xff0c;如果哪里有问题还请大佬们给我指出&#xff0c;感谢支持 &#x1f525;&#x1f525;&#x1f525;专题文章&…

《断点回归的非参数估计及 Stata 实现》

目录 一、引言 二、文献综述 三、理论原理 四、实证模型 五、稳健性检验 六、程序代码及解释 七、代码运行结果及解释 一、引言 断点回归&#xff08;Regression Discontinuity&#xff0c;RD&#xff09;设计是一种准实验方法&#xff0c;用于评估政策或干预措施的因果…

鉴权Cookies、授权session、token

2 鉴权、授权 区别&#xff1a;一个存在浏览器&#xff0c;一个存在服务器&#xff0c;session存到服务端当中 问1&#xff1a;http协议是一个典型的无状态协议&#xff0c;无记忆&#xff0c;那第一次登录下次是不是还要登录一次&#xff1f; ANS&#xff1a;不需要 因为Co…

C++设计模式3:工厂模式

工厂模式都是在那种有着大量类的程序里面使用的&#xff0c;比如我突然要产生许多的类&#xff0c;这时候就可以用工厂模式&#xff0c;工厂模式有如下三种类型。 简单工厂 用户输入实例化的信息(比如产品名称)&#xff0c;向工厂申请对象&#xff0c;工厂返回相应的对象&…

npm install报错,解决记录:11个步骤诊断和解决问题

在处理npm install报错时&#xff0c;可以遵循以下步骤来诊断和解决问题&#xff1a; 查看错误信息&#xff1a; 错误信息通常会给出问题的线索&#xff0c;例如依赖包版本冲突、网络问题、权限问题等。 更新npm和Node.js&#xff1a; 首先尝试更新npm和Node.js到最新版本&…

地平线—征程2(Journey 2-J2)芯片详解(16)—DDR系统

写在前面 本系列文章主要讲解地平线征程2(Journey 2-J2)芯片的相关知识,希望能帮助更多的同学认识和了解征程2(Journey 2-J2)芯片。 若有相关问题,欢迎评论沟通,共同进步。(*^▽^*) 错过其他章节的同学可以电梯直达目录↓↓↓ 地平线—征程2(Journey 2-J2)芯片详解…

新开发体育直播平台的创业指南:降低赛事版权成本方法

在全球化浪潮下&#xff0c;体育赛事已成为连接世界各地观众的情感纽带&#xff0c;其巨大的影响力不仅激发了全球观众的热情&#xff0c;也催生了体育赛事直播行业的蓬勃发展。然而&#xff0c;高昂的版权费用如同一道难以逾越的门槛&#xff0c;让众多新进入者和小型体育直播…

【与C++的邂逅】--- 类与对象(下)

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 与C的邂逅 本节我们继续学习类与对象下&#xff0c;下面给出本节内容大纲。 &#x1f3e0; 再谈构造 &#x1f4cc; 构造函数体赋值 在创建对象时&…

【图形学】TA之路-基于Unity Shader编程之初体验

学习shader之前你必须知道的事情&#xff1a; Unity开发引擎、Direct3D、Shader他们之间的关系 Direct3D 是一个底层图形 API&#xff0c;它直接与 GPU &#xff08;显卡&#xff09;交互&#xff0c;提供了访问硬件加速功能的接口。Unity 开发引擎&#xff0c;它封装了很多底…

[Linux] 认识系统服务(daemon)

参考&#xff1a;《鸟哥的Linux私房菜》 一、什么是 daemon 与服务&#xff08;service&#xff09; 在英语中的daemon就有守护进程&#xff0c;后台程序的意思。简单来说就是一直在后台运行的进程&#xff0c;我们就称之为服务(service)&#xff0c;或者是守护进程(daemon)。…

Java爬虫图像处理:从获取到解析

在互联网时代&#xff0c;数据的价值日益凸显&#xff0c;而爬虫技术作为获取网络数据的重要手段&#xff0c;其应用范围越来越广泛。本文将探讨Java爬虫在图像处理方面的应用&#xff0c;包括如何从网络中获取图像数据&#xff0c;以及如何对这些数据进行解析和处理。 Java爬…

实现Kruskal算法连通游戏地图地牢

前置知识 c分享&#xff5c;并查集从入门到出门 - 力扣&#xff08;LeetCode&#xff09; 彻底搞懂克鲁斯卡尔&#xff08;Kruskal&#xff09;算法&#xff08;附C代码实现&#xff09; - QuanHa - 博客园 (cnblogs.com) 白色点矩形是地牢&#xff0c;其中白线是按照krsukal…

网络编程知识点总结

物理链路网络运输会话表示应用 物链网运会表应 实际的数据帧 TCP和UDP的异同&#xff08;笔试面试&#xff09; 主机&#xff1a;host 转换&#xff1a;to 网络&#xff1a;network uint32_t htonl(uint32_t hostlong); //将4字节无符号整数的主机字节序转换为网络字节序&a…