GC复制存活对象,它内存地址变了么?

简介: GC复制存活对象,它内存地址变了么?

前言

前些天与一位朋友技术交流,朋友在招人面试时想到一个问题,JVM垃圾回收时,会复制存活的对象到不同的区域。比如从新生代复制到老年代,在此过程中,被复制的对象的地址是否变了呢?对他提出的这个问题很感兴趣,深入研究了一下,便有了这篇文章。

更新引用是JVM的职责

任何一款JVM的设计,采用任何一种GC算法进行对象的移动操作时,如何更新对象引用都是JVM的基本职责。也就是说,当移动对象时,必然会涉及到对象引用的变更,只不过这部分操作JVM已经帮我们做了。

作为开发者来说,可以将引用理解为存储对象的抽象句柄,而不必担心JVM是如何管理对象存储的。但如果做技术研究,好奇底层的实现,倒是值得深入研究一下。

当对象的实际地址发生变化时,简单来说,JVM会将指向该地址的一个或多个变量所使用的引用地址进行更新,从而达到在“不知不觉”中移动了对象的效果。

JVM规范中只规定了引用类型是指向对象的引用,并没有限制具体的实现。因此,不同虚拟机的实现方式可能不同。通常有两种实现形式:句柄访问和直接指针访问。

句柄访问

先来看一张图,句柄访问的形式是堆空间维护一个句柄池,对象引用中保存的是对象的句柄位置。在堆中的句柄包含对象的实例数据和类型数据的真实地址。image.png这种形式的实现好处很明显,引用中保存的对象句柄地址相对稳定(不变),当GC操作移动对象时只用维护句柄池中存储的信息即可,特别是多个变量都引用同一个句柄池中的句柄时,可以减少更新变量存储的引用,同时确保变量的地址不变。缺点就是多了一次中转,访问效率会有影响。

直接指针访问

直接指针访问省去了中间的句柄池,对象引用中保持的直接是对象地址。 image.png这种方式很明显节省了一次指针定位的开销,访问速度快。但是当GC发生对象移动时,变量中保持的引用地址也需要维护,如果多个变量指向一个地址,需要更新多次。Hot Spot虚拟机便是基于这种方式实现的。

如何查看引用地址?

上面聊了对象引用的实现形式,那么在日常开发中是否可以通过打印等形式来查看对象的地址吗?有这样一个说法,通过对象默认的toString方法打印出来的信息中包含对象的引用地址。下面我们通过一个实例来看看:

Bike bike = new Bike();
System.out.println(bike);

当我们执行上述程序时,控制台会打印出如下信息:

com.secbro2.others.Bike@4dc63996

@后面的字符串是什么?是对象的地址吗?这种地址的说法其实在坊间流传了很久。我们先来看Object的toString源码:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

通过源码我们会发现,其实@符合后面并不是对象的地址,而只是hashcode的十六进制展现形式而已。

那么,如何打印对象的内存地址呢?我们需要依赖一个JOL(Java Object Layout)类库,在项目中添加如下Maven依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

然后在程序中通过如下方法使用:

String answer = "42";
System.out.println("The memory address is " + VM.current().addressOf(answer));

会发现打印的内容如下:

The memory address is 31856221536

上面的便是真实的内存地址,虽然能够获取并打印出内存地址,但由于不同环境下的JVM采用了不同的指针压缩操作。因此,我们不要基于此地址来做一些本机内存相关的操作。但上面的打印,明确的证明了toString方法打印出来的信息并不包括对象的内存地址。

鉴于此,基于toString方法打印出来hashCode值只能保证两个对象的hashcode一样,却无法保证两个引用地址指向同一对象。

小结

通过与朋友的一个小交流,深挖一下,竟然发现不少底层的知识点,交流和探索的作用可见一斑。总结来说就是:JVM在GC操作时会自动维护引用地址,变量对应的应用地址是否变化要看采用的是基于句柄池方式还是直接指针指向的方式。同时,当我们通过toString方法打印时,输出的内容并不包含对象地址,只不过是对象hashcode的十六进制而已。


目录
相关文章
|
7月前
|
存储 Java
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
|
10月前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
221 3
|
11月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
553 4
|
12月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
118 2
|
3月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
845 0
|
3月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
244 0
|
3月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
231 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
828 0
|
12月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。

热门文章

最新文章