上一篇文章简单介绍了synchronized关键字的使用方法。其中,同步代码块使用monitorenter和monitorexit两条指令实现,同步方法使用ACC_SYNCHRONIZED标签完成。接下来的几篇文章将从JVM源码的角度进行更深入的探讨,一层层揭开synchronized的面纱。在进入正题之前,肯定有一些基础知识需要铺垫,那么我们先来看看一个容易被忽视但又非常重要的知识点——Java对象模型。大家都知道Java对象是存放在堆内存中的。在内存中,一个Java对象由三部分组成:对象头、实例数据和对齐填充。其中,对象头是一个非常关键的部分,因为对象头包含了锁状态标志、线程持有的锁等标志。本文主要从Java对象模型入手,了解一下我们关系的对象头以及对象头中锁相关的运行时数据在JVM中是如何表示的。Java的对象模型接触过Java的人都知道Java是一种面向对象的语言。在学习Java的过程中,下面两句话你一定很熟悉:1、在面向对象的软件中,一个对象(Object)是某个类(Class)的一个实例。2、万物皆对象我们也知道,在JVM的内存结构中,对象是存放在堆内存中的,而我们在对对象进行操作的时候,实际上是在对对象的引用进行操作。那么JVM中对象本身的结构是怎样的呢?本文所有分析均基于HotSpot虚拟机。oop-klassmodelHotSpot是基于c++实现的,而c++是一门面向对象的语言,其本身就具有面向对象的基本特性,所以Java中的对象表示,最简单的方式就是生成一个c++类并与之对应。但是HotSpotJVM并没有这样做,而是设计了一个OOP-KlassModel。OOP(OrdinaryObjectPointer)是指普通对象指针,Klass用于描述对象实例的具体类型。为什么HotSpot设计一个oop-class模型?答案是:HotSoptJVM的设计者不希望每个对象都包含一个vtable(虚函数表)。这个解释似乎有道理。众所周知,C++和Java都是面向对象的语言,而面向对象语言的一个重要特征就是多态性。关于多态性的实现,C++和Java有着本质的区别。多态性是面向对象最重要的特征之一。它是方法的动态绑定,实现了运行时的类型决定了对象的行为。多态的表现是父类指针或引用指向子类对象,在这个指针上调用的方法使用子类的实现版本。多态是实现IOC和模板模式的关键。在C++中,多态性是通过虚函数表的方式实现的。每个包含虚函数的类都有一个虚函数表(virtualtable),一个指向虚函数表的地方存放在该类对象地址空间的最前端。指针。在虚函数表中,所有的虚函数都是按照声明的顺序排列的。由于C++在运行时不维护类型信息,子类重写的方法在编译时直接在子类的虚函数表中替换。在Java中,类型信息和类继承是在运行时维护的。每个类都会在方法区对应一个数据结构,用于存储类信息,这个数据结构可以通过Class对象访问。其中,类型信息有一个superclass属性,表示它的超类,以及这个类对应的方法表(只包括本类定义的方法,不包括从超类继承的方法)。并且在堆上创建的每个对象都有一个指向方法区类型信息数据结构的指针,通过这个指针可以判断对象的类型。上面一段摘自网络,我说的有一定道理,但也不完全正确。至于为什么,后面介绍Klass的时候会细说。opp-klass模型的整体定义可以在HotSpot的源代码中找到。oops模块可以分为两个相对独立的部分:OOP框架和Klass框架。oops和klass各自的体系定义在oopsHierarchy.hpp中。oopoopsystem://定义oops的公共基类typedefclassoopDesc*oop;//表示一个Java类型实例typedefclassinstanceOopDesc*instanceOop;//表示一个Java方法typedefclassmethodOopDesc*methodOop;//表示一个Java方法中的常量信息typedefclassconstMethodOopDesc*constMethodOop;//记录性能信息的数据结构typedefclassmethodDataOopDesc*methodDataOop;//定义数组OOPS的抽象基类typedefclassarrayOopDesc*arrayOop;//表示持有一个OOPS数组typedefclassobjArrayOopDesc*objArrayOop;//表示包含基本类型的数组typedefclasstypeArrayOopDesc*typeArrayOop;//表示Class文件中描述的常量池typedefclassconstantPoolOopDesc*constantPoolOop;//常量池告诉缓存typedefclassconstantPoolCacheOopDesc*constantPoolCacheOop;//描述了一个C++类,相当于一个Java类typedefclassklassOopDesc*klassOop;//表示对象头类型定义类markOopDesc*markOop;上面列出的是整个Oops模块的结构,其中包含多个子模块。每个子模块对应一个类型,OOP的每个类型代表JVM内部使用的特定对象类型。从上面的代码可以看出,有一个类型为oppDesc的变量opp,OOPS类的公共基类型为oopDesc。classoopDesc{friendclassVMStructs;private:volatilemarkOop_mark;union_metadata{wideKlassOop_klass;narrowOop_compressed_klass;}_metadata;}在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中,oop.hpp中定义了oopDesc类,instanceOop.hpp中定义了instanceOopDesc,arrayOop.hpp中定义了arrayOopDesc。简单看一下相关定义:classinstanceOopDesc:publicoopDesc{}classarrayOopDesc:publicoopDesc{}从上面的源码可以看出,instanceOopDesc其实是继承了oopDesc,并没有增加其他数据结构,也就是instanceOopDesc包含两部分数据:markOop_mark和union_metadata。这里的markOop大家可能比较熟悉,这不就是OOPS体系的一部分吗,上面的注释里说了,就是objectheader的意思。_metadata是一个联合体,这个字段称为元数据指针。指向描述类型的Klass对象的指针。在HotSpot虚拟机中,对象在内存中的布局可以分为三个区域:对象头、实例数据和对齐填充。在虚拟机内部,一个Java对象对应一个instanceOopDesc对象,对象中有两个字段分别代表对象头和实例数据。那就是_mark和_metadata。正如我们在文章开头所说的,我们之所以要写这篇文章,是因为对象头中有与锁相关的运行时数据。这些运行时数据是实现synchronized和其他类型锁的重要依据。因为本文主要介绍的是oop-klass模型,所以对象头这里暂时不展开,下一篇再介绍。前面介绍的_metadata是一个union,其中_klass是普通指针,_compressed_klass是压缩类指针。在深入介绍之前,我们先来介绍一下oop-Klass中的另一个主角klass。klasklasssystem//klassOop的一部分,用来描述语言层的类型classKlass;//描述一个虚拟机级别的Java类classinstanceKlass;//专有的instantKlass,代表java.lang.Class的KlassclassinstanceMirrorKlass;//专有的instantKlass,KlassclassinstanceRefKlass表示java.lang.ref.Reference的子类;//KlassclassmethodKlass表示methodOop;//KlassclassconstMethodKlass表示constMethodOop;//KlassclassmethodDataKlass表示methodDataOop;//作为klass链的端点,klassKlass的Klass是自己的classklas;//KlassclassinstanceKlassKlassforinstanceKlass;//KlassclassarrayKlassKlassforarrayKlass;/KlassclassobjArrayKlassKlassforobjArrayKlass;/KlassclasstypeArrayKlassKlassfortypeArrayKlass;//KlassclasstypeArrayKlassKlassfortypeArray;KlassclasstypeArrayKlassoftypeArrayOop;//KlassclassconstantPoolKlass代表constantPoolOop;//KlassclassconstantPoolCacheKlass代表constantPoolCacheOop;就像oopDesc是其他oop类型的父类一样,Klass类是其他klass类型的父类。Klass向JVM提供了两个功能:在语言层面实现Java类(已经在Klass基类中实现)实现Java对象的分布功能(通过Klass子类提供的虚函数实现)。oop-klass模型是因为HotSoptJVM的设计者不希望每个对象都包含一个虚函数表。HotSoptJVM的设计者将对象拆分为两部分,klass和oop。oop的作用主要是表示对象的实例数据,所以不包含任何虚函数。为了实现虚函数多态,klass提供了虚函数表。所以,关于Java的多态,其实是有虚函数的影子的。_metadata是commonbody,其中_klass是普通指针,_compressed_klass是压缩类指针。两个指针都指向instanceKlass对象,用来描述对象的具体类型。instanceKlassJVM在运行时,需要一种机制来识别Java内部类型。HotSpot中的解决方案是:为每个加载的Java类创建一个instanceKlass对象,用于在JVM层表示Java类。我们看下instanceKlass的内部结构://类objArrayOop_methods拥有的方法列表;//描述方法顺序typeArrayOop_method_ordering;//接口实现objArrayOop_local_interfaces;//继承接口objArrayOop_transitive_interfaces;//域typeArrayOop_fields;//常量constantPoolOop_constants;//Classloaderoop_class_loader;//protecteddomainoop_protection_domain;...可以看到,一个类该有的东西基本都在这里了。这里还有一点需要简单介绍一下。在JVM中,对象在内存中的基本存在形式是oop。那么,对象所属的类在JVM中也是一种对象,所以它们实际上会被组织成一种oop,即klassOop。同样,对于klassOop,也有对应的klass来描述,就是klassKlass,它也是klass的子类。klassKlass作为oop的类链的端点。关于对象和数组的klass链大致如下:在这种设计下,JVM对内存的分配和回收可以统一管理。oop-klass-klassKlass之间的关系如图所示:内存存储关于一个Java对象,它是如何存储的,一般很多人会回答:对象存储在堆上。稍微好一点的人会回答:对象存放在堆上,对象的引用存放在栈上。今天给大家一个更厉害的答案:对象的实例(instantOopDesc)存放在堆上,对象的元数据(instantKlass)存放在方法区,对象的引用存放在堆。其实仔细看的话,上面这句话有点刻意炫耀的意思。因为我们都知道。方法区用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等数据。所谓加载类信息,其实就是为每一个加载类创建了一个instantKlass对象。谈话很便宜,给我看代码:classModel{publicstaticinta=1;publicintb;publicModel(intb){this.b=b;}}publicstaticvoidmain(String[]args){intc=10;ModelmodelA=newModel(2);ModelmodelB=newModel(3);}存储结构如下:总结一下每个Java类,当它被JVM加载时,JVM会为这个类创建一个instanceKlass,保存在方法区,用它来表示JVM层的Java类。当我们在Java代码中使用new创建对象时,JVM会创建一个instanceOopDesc对象,它包含两部分信息,方法头和元数据。对象头中有一些运行时数据,包括与多线程相关的锁的信息。元数据实际上维护了指针,指针指向对象所属类的instanceKlass。【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读更多本作者好文
