JVM12_StringTable、对象的实例化、对象的内存布局 、对象访问的两种方式、对象头信息

本文探讨了Java中对象创建的多种方式,包括new关键字、反射、克隆等,并详细解释了对象创建过程中的六个关键步骤。此外,还介绍了字符串常量池与方法区的变化,以及不同对象访问方式的优缺点。

字符串常量池

为什么要调整,从永久代放到堆空间?

其实就是因为字符串常量池在永久代中,回收效率不高,而开发中会产生大量的字符串,这样就会导致永久代内存不足。放在堆中,能及时回收内存
在这里插入图片描述

静态变量

首先,new出来的东西没有疑问,都是放到对空间中的,
JDK中静态变量的变化,只是指的静态变量的引用存放的位置,JDK6是在永久代,JDK7和8放到了堆空间中

jhsdb.exe是在JDK9时才出现的,监控进程的工具
在这里插入图片描述
静态变量和映射的Class对象都放在了堆中

方法区的垃圾回收行为

对于方法区的回收行为,记住两点:
1、java虚拟机规范,对方法区的垃圾回收要求不高,可以不进行回收
2、如果要回收垃圾,主要是两方面:常量池中废弃的常量和不再使用的类,常量的回收简单,但是对于类的回收则不太容易
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

在这里插入图片描述
面试题
在这里插入图片描述
在这里插入图片描述

对象的实例化

在这里插入图片描述
在这里插入图片描述

创建对象的方式

在这里插入图片描述

1、new,除了简单的new , 还有变形:单例模式,XxxFactory的静态方法

2、3、反射方式:Class下的newInstance()方式在JDK9开始不推荐使用了,因为要求高,必须是空参的构造器才可以
现在推荐使用Constructor下的newInstance(Xxx)的方式。可以调用无参的或者带参的构造器方式,比较灵活

4、使用clone() 浅复制
5、反序列化。序列化可以实现数据从一个进程到另一个进程的传递,基于本地和网络都可以实现,将对象转化成二进制流,传递后再从二进制流转换成对象
6、第三方库:

创建对象的步骤

在这里插入图片描述

首先,new关键字,调用的是底层的new方法创建对象
先查看在方法区是否加载了Object类, 加载完后就开始在堆中开辟对象需要的空间,
1、加载当前对象创建所需要的类对象
在这里插入图片描述
如果是引用变量,那么只给4个字节大小
当内存规整的时候,为对象分配内存使用指针碰撞法,即规整地新分配内存,此处GC对应标记压缩算法。
当内存不规整时,为对象分配内存时会维护一个列表,专门存放空闲内存位置。当分配内存时选择可以放下的内存进行分配
此处GC对应标记清除算法,如CMS,垃圾回收后内存就不是规整的。
在这里插入图片描述

在这里插入图片描述
第四步,这里给对象属性赋值的操作是:属性的默认初始化。
对象属性赋值操作有
1、属性的默认初始化
2、显示初始化和代码块中初始化,这两个就看代码的前后位置
3、构造器中初始化
在这里插入图片描述
第六步:对属性进行显式初始化,代码块初始化、构造器中初始化,也就是Person()这一步,在解析后的字节码中就是中的内容
总结六步:1、加载类元信息2、为对象分配内存空间,分内存规整和不规整的不同分配方式
3、处理并发问题4、为属性进行默认零值初始化5、设置对象头信息
6、最终属性显式初始化、代码块中初始化、构造器中初始化
这六步执行完后,完整的对象才算创建完成

对象的内存布局

在这里插入图片描述
对象头包含两部分内容:运行时元数据和类型指针,运行时元数据其实就是运行中的一些必要信息,类型指针就是对象是由哪个类创建的

对象创建的示例说明

在这里插入图片描述
在这里插入图片描述

通过上图代码创建Customer()对象的过程,内存中布局如下:
在这里插入图片描述

对象访问定位

在这里插入图片描述
通过栈帧中的对象引用来访问堆中创建好的对象,引用保存了对象存储位置的内存地址,所以可以访问到这个对象实例

对象访问的两种方式

由于java虚拟机规范中并没有明确对象访问采用哪种方式,所以就存在这么两种方式
Hotspot采用的是直接指针的方式

1、句柄访问

在这里插入图片描述

2、直接指针

在这里插入图片描述
之所以hotspot最终采用的是直接指针的对象访问方式,还是因为优势更大一点,
在直接指针访问中,1、不存在句柄池这个中间量,所以不用给其分配内存,节省空间,
2、没有中间句柄池,直接进行访问,效率更高
当然,直接指针的缺点就是当对象存储位置发生修改时,变量引用也需要修改,
而在句柄访问方式中,对象存储位置变化时,只要修改句柄池中指针的位置即可。

<< 在 Java 中声明字符串时,可以使用两种方式创建字符串实例:直接用字面量(`String str = "abc";`)或通过 `new` 关键字显式地构造一个新的字符串对象 (`String str = new String("abc");`)。这两种方式有显著的区别。 ### 区别: 1. **内存分配位置** - 使用 字面量定义(`"abc"`) 的时候, JVM 首先会在常量池中查找是否已经存在值为 `"abc"` 的 `String` 对象。如果不存在,则将该字符串放入常量池,并返回引用;否则直接返回已存在的常量池中的引用。 - 当使用 `new` 创建 `String` 对象的时候,JVM 总是在堆(heap)上生成新的对象,即使它的内容已经在字符串常量池(String Pool)中存在了。 2. **性能方面** - 直接使用字面量更高效,因为避免了重复创建相同的对象并且利用了缓存机制(即重用了常量池中的已有对象). - 每次调用 `new`,都会产生新对象,在大量数据处理的情况下可能会导致更多的垃圾回收(GC),从而影响效率。 3. **比较结果不同** - 如果两个字符串都是通过字面量形式定义且具有相同的内容,那么它们实际上是同一个对象(reference equality),例如:`"hello".equals("hello") == true && ("hello" == "hello") == true` - 若采用`new`关键字分别初始化两份完全一样的字符串,虽然他们的`.equals()`方法会判定他们相等,但其实际地址却是不一样的: 如下所示 ```java public class Test { public static void main(String[] args){ String s1 = "test"; String s2 = "test"; //true because both point to same object in pool. System.out.println(s1==s2); String s3 = new String("test"); String s4 = new String("test"); //false as they are different objects on heap. System.out.println(s3==s4); //True since content is equal. System.out.println(s3.equals(s4)); } } ``` 上述例子说明即使是同样字符序列构成的新建字符串也不会共享同一存储空间。 --- ### 解释 - 在第一种情况下 (不带 `new`),当编译器遇到 `"test"` 这样的文本串时,它实际上检查内部维护的一个全局表——所谓的“字符串池”。如果没有找到与之匹配的条目就添加进去并把指向那个地方的指针赋予变量名`s1/s2`; 反之则复用现有的入口。因此在这种情形之下我们得到的是同一条记录的不同引用而已。 - 第二个情况涉及到了显示构建动作(new operator卡顿触发执行自定义逻辑). 此过程无视任何预先准备好的资源而是单独开辟了一块区域存放这个特定实体的数据副本. #### 注意事项: 尽管如此,有时开发者还是希望强制让某个经由new出来的string加入到intern table里头去享受后续可能带来的好处,这时候就可以借助 intern() 方法手动完成此操作: ```java // After calling .intern(), 's5' will refer back to pooled version if already exists. String s5 = new String("anotherTest").intern(); System.out.println(s5 == "anotherTest"); // This could be True now depending upon jvm implementation details. ``` 以上就是关于Java中`String`字面量和通过`new`关键词创建的主要差异点以及背后的原因分析。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值