Class a = new ClassA ( );
当Hotspot执行到这里时,会先将 ClassA 这个类型加载到 perm 区 ( 也叫方法区 ),然后在 Hotspot 堆中为其实例对象a开辟一块内存空间,存放实例数据。在 JVM加载ClassA到 perm 区时,JVM就会创建一个instanceKlass,instanceKlass中保存了 ClassA 这个 Java 类中所定义的一切信息,包括变量 、方法 、父类 、接 口、构造函数 、属性等,所以 instanceKlass 就是 ClassA这个Java类类型结构的对等体。而 instanceOop 这个“普通对象指针”对象中包含了一个指针,该指针就指向instanceKlass这个实例。在JVM实例化ClassA时,JVM又会在堆中创建一个instanceOop , instanceOop便是 ClassA 对象实例 a 在内存中的对等体,主要存储 ClassA 实例对象的成员变量。 其中,instanceOop 中有一个指针指向 instanceKlass ,通过这个指针,JVM便可以在运行期获取这个类实例对象的类元信息。
在JVM内部定义了3种结构去描述一种类型 :oop 、klass 和 handle 类。注意,这 3 种数据结构不仅能够描述外在的 Java 类 ,也能够描述 JVM内在的C++类型对象。
klass
-
klass主要描述 Java 类和 JVM内部C++类型的元信息和虚函数,这些元信息的实际值就保存在oop里面
-
// 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; // 基础类型的数组类
… -
klass字段
- // hotspot/src/share/vm/oops/klass.hpp
class Klass : public Metadata {
…
// 类名,其中普通类名和数组类名略有不同
// 普通类名如:java/lang/String,数组类名如:[Ljava/lang/String;
Symbol* _name;
// 最后一个secondary supertype
Klass* _secondary_super_cache;
// 保存所有secondary supertypes
Array<Klass*>* _secondary_supers;
// 保存所有primary supertypes的有序列表
Klass* _primary_supers[_primary_super_limit];
// 当前类所属的java/lang/Class对象对应的oop
oop _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;
…
}
- // hotspot/src/share/vm/oops/klass.hpp
oop
所谓oop,就是ordinary object pointer ,也即普通对象指针。但是究竟什么才是普通对象指针呢?
-
如果是调用JVM内部C++类型所对应的oop的函数 ,则不需要通过 handle 来中转,直接通过 oop 拿到指定的 klass便能实现
-
oop 中保存一个指针指向 klass ,这样在运行期JVM便能够知道每一个实例的数据结构和实际类型。
-
Hotspot里的 oop 指啥?
- Hotspot里的oop 其实就是 GC 所托管的指针,每一个 oop 都是一种 xxxOopDesc*类型的指针。所有oopDesc及其子类( 除神奇的 markOopDesc 外 ) 的实例都由 GC 所管理,这才是最最重要的,是 oop 区分 Hotspot 里所使用的其他指针类型的地方。
-
-
oop内存布局
- 对象的实例(instantOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。
-
oop结构
-
volatile markOop _mark;
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata;- Mark Word:instanceOopDesc中的_mark成员,允许压缩。它用于存储对象的运行时记录信息,如哈希值、GC分代年龄(Age)、锁状态标志(偏向锁、轻量级锁、重量级锁)、线程持有的锁、偏向线程ID、偏向时间戳等
- Mark Word:instanceOopDesc中的_mark成员,允许压缩。它用于存储对象的运行时记录信息,如哈希值、GC分代年龄(Age)、锁状态标志(偏向锁、轻量级锁、重量级锁)、线程持有的锁、偏向线程ID、偏向时间戳等
-
-
markOopDesc
- markOopDesc 指针本生存放了数据的值,而不是指向 对象的指针。所以markOop不受gc 托管, 当 栈退出时这个指针(值) 也就跟着被回收了。
handle
-
handle是对 oop 的行为的封装,在访问 Java 类时一定是通过 handle 内部指针得到 oop 实例的,再通过 oop 就能拿到 klass ,如此 handle 最终便能操纵 oop 的行为了(注意,如果是调用JVM内部C++类型所对应的oop的函数 ,则不需要通过 handle 来中转,直接通过 oop 拿到指定的 klass便能实现)
-
handler 多包装了一层有什么用?
- handle封装了oop,由于通过oop可以拿到 klass ,而 klass 是对 Java 类数据结构和方法的描述 ,因此 handle 间接封装了 klass。JVM内部使用一个 table 来存储 oop 指针。
如果说oop是对普通对象的直接引用,那么 handle 就是对普通对象的一种间接引用,中间隔了一层。但是JVM内部为何要使用这种间接引用呢?答案是,这完全是为GC考虑。具体表现在2个地方 :
通过handle,能够让 GC 知道其内部代码都有哪些地方持有 GC 所管理的对象的引用,这只需要扫描 handle 所对应的 table ,这样 JVM 便无须关注其内部到底哪些地方持有对普通对象的引用。
在GC过程中如果发生了对象移动(例如从新生代移到了老年代),那么JVM的内部引用无须跟着更改为被移动对象的新地址,JVM 只需要更改 handle table 里对应的指针即可 。
当我们使用new创建一个Java对象实例的时候,JVM会创建一个instanceOopDesc对象来表示这个Java对象;instanceOopDesc实际上就是继承了oopDesc,并没有增加其他的数据结构,也就是说instanceOopDesc中包含两部分数据:markOop _mark和union _metadata :他表示对象头。 _metadata是一个联合体,这个字段被称为元数据指针。指向描述类型Klass对象的指针。
markOop _mark(Mark Word)
instanceKlass的内部结构
//类拥有的方法列表
objArrayOop _methods;
//描述方法顺序
typeArrayOop _method_ordering;
//实现的接口
objArrayOop _local_interfaces;
//继承的接口
objArrayOop _transitive_interfaces;
//域
typeArrayOop _fields;
//常量
constantPoolOop _constants;
//类加载器
oop _class_loader;
//protected域
oop _protection_domain;
什么是OOP对象?
在java虚拟机执行内部,每当java程序创建一个类的实例,在JVM内部就会相应地创建一个对应类型的OOP对象。
概览
JVM内部基于oop-klass模型描述一个 Java 类 ,将一个 Java 类一拆为二分别描述,第一个模型是oop,第二个模型是klass。所谓oop,并不是object-oriented programming(面向对象编程),而是ordinary object pointer(普通对象指针),它用来表示对象的实例信息,看起来像个指针,而实际上对象实例数据都藏在指针所指向的内存首地址后面的一片内存区域中。 (理解:oop指向堆中实例对象所在内存区域的首地址)
而klass则包含元数据和方法信息,用来描述 Java 类而 klass 则包含元数据和方法信息,用来描述 Java 类或者JVM内部自带的C++类型信息。其实,klass便是前文一直在讲的数据结构,Java 类的继承信息、成员变量 、静态变量 、成员方法 、构造函数等信息都在 klass 中保存 ,JVM据此便可以在运行期反射出Java类的全部结构信息。当然,JVM本身所定义的用于描述Java类的C++类也使用klass去描述,这相当于使用另一种面向对象的机制去描述C++类这种本身便是面向对象的数据。