首页 > 系统相关 >第四章 对象的实例化内存布局与访问定位

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

时间:2024-07-04 17:08:23浏览次数:6  
标签:初始化 对象 实例 内存 new public 第四章

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

  对象的实例化

   对象创建的方式

  (1)new:最常见的方式、单例类中调用getInstance的静态类方法,XXXFactory的静态方法

  (2)Class的newInstance方法:在JDK9里面被标记为过时的方法,因为只能调用空参构造器,并且权限必须为public

  (3)Constructor的newInstance(xxx):反射的方式,可以调用空参的,或者带参的构造器

  (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)判断对象对应的类是否加载、链接、初始化

    虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。(即判断类元信息是否存在)

    如果该类没有加载,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的.class文件,如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class对象

  (2)为对象分配内存

    首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小

    如果内存规整:采用指针碰撞分配内存

      如果内存是规整的,那么虚拟机将采用的是指针碰撞法(Bump The Point)来为对象分配内存

      意思是所有用过的内存在一边,空闲的内存放另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针往空闲内存那边挪动一段与对象大小相等的距离罢了。

      如果垃圾收集器选择的是Serial ,ParNew这种基于压缩算法的,虚拟机采用这种分配方式。一般使用带Compact(整理)过程的收集器时,使用指针碰撞

      标记压缩(整理)算法会整理内存碎片,堆内存一存对象,另一边为空闲区域

    如果内存不规整:

      如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表来为对象分配内存

      意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为了 “空闲列表(Free List)”

      选择哪种分配方式由Java堆是否规整所决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

      标记清除算法清理过后的堆内存,就会存在很多内存碎片

  (3)处理并非问题

    采用CAS + 失败重试保证更新的原子性

    每个线程分配TLAB - 通过设置-XX:+UseTLAB参数来设置(区域加锁机制)

    在Eden区给每个线程分配一块区域

  (4)初始化分配的空间

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

    给对象属性赋值的顺序:

      属性的默认值初始化

      显示初始化/代码块初始化

      构造器初始化

  (5)设置对象的对象头

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

  (6)执行init方法进行初始化

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

    因此一般来说(由字节码中跟随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{

}

  图解内存布局

   对象的访问定位

   定位,通过栈上reference访问

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

  (1)句柄访问

    缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问堆中的对象,效率低

    优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要修改

   (2)直接指针(Hotspot采用)

    优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据

    缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改reference的值

 

标签:初始化,对象,实例,内存,new,public,第四章
From: https://www.cnblogs.com/homle/p/18284218

相关文章

  • 玄机——第四章 windows实战-emlog wp
    文章目录一、前言二、概览简介三、参考文章四、步骤(解析)准备阶段#1.0步骤#1通过本地PCRDP到服务器并且找到黑客植入shell,将黑客植入shell的密码作为FLAG提交;拓展1.1步骤#2通过本地PCRDP到服务器并且分析黑客攻击成功的IP为多少,将黑客IP作为FLAG提......
  • golang 内存逃逸 你应该知道的知识
    逃逸分析目录1.为什么要了解内存逃逸2.什么是逃逸分析3.内存逃逸的影响-性能和稳定性4.内存逃逸的原因5.内存逃逸的检测6.如何避免内存逃逸7.内存逃逸代码示例原文链接:一文弄懂Golang中的内存逃逸1.为什么要了解内存逃逸-内存逃逸是Go语言编程中一个特别需要注意的问......
  • cpu内存硬盘之间的工作原理!
    在现代计算机系统中,CPU(中央处理器)、内存(RAM)和硬盘(硬盘驱动器或固态硬盘)是三大核心组成部分。它们之间的协同工作关系直接影响整个计算机系统的性能和效率。为了更好地理解计算机的工作原理,CPU、内存和硬盘的基本功能及它们之间的相互交互。这三者的工作原理及其之间的关系。CP......
  • 面试官:Java类是如何被加载到内存中的?
    面试连环callJava类是如何被加载到内存中的?Java类的生命周期都有哪些阶段?JVM加载的class文件都有哪些来源?JVM在加载class文件时,何时判断class文件的格式是否符合要求?类生命周期一个类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、......
  • python自动化内存管理
    引用在编程中,引用是指用来标识、访问或操作某个对象的值的标识符或变量。我们可以将引用看作是对象的别名,通过引用可以操作对象,包括读取、修改和传递对象的值。举例来说,假设我们有一个字符串对象`name`,我们可以创建一个变量`person`来引用这个字符串对象。在这个例子中,`perso......
  • springboot项目国产化适配,jar改war包碰到的坑-tomcat版本要适配(非法访问:此Web应用程序
    项目原来是jar包运行,国产化适配要改成war包。可以参考https://blog.csdn.net/NAMELZX/article/details/138123405或者其他jar 改成 war 的文章。改成war后,在本地tomcat8上运行,一直报org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading非法......
  • labelme转yolo格式txt 目标检测和实例分割的脚本
    labelme标注后的数据转yolo目标检测格式txt的脚本点击查看代码#https://blog.csdn.net/m0_63172128/article/details/135942221importbase64importrandomimportshutilfromtqdmimporttqdmimportmathimportjsonimportosimportnumpyasnpimportPIL.Imageim......
  • EXCEL中20个数据处理类函数公式应用实例
    在Excel中,数据处理类函数是进行数据分析和报告制作的重要工具。以下列举了另外20个数据处理类函数及其应用实例,这些函数涵盖了一系列高级的数据处理需求,包括统计分析、财务计算、工程计算以及更复杂的文本和日期时间操作。数据统计与分析STDEV.S函数:用于计算样本标准差。例......
  • GaussDB(DWS)性能调优,解决DM区大内存占用问题
    本文分享自华为云社区《GaussDB(DWS)性能调优:DM区优化案例——维度表关联条件存在会计期》,作者:O泡果奶~。当前DM(P1、P3、CBGDM)存在维度表与主表关联时使用会计期作为关联条件,会导致出现大内存占用或未识别数据倾斜的问题【场景一】f.period_id=维度表.period_id1.1、【问题......
  • Linux进程间的通信方式(二)System V 共享内存
    文章目录前言1.共享内存的概念1.1什么是共享内存1.2linux的内存管理机制1.3内存映射2.共享内存的接口分类3.共享内存的相关操作函数3.1ftok函数(获取一个key值)3.2shmget函数(创建或获取一个共享内存描述符)3.3shmat函数(映射共享内存地址空间)3.4shmdt函数(......