JVM之运行时数据区、对象的创建、对象的内存布局(对象头、实例数据、对齐填充)、对象的访问定位(句柄、直接指针)

本文详细探讨了Java虚拟机中的运行时常量池,包括其在Class文件中的作用、不同类型常量的存储,以及对象的创建过程,涉及类加载、内存分配、初始化和访问定位。同时,讲解了HotSpot虚拟机中对象内存布局和访问方式的两种策略:句柄与直接指针。
摘要由CSDN通过智能技术生成

1.运行时数据区

虚拟机栈
本地方法栈
程序计数器

方法区:存储已被JVM加载的类型信息、常量、静态变量、即时编辑器编译后的代码缓存等数据。
方法区与永久代的关系:
方法区是Java虚拟机规范中的内容,是一种规范、标准,而永久代是一种实现,使用HotSpot使用永久代区实现方法区,这样就可以像管理堆一样去管理方法区,省去了专门为方法区编写内存管理代码的工作。

现在来看,使用永久代并不是方法区的一个很好的实现,Java程序容易遭遇内存泄漏(永久代中由-XX:MaxPermSize的上限,潮池这个限制就会出现内存泄漏),而J9和JRockit使用直接内存实现方法区,只要没有触碰到当前进程可用内存上限就可以,例如32系统由4G的限制。

⭐运行时常量池

运行时常量池:是方法区的一部分,存放编译器生成的各种字面量符号引用

《深入理解Java虚拟机》原文:
运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表,常量池表用于存放编译期生成的各种字面量和符号引用,而在类加载常量池表中的字面量和符号引用将被放到方法区的运行时常量池当中。
补充:Class文件是在编译器产生的。Java程序–>javac编译器–>字节码文件.class

《深入理解Java虚拟机》第六章中关于常量池的解释:

常量池中主要存放有两大类常量:字面量和符号引用。
(1)字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。上面介绍的String类型的字符串常量也就是属于字面量;
(2)符合引用属于编译原理方面的概念,包括了下面三类常量:
类和接口的全限定名、 字段的名称和描述符、 方法的名称和描述符、 被模块导出或开放的包、方法句柄和方法类型、动态调用点和动态常量。
这部分信息将在类加载完成后,存放到运行时常量池当中

知乎:JVM详解之:运行时常量池:https://zhuanlan.zhihu.com/p/160770086

运行时常量池和class文件的常量池是一一对应的,它就是class文件的常量池来构建的。 运行时常量池中有两种类型,分别是symbolic references符号引用和static constants静态常量。 其中静态常量不需要后续解析,而符号引用需要进一步进行解析处理。

字符串常量池

JDK1.8版本的字符串常量池中存的是字符串对象,以及字符串常量值。 附
上常考面试题: 输出结果?创建了几个对象?
String s1 = "abc"; String s2 = "abc";
System.out.println(s1== s2);
结果输出:
true
创建了1个对象。采用字面值的方式创建一个字符串时,JVM首先会去字符串常量池中查找是否存在"abc"这个对象,如果不存在,则在字符串池中创建"abc"这个对象,然后将池中"abc"这个对象的引用地址返回给"abc"对象的引用s1,这样s1会指向池中"abc"这个字符串对象;如果存在,则不创建任何对象,直接将池中"abc"这个对象的地址返回,赋给引用s2。因为s1、s2都是指向同一个字符串池中的"abc"对象,所以结果为true。

输出结果?创建了几个对象?
String s3 = new String("xyz"); String s4 = new String("xyz");
System.out.println(s3==s4);
结果输出:false
创建了3个对象。采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"xyz"这个字符串对象,如果有,则不在池中再去创建"xyz"这个对象了,直接在中创建一个"xyz"字符串对象,然后将堆中的这个"xyz"对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个"xyz"字符串对象;如果没有,则首先在字符串池中创建一个"xyz"字符串对象,然后再在堆中创建一个"xyz"字符串对象,然后将堆中这个"xyz"字符串对象的地址返回赋给s3引用,这样,s3指向了堆中创建的这个"xyz"字符串对象。s4则指向了堆中创建的另一个"xyz"字符串对象。s3s4是两个指向不同对象的引用,结果当然是false。
代码详解
String s1 = new StringBuilder().append("ja").append("va1").toString();
System.out.println(s1.intern() == s1);
输出结果:true
详解:StringBuilder().toString()这个方法虽然是new了一个String对象,但其实和"java1"是一样的,大家可以看下源码,这里的s1.intern()返回的是常量池中字符串的引用,所以s1.intern()
== s1。

##2.HotSpot 虚拟机对象探秘

2.3 对象的创建

在这里插入图片描述
(1)类加载检查
当Java虚拟机遇到一条字节码new指令的时候,首先检查这个指令的参数能否在常量池中定位到这个类的符号引用,并检查这个符号引用代表的类是否被加载、解析以及初始化过,如果没有,就首先执行类加载过程。
(2)分配内存
类加载通过后,虚拟机将为新生对象分配内存,内存大小是在类加载时候就确定了的,为对象分配内存就是从堆中划分一块特定大小的区域。划分区域有指针碰撞和空闲列表两种方式,选择哪种分配方式是由Java堆是否规整来决定的,而Java堆是否规整又取决于采用的垃圾回收方法是否带有整理功能。
(3)初始化零值
分配内存完成后,虚拟机将分配到的空间都初始化为零值。这样保证了对象的实例字段不赋初值就可以直接使用,程序能够访问到这些字段的数据类型对应 的零值
(4)设置对象头
初始化零值之后,虚拟机还需要对对象头进行必要的设置,如这个对象对应的类,如何才能找到对应类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。根据虚拟机当前的运行状态不同,是否使用偏向锁等。
(5)执行init方法
对于虚拟机来说,对象初始化已经完成了,但是从程序员的角度来说,这只是刚开始,接着会执行init方法,按照程序员的议员初始化实力对象的字段

3.对象内存布局

在HotSpot虚拟机中,对象的内存布局分为三部分:对象头,实例数据,对齐填充。
对象头包含两部分内容,一是对象实例自身的运行时数据,包含哈希码、GC分代年龄,锁状态标志等。二是类型指针,即实例指向对应的类的元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。
实例数据:是对象的真正有效信息,也就是程序中的各个字段内容。
对齐填充:占位作用,无实际意义。

4.对象的访问定位

Java通过栈上的reference数据操作堆上的具体对象。对象的访问方式由JVM来决定的,主流的有两种:
(1)句柄
句柄方式是在堆区划分拿出来一块内存作为句柄池,reference数据存储的是对象的句柄地址,而这个句柄包含了对象实例数据的指针和对象类型数据的指针,对象实例指针指向了堆区的对象实例数据,对象类型数据指针指向了方法区的对象类型数据。

(2)直接指针

reference存储的就是对象实例数据在堆上的地址,而对象类型数据的地址指针在堆上分配,指向方法区中的对象类型数据。

句柄方式的,reference数据存储的是稳定的句柄地址,但是访问对象实例数据需要访问两次。而直接指针方式只需访问一次,访问速度快,但是对象在堆上移动时需要修改reference数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值