深入理解多线程(二)—— Java的对象模型
2018-11-13 21:21 GarfieldEr007 阅读(688) 评论(0) 编辑 收藏 举报上一篇文章中简单介绍过synchronized
关键字的方式,其中,同步代码块使用monitorenter
和monitorexit
两个指令实现,同步方法使用ACC_SYNCHRONIZED
标记符实现。后面几篇文章会从JVM源码的角度更加深入,层层剥开synchronized
的面纱。
在进入正题之前,肯定有些基础知识需要铺垫,那么先来看一下一个容易被忽略的但是又很重要的知识点 —— Java对象模型 。
大家都知道的是,Java对象保存在堆内存中。在内存中,一个Java对象包含三部分:对象头、实例数据和对齐填充。其中对象头是一个很关键的部分,因为对象头中包含锁状态标志、线程持有的锁等标志。这篇文章就主要从Java对象模型入手,找一找我们关系的对象头以及对象头中和锁相关的运行时数据在JVM中是如何表示的。
Java的对象模型
任何一个接触过Java的人都知道,Java是一种面向对象语言。在学习Java的过程中你一定对下面两句话不陌生:
-
1、在面向对象的软件中,对象(Object)是某一个类(Class)的实例。 维基百科
-
2、一切皆对象 Thinking In Java
我们还知道,在JVM的内存结构中,对象保存在堆内存中,而我们在对对象进行操作时,其实操作的是对象的引用。那么对象本身在JVM中的结构是什么样的呢?本文的所有分析均基于HotSpot虚拟机。
oop-klass model
HotSpot是基于c++实现,而c++是一门面向对象的语言,本身是具备面向对象基本特征的,所以Java中的对象表示,最简单的做法是为每个Java类生成一个c++类与之对应。但HotSpot JVM并没有这么做,而是设计了一个OOP-Klass Model
。OOP(Ordinary Object Pointer
)指的是普通对象指针,而Klass
用来描述对象实例的具体类型。
为什么HotSpot要设计一套oop-klass model
呢?答案是:HotSopt JVM的设计者不想让每个对象中都含有一个vtable
(虚函数表)
这个解释似乎可以说得通。众所周知,C++和Java都是面向对象的语言,面向对象语言有一个很重要的特性就是多态。关于多态的实现,C++和Java有着本质的区别。
多态是面向对象的最主要的特性之一,是一种方法的动态绑定,实现运行时的类型决定对象的行为。多态的表现形式是父类指针或引用指向子类对象,在这个指针上调用的方法使用子类的实现版本。多态是IOC、模板模式实现的关键。
在C++中通过虚函数表的方式实现多态,每个包含虚函数的类都具有一个虚函数表(virtual table),在这个类对象的地址空间的最靠前的位置存有指向虚函数表的指针。在虚函数表中,按照声明顺序依次排列所有的虚函数。由于C++在运行时并不维护类型信息,所以在编译时直接在子类的虚函数表中将被子类重写的方法替换掉。
在Java中,在运行时会维持类型信息以及类的继承体系。每一个类会在方法区中对应一个数据结构用于存放类的信息,可以通过Class对象访问这个数据结构。其中,类型信息具有superclass属性指示了其超类,以及这个类对应的方法表(其中只包含这个类定义的方法,不包括从超类继承来的)。而每一个在堆上创建的对象,都具有一个指向方法区类型信息数据结构的指针,通过这个指针可以确定对象的类型。
上面这段是我从网上摘取过来的,说的有一定道理,但是也不全对。至于为啥,我会在后文介绍到Klass的时候细说。
关于opp-klass模型的整体定义,在HotSpot的源码中可以找到。
oops模块可以分成两个相对独立的部分:OOP框架和Klass框架。
在oopsHierarchy.hpp里定义了oop和klass各自的体系。
oop-klass结构
oop体系:
//定义了oops共同基类
typedef class oopDesc* oop;
//表示一个Java类型实例
typedef class instanceOopDesc* instanceOop;
//表示一个Java方法
typedef class methodOopDesc* methodOop;
//表示一个Java方法中的不变信息
typedef class constMethodOopDesc* constMethodOop;
//记录性能信息的数据结构
typedef class methodDataOopDesc* methodDataOop;
//定义了数组OOPS的抽象基类
typedef class arrayOopDesc* arrayOop;
//表示持有一个OOPS数组
typedef class objArrayOopDesc* objArrayOop;
//表示容纳基本类型的数组
typedef class typeArrayOopDesc* typeArrayOop;
//表示在Class文件中描述的常量池
typedef class constantPoolOopDesc* constantPoolOop;
//常量池告诉缓存
typedef class constantPoolCacheOopDesc* constantPoolCacheOop;
//描述一个与Java类对等的C++类
typedef class klassOopDesc* klassOop;
//表示对象头
typedef class markOopDesc* markOop;
上面列出的是整个Oops模块的组成结构,其中包含多个子模块。每一个子模块对应一个类型,每一个类型的OOP都代表一个在JVM内部使用的特定对象的类型。
从上面的代码中可以看到,有一个变量opp的类型是oppDesc
,OOPS类的共同基类型为oopDesc
。
在Java程序运行过程中,每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的OOP对象。在HotSpot中,根据JVM内部使用的对象业务类型,具有多种oopDesc
的子类。除了oppDesc
类型外,opp体系中还有很多instanceOopDesc
、arrayOopDesc
等类型的实例,他们都是oopDesc
的子类。
这些OOPS在JVM内部有着不同的用途,例如,instanceOopDesc
表示类实例,arrayOopDesc
表示数组。也就是说,当我们使用new
创建一个Java对象实例的时候,JVM会创建一个instanceOopDesc
对象来表示这个Java对象。同理,当我们使用new
创建一个Java数组实例的时候,JVM会创建一个arrayOopDesc
对象来表示这个数组对象。
在HotSpot中,oopDesc类定义在oop.hpp中,instanceOopDesc定义在instanceOop.hpp中,arrayOopDesc定义在arrayOop.hpp中。
简单看一下相关定义:
class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {
wideKlassOop _klass;
narrowOop _compressed_klass;
} _metadata;
private:
// field addresses in oop
void* field_base(int offset) const;
jbyte* byte_field_addr(int offset) const;
jchar* char_field_addr(int offset) const;
jboolean* bool_field_addr(int offset) const;
jint* int_field_addr(int offset) const;
jshort* short_field_addr(int offset) const;
jlong* long_field_addr(int offset) const;
jfloat* float_field_addr(int offset) const;
jdouble* double_field_addr(int offset)