Java对象表示——Oop-Klass模型(二)

2023-10-18 12:08
文章标签 java 模型 对象 oop 表示 klass

本文主要是介绍Java对象表示——Oop-Klass模型(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在《Java对象表示——Oop-Klass模型(一)》一文的最后讲到,为了实现Java方法调用的动态绑定,HotSpot使用了与C++虚函数类似的机制,同时为了避免每个对象都维护一个虚函数表,于是就设计了Klass类。

如下为HotSpot源码中对Klass的功能介绍:

A Klass provides:

​ 1: language level class object (method dictionary etc.)

​ 2: provide vm dispatch behavior for the object

Both functions are combined into one C++ class.

可见,Klass主要提供了两个功能:

(1)用于表示Java类。Klass中保存了一个Java对象的类型信息,包括类名、限定符、常量池、方法字典等。一个class文件被JVM加载之后,就会被解析成一个Klass对象存储在内存中。

(2)实现对象的虚分派(virtual dispatch)。所谓的虚分派,是JVM用来实现多态的一种机制。

class A {void callMe() {System.out.println("This is A.");}
}
class B extends A {@Overridepublic void callMe() {System.out.println("This is B.");}
}
class C extends A {@Overridepublic void callMe() {System.out.println("This is C.");}
}
public class VmDispatch {public static void main(String[] args) {A b = new B();A c = new C();// b和c的静态类型为A,那么JVM是如何将它们动态绑定到正确的实现上的呢?b.callMe();c.callMe();}
}
/* Output:
This is B.
This is C.
*/

考虑上述例子,基类A有两个子类,分别为BC。在main函数中,bc的静态类型都是A,但是在调用callMe()方法时,JVM会将它们绑定到正确的实现上。这其中奥秘就是JVM的虚分派机制,而该机制的实现用到了Klass中的虚函数表

Klass的继承体系

跟Oop一样,Klass也有一个继承体系,如下图所示:

Klass继承体系

// hotspot/src/share/vm/oops/oopsHierarchy.hpp
...
class Klass;  // Klass继承体系的最高父类
class   InstanceKlass;  // 表示一个Java普通类,包含了一个类运行时的所有信息
class     InstanceMirrorKlass;  // 表示java.lang.Class
class     InstanceClassLoaderKlass; // 主要用于遍历ClassLoader继承体系
class     InstanceRefKlass;  // 表示java.lang.ref.Reference及其子类
class   ArrayKlass;  // 表示一个Java数组类
class     ObjArrayKlass;  // 普通对象的数组类
class     TypeArrayKlass;  // 基础类型的数组类
...

不同于Oop,Klass在InstanceKlass下又设计了3个子类,其中InstanceMirrorKlass用于表示java.lang.Class类型,该类型对应的oop特别之处在于其包含了static field,因此计算oop大小时需要把static field也考虑进来;InstanceClassLoaderKlass主要提供了遍历当前ClassLoader的继承体系;InstanceRefKlass用于表示java.lang.ref.Reference及其子类。

对象的类型信息

Klass的主要用途之一就是保存一个Java对象的类型信息,如下选出其中一些比较重要的field:

// hotspot/src/share/vm/oops/klass.hpp
class Klass : public Metadata {
...// 类名,其中普通类名和数组类名略有不同// 普通类名如:java/lang/String,数组类名如:[Ljava/lang/String;Symbol*     _name;// 最后一个secondary supertypeKlass*      _secondary_super_cache;// 保存所有secondary supertypesArray<Klass*>* _secondary_supers;// 保存所有primary supertypes的有序列表Klass*      _primary_supers[_primary_super_limit];// 当前类所属的java/lang/Class对象对应的oopoop       _java_mirror;// 当前类的直接父类Klass*      _super;// 第一个子类 (NULL if none); _subklass->next_sibling() 为下一个Klass*      _subklass;// 串联起当前类所有的子类Klass*      _next_sibling;// 串联起被同一个ClassLoader加载的所有类(包括当前类)Klass*      _next_link;// 对应用于加载当前类的java.lang.ClassLoader对象ClassLoaderData* _class_loader_data;// 提供访问当前类的限定符途径, 主要用于Class.getModifiers()方法.jint        _modifier_flags;// 访问限定符AccessFlags _access_flags;    
...
}

如上述代码片段所示,Klass继承了Metadata,后者为《深入解析Java的运行时数据区》一文中提到的“元空间”(Metaspace)的实现,这也意味着Java对象的类型信息存储在方法区,而不是在堆中

primary supertype和secondary supertype主要用于快速类型检查(比如在调用instanceOf时能够快速得到结果),其中primary type和secondary type的定义出现在《Fast subtype checking in the HotSpot JVM》一文中:

A klass T is a primary type iff T is a proper class, or an array of a primary type, or an array of primitive values. Interfaces and arrays of interfaces are excluded.

A klass T is a secondary type iff T is a interface or an array of a secondary type. Every type is either a primary type or a secondary type but not both.

接着,我们继续看下表示普通对象类型的InstanceKlass所包含的信息,它继承自Klass,在父类的基础上增加了不少信息,如下列出较为重要的一些:

// hotspot/src/share/vm/oops/instanceKlass.hpp
class InstanceKlass: public Klass {
...// 当前类的状态enum ClassState {allocated,  // 已分配loaded,  // 已加载,并添加到类的继承体系中linked,  // 链接/验证完成being_initialized,  // 正在初始化fully_initialized,  // 初始化完成initialization_error  // 初始化失败};// 当前类的注解Annotations*    _annotations;// 当前类数组中持有的类型Klass*          _array_klasses;// 当前类的常量池ConstantPool* _constants;// 当前类的内部类信息Array<jushort>* _inner_classes;// 保存当前类的所有方法.Array<Method*>* _methods;// 如果当前类实现了接口,则保存该接口的default方法Array<Method*>* _default_methods;// 保存当前类所有方法的位置信息Array<int>*     _method_ordering;// 保存当前类所有default方法在虚函数表中的位置信息Array<int>*     _default_vtable_indices;// 保存当前类的field信息(包括static field),数组结构为:// f1: [access, name index, sig index, initial value index, low_offset, high_offset]// f2: [access, name index, sig index, initial value index, low_offset, high_offset]//      ...// fn: [access, name index, sig index, initial value index, low_offset, high_offset]//     [generic signature index]//     [generic signature index]//     ...Array<u2>*      _fields;
...
}

注意到,_fields中的每个元素都包含了当前field都偏移量信息,如前文《Java对象表示——Oop-Klass模型(一)》所提到,这些偏移量用于在oop中找到对应field的地址。

虚函数表(vtable)

虚函数表(vtable)主要是为了实现Java中的虚分派功能而存在。HotSpot把Java中的方法都抽象成了Method对象,InstanceKlass中的成员属性_methods就保存了当前类所有方法对应的Method实例。HotSpot并没有显式地把虚函数表设计为Klass的field,而是提供了一个虚函数表视图,并在类初始化时创建出来。

// hotspot/src/share/vm/oops/instanceKlass.hpp
class InstanceKlass: public Klass {
...  // 返回一个新的vtable,在类初始化时创建klassVtable* vtable() const;inline Method* method_at_vtable(int index);
..
}
// 以下为方法对应实现
// hotspot/src/share/vm/oops/instanceKlass.cpp
...
// vtable()的实现
klassVtable* InstanceKlass::vtable() const {return new klassVtable(this, start_of_vtable(), vtable_length() / vtableEntry::size());
}
// method_at_vtable()的实现
inline Method* InstanceKlass::method_at_vtable(int index)  {... // 校验逻辑vtableEntry* ve = (vtableEntry*)start_of_vtable();return ve[index].method();
}

一个klassVtable可看成是由多个vtableEntry组成的数组,其中每个元素vtableEntry里面都包含了一个方法的地址。在进行虚分派时,JVM会根据方法在klassVtable中的索引,找到对应的vtableEntry,进而得到方法的实际地址,最后根据该地址找到方法的字节码并执行。

vtalbe结构

总结

这两篇文章我们探讨了HotSpot的Oop-Klass对象模型,其中Oop表示对象的实例,存储在堆中;Klass表示对象的类型,存储在方法区中。但这也只是讲述了Oop-Klass对象模型中最基础的部分,该模型所包含的内容还远不止这些,如果想要更加全面而深入地了解Oop-Klass对象模型,最好的方法是阅读HotSpot的源码。

最后,我们通过一张图来总结这两篇文章所讲述的内容:

Oop-Klass对象模型在内存中的分布

这篇关于Java对象表示——Oop-Klass模型(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/232481

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2