刚new出来的对象实例,内部的属性对象实例是如何在内存中访问的?
1.先贴个代码
(1)我们定义了两个类:一个 Book书、一个Bookmark书签
@Data
public static class Book {
private int id;
private String name;
private Bookmark bookmark;
public Book(int id, String name, Bookmark bookmark) {
this.id = id;
this.name = name;
this.bookmark = bookmark;
}
}
@Data
public static class Bookmark {
private int index;
private String name;
public Bookmark(int index, String name) {
this.index = index;
this.name = name;
}
}
(2)这个时候,我们将构造一个书本的实例
public static void main(String[] args) {
Book newBook = new Book(1, "JavaBook", new Bookmark(10, "java"));
Bookmark bookmark = newBook.getBookmark();
System.out.println(bookmark.name);
}
这是一个很简单的操作,其中我们关心“Bookmark bookmark = newBook.getBookmark();”他是如何获取到bookmark这个实例对象呢?
抛出问题,然后进入分析。
2.在之前的运行时数据区分析的过程中,我们记得说过:
分析实例对象的组成的时候,对象头中的类型指针,这是通过实例对象获取到类元数据信息建立的一个桥梁。
那么在分析Java虚拟机栈的时候,我们分析到了栈帧中的组成的时候,有局部变量表;局部变量表中会有当前方法所有引用到的局部变量,其中除了八大基本类型外,还有一个reference,这个类型就是具体的对象实例;那么我们需要在具体的操作堆上找到对应的对象实例。
那么在我们使用这个对象实例的时候,我们如何在内存中进行指向呢?
在对象头中的类型指向中我们是通过实例的对象头信息进行绑定的,这是一种方式,那么有没有其他方式呢?
3.关于直接通过对象实例的对象头中的类型指针进行绑定访问类型数据,这种方式叫做直接指针访问。
如下图所示,在局部变量表中,通过reference的指向堆内存中的对象实例的内存地址,然后就可以访问到具体的对象实例了。
4.那么还有一种访问的方式,这种访问的方式可能在HotSpot中不常见,但是在其他的虚拟机中会出现这样的设定,就是通过一个中间地址维护局部变量的引用和实例对象的关系。这种方式称之为“句柄池”。
什么叫句柄:句柄(Handle)是一个是用来标识对象或者项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量 [1] 。
也就是说这是建立实例和引用地方的一个中继的虚拟地址,这种虚拟地址可以提供管理,记录的功能。再实例对象内存地址发生变动的时候,我们只需要改变句柄池中的指针指向就可以了。
那么优势就很明显了,我们后面总结一下。
5.两种方式我们可以很清晰看出优劣的;对于直接指针方式,我们可以直接通过当前调用的对象直接获取到对应的实例对象,和类型数据。那么通过句柄池方式,我们先要访问句柄池的数据,然后通过句柄池的指向地址,才能访问到对应的实例对象,和类型数据,那么就无异于加重了访问的开销。我们知道,在Java程序中,对一个实例,类信息的访问是无时无刻的,一门面向对象的语言。
但是通过句柄池的间接方式的优势是什么呢?我们知道垃圾回收器的内存整理方式,会频繁的改动对象实例的内存位置,那么这个时候,当所有调用该对象实例的地方,我们都需要进行维护,这种维护成本也是相当的高的,那么我们需要解决这种维护成本问题,这个时候句柄池的优势有了,我们只需要对句柄池指向的地址进行修改,那么就一下所有的访问地址都直接更新了。从GC角度分析,这是一种很好的设计方案。