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);

1

2

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


com.secbro2.others.Bike@4dc63996

1

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


public String toString() {

   return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

1

2

3

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


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


<dependency>

   <groupId>org.openjdk.jol</groupId>

   <artifactId>jol-core</artifactId>

   <version>0.10</version>

</dependency>

1

2

3

4

5

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


String answer = "42";

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

1

2

会发现打印的内容如下:


The memory address is 31856221536

1

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


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


小结

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



目录
相关文章
|
6天前
|
缓存 监控 算法
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
jvm性能调优实战 - 39一次大促导致的内存泄漏和Full GC优化
81 0
|
6天前
|
运维 JavaScript Java
Serverless 应用引擎产品使用之Nacos 在集中发版时遇到老年代暂满,并且频繁进行 Full GC,但是 GC 后内存没有降下来如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
31 0
|
6天前
|
Java 开发者 iOS开发
8 种 Java- 内存溢出之二 -GC overhead limit exceeded
8 种 Java- 内存溢出之二 -GC overhead limit exceeded
|
6天前
|
存储 算法 Oracle
干货 | 一文看懂JVM内存布局及GC原理 携程
干货 | 一文看懂JVM内存布局及GC原理 携程
84 0
|
5月前
|
存储 Java C#
C# 垃圾回收机制(GC) 的概述 资源清理 内存管理
C# 垃圾回收机制(GC) 的概述 资源清理 内存管理
|
5月前
|
JSON Java 数据格式
jackson 转换报内存缢出:java.lang.OutOfMemoryError: GC overhead limit exceeded at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:141)
转换报内存缢出。 原因是查询数据时,在转json的数据太大,内存不足 解决方法:1、加内存(没有从根本上解决问题)。2、优化代码,例如减少查询,分页查询。
55 0
|
6月前
|
缓存 Java 程序员
如何写出高性能代码(三)优化内存回收(GC)
可复用性在这里指的是,大多数的对象都是可以被复用的,这些可以被复用的对象就没必要每次都新建出来,浪费内存空间了。 处了巧用数据特性 中的例子,我这里再个Java中已经被用到的例子,这个还得从一段奇怪的代码说起。
37 0
|
存储 缓存 负载均衡
JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
68809 9
JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
|
11月前
|
存储 算法 安全
Java GC算法背景原理与内存池划分
Java GC基础概念,入门进阶必备,本文带你了解GC算法原理与面试常问的年轻代老年代等内存池划分问题。
80 0
Java GC算法背景原理与内存池划分
|
存储 缓存 监控
重学Node系列03-内存管理及GC算法
Node内存控制 这是重新阅读《深入浅出NodeJS》的相关笔记,这次阅读发现自己依旧收获很多,而第一次阅读的东西也差不多忘记完了,所以想着这次过一遍脑子,用自己的理解输出一下,方便记忆以及以后回忆...
98 0