首先我们要知道:

在jvm的内存结构中,对象保存在堆中,而我们在对对象进行操作时,其实操作的是对象的引用。

Java对象包含三个部分

一个Java对象可以分为三部分存储在内存中,分别是:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

  1. 对象头(包含锁状态标志,线程持有的锁等标志)
  2. 实例数据
  3. 对齐填充

oop-klass model(hotspot jvm中的对象模型)

Java虚拟机的底层是使用c++实现,而jvm并没有根据一个Java的实例对象去创建对应的c++对象,而是设计了一个oop-klass model ;

  1. OOP(Ordinary Object Pointer):普通对象指针; 表示一个实例信息
  2. Klass:描述对象实例的具体类型, 含了元数据和方法信息
  3. 创建目的:不想让每个对象中都含有一个vtable(虚函数表)

类就是一类事物的抽象概括。

OOP体系:

  1. OOPs模块中包含了多个子模块,每个子模块对应一个类型,每一个类型的OOP都代表一个在JVM内部使用的特定对象的类型。
  2. 在Java程序运行过程中,每创建一个新的对象,在JVM内部就会相应的创建一个对应类型的OOP对象。

代码:

  1. typedef class oopDesc* oop;

// 定义了oop共同的基类,其他的类型都是它的子类

  1. typedef class instanceOopDesc* instanceOop;

/* 表示一个Java类型实例,每当我们new一个对象时,

JVM都会创建一个instanceOopDesc */

  1. typedef class methodOopDesc* methodOop;

// 表示一个Java方法

  1. typedef class constMethodOopDesc* constMethodOop;

// 表示一个Java方法中的不变信息

  1. typedef class MethodDataOopDesc* methodDataOop;

// 记录性能信息的数据结构

  1. typedef class arrayOopDesc* arrayOop;

/* 定义了数组oops的抽象基类,下面两个类相当于此类的子类,

new一个数组时会建立此对象*/

  1. typedef class objArrayOopDesc* objArrayOop;

// 表示持有一个oops数组,对应存储对象的数组

  1. typedef class typeArrayOopDesc* typeArrayOop;

// 表示容纳基本类型的数组,对应存储基本类型的数组

  1. typedef class constantPoolOopDesc* constantPoolOop;

// 表示在class文件中描述的常量池

  1. typedef class constantPoolCacheOopDesc* constantPoolCacheOop;

// 常量池缓存

  1. typedef class klassOopDesc* klassOop;

// 描述一个与Java类对等的C++类

  1. typedef class markOopDesc* markOop;

// 表示对象头

OopDesc结构:

class oopDesc {

friend class VMStructs;

private:

/*

* 实际上也是代表了instanceOopDesc、arrayOopDesc和OopDesc

* 包含了markOop _mark和union_matadata两部分

*/

volatile markOop _mark; // 保存锁标记、gc分代等信息

union _metadata { wideKlassOop _klass; // 普通指针,

// 压缩类指针,和普通指针都指向instanceKlass 对象

narrowOop _compressed_klass; } _metadata;

private:

// 实例数据保存的位置

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) const;

address* address_field_addr(int offset) const; }

/* instanceOopDesc和arrayOopDesc都直接继承了oopDesc,

都没有增加其他的数据结构 */

class instanceOopDesc : public oopDesc {

}

class arrayOopDesc : public oopDesc { }

  1. 职能:表示对象的实例数据,不含任何虚函数
  2. 对象在内存中的基本形式就是oop
  3. 对象所属的类也是一种oop,即klassOop,对应的klass是klassKlass

Klass体系:

结构:

  • class Klass;

// klassOop的一部分,用来描述语言层的类型,其他所有类的父类

  • class instanceKlass;

// 在虚拟机层面描述一个Java类,每一个已加载的Java类都会创建一个此对象,

// 在JVM层表示Java类

  • class instanceMirrorKlass;

// 专有instantKlass,表示java.lang.Class的Klass

  • class instanceRefKlass;

// 专有instantKlass,表示java.lang.ref.Reference的子类的Klass

  • class methodKlass; // 表示methodOop的Klass
  • class constMethodKlass; // 表示constMethodOop的Klass
  • class methodDataKlass; // 表示methodDataOop的Klass
  • class klassKlass; // 最为klass链的端点,klassKlass的Klass就是它自身
  • class instanceKlassKlass; // 表示instanceKlass的Klass
  • class arrayKlassKlass;

// 表示arrayKlass的Klass

  • class objArrayKlassKlass; // 表示objArrayKlass的Klass
  • class typeArrayKlassKlass; // 表示typeArrayKlass的Klass
  • class arrayKlass; // 表示array类型的抽象基类
  • class objArrayKlass; // 表示objArrayOop的Klass
  • class typeArrayKlass; // 表示typeArrayOop的Klass
  • class constantPoolKlass; // 表示constantPoolOop的Klass
  • class constantPoolCacheKlass;

// 表示constantPoolCacheOop的Klass

功能:

  • 实现语言层面的Java类(在Klass基类中已经实现)
  • 实现Java对象的分发功能(由Klass子类提供虚函数实现)

目的:为了实现虚函数多态,提供了虚函数表。

  • instanceKlass的内部结构:
  • objArrayOop _methods;

// 类拥有的方法

  • typeArrayOop _method_ordering;

//描述方法顺序

  • objArrayOop _local_interfaces;

//实现的接口

  • objArrayOop _transitive_interfaces;

//继承的接口

  • typeArrayOop _fields;

//域

  • constantPoolOop _constants;

//常量

  • oop _class_loader;

//类加载器

  • oop _protection_domain;

//protected域

....

HotSpotJVM的设计这把对象一拆为二,分为Klass和oop,其中oop的只能主要在于表示对象的实例数据,所以其中不含有任何虚函数,而klass为了实现虚函数多态,所以提供了虚函数表。所以,关于java的多态,其实也有虚函数的影子在。

instanceKlass

JVM在运行时,需要一种用来标识Java内部类型的机制。在HotSpot中的解决方案是:为每一个已加载的Java类创建一个instanceKlass对象,用来在JVM层表示Java类。

结构:

//类拥有的方法列表

objArrayOop     _methods;

//描述方法顺序

typeArrayOop    _method_ordering;

//实现的接口

objArrayOop     _local_interfaces;

//继承的接口

objArrayOop     _transitive_interfaces;

//域

typeArrayOop    _fields;

//常量

constantPoolOop _constants;

//类加载器

oop             _class_loader;

//protected域

oop             _protection_domain;

....

在JVM中,对象在内存中的基本存在形式就是oop。那么,对象所属的类,在JVM中也是一种对象,因此它们实际上也会被组织成一种oop,即klassOop。同样的,对于klassOop,也有对应的一个klass来描述,它就是klassKlass,也是klass的一个子类。

klassKlass作为oop的klass链的端点。关于对象和数组的klass链大致如下图:

oop-klass-klassKlass关系图:

符号引用

符号引用就是用一组符号来描述所引用的目标,我们都知道在Java中,通常情况下我们写的一个Java类被编译以后都是一个class文件,在编译的时候,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。

一般一个对象的创建就是从new开始的,而操作这些创建指令的就是jvm了,首先当你开始new的时候,jvm会先去查找一个符号引用,如果找不到这个符号引用就说明这个类还没有被加载,因此jvm就会进行类加载,然后符号引用被解析完成,紧接着jvm会为对象在堆内存中分配内存,也就是说我们这个user对象就在堆内存中有一块内存空间了。

HotSpot虚拟机实现的Java对象包括三个部分:对象头,实例字段和对齐填充

为对象分配完堆内存之后,jvm会将该内存进行零值初始化。

内存存储:

关于一个Java对象,他的存储是怎样的,一般很多人会回答:对象存储在堆上。稍微好一点的人会回答:对象存储在堆上,对象的引用存储在栈上。今天,再给你一个更加显得牛逼的回答:

对象的实例(instantOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。

其实如果细追究的话,上面这句话有点故意卖弄的意思。因为我们都知道。方法区用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 所谓加载的类信息,其实不就是给每一个被加载的类都创建了一个 instantKlass对象么。

示例

class Model{
public static int a = 1;
private final int NUMBER = 2;
public int b;
public int c = 3;public Model(int b){
this.b = b;
}public static void main(String[] args){
int d = 10;
Model modelA = new Model(2);
Model modelB = new Model(3);
}
}

存储结构:

总结:

在Java中,JVM中的对象模型包含两部分:Oop和Klass,在类被加载的时候,JVM会给类创建一个instanceKlass,其中包含了类信息、常量、静态变量、即时编译器编译后的代码等,存储在方法区,用来在JVM层表示该Java类。而使用new一个对象后,JVM就会创建一个instanceOopDesc对象,该对象包含对象头和实例数据,对象头中保存的是锁的状态标志等信息,元数据则实际上是一个指针,指向instanceKlass。

Java对象模型---对象头(Mark Word)

对象自身的运行时数据

这部分存储包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据被官方称为Mark Word,在32位和64位的虚拟机中的大小分别为32bit和64bit。

由于对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以提高存储空间的利用率。即这部分数据会根据对象的状态来分配存储空间。

对象的类型指针

即指向对象的类元数据的指针。虚拟机可以通过该指针判定对象实例属于哪个类。

在Java对象中比较特殊的是Java数组,一个数组实例的对象头中必须记录数组的长度。JVM可以通过对象头中的数组长度数据来判定数组的大小,这是访问数组类型的元数据无法得到的。

对象的实例数据

前面提到对象头是对象的额外开销,只有实例数据才是一个对象实例存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分内容同时记录了子类从父类继承所得的各类型数据。

填充

对齐填充在对象数据中并不是必然的,只是起着占位符的作用,没有特别含义。HotSpot要求对象起始地址必须是8字节的整数倍。对象头的大小刚好符合要求,因此当实例数据没有对齐时,就需要通过填充来对齐数据。

获取类的元数据

虚拟机在加载类的时候会将类的信息、常量、静态变量和即时编译器编译后的代码等数据存储在方法区(Method Area)。类的元数据,即类的数据描述,也被存在方法区。我们知道对象头中会存有对象的类型指针,通过类型指针可以获取类的元数据。因此,对象的类型指针其实指向的是方法区的某个存有类信息的地址。

但是,并不是每个对象实例都存有对象的类型指针。根据对象访问定位方法的不同,对象的类型指针被存放在不同的区域。

  • 通过句柄访问对象

    • 对象的类型指针被存放在句柄池中;
  • 通过Reference指针直接访问对象
    • 对象的类型指针被存放在对象本身的数据中。

比较来说:

  • 使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,当对象被移动(垃圾收集时会经常移动对象)时智慧改变句柄中实例数据执政,而reference本身不需要修改。
  • 使用直接指针访问方式的最大好处就是速度快,节省了一次指针定位的时间开销(对象的访问在java中也非常频繁)

因此,Java的对象数据存储可以理解为:

  • 引用类型(指向对象的Reference)

    • 存储在栈中
  • 对象的类的元数据 (Class MetaData)
    • 存储在方法区中
  • 对象的实例数据
    • 存储在堆中

对象内存布局

  • 存储的是与对象本身定义的数据无关的额外存储成本,其数据结构不固定。
  • 32位JVM中,对象不同装填的mark word各个比特位区间图示如下:

对象五种状态:无锁态、轻量级锁、重量级锁、GC标记和偏向锁。

HotSpot中对象头主要包含两部分

第一:

用于存储对象自身的运行时数据,如上表中的对象哈希码,对象分代年龄,偏向线程id,偏向时间戳等。

第二:

类型指针了,我们看表中也有指针字样,那么这部分主要就是杜希昂指向它的类元数据的指针了,虚拟机就是通过这个指针来确定这个对象时那个实例。

偏向锁和重量级锁

  • 偏向锁:主要解决无竞争下的锁性能问题

    • 按照HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作,CAS操作会延迟本地调用
    • 偏向锁会偏向第一个访问锁的程序,如果接下来的运行过程中,该锁没有被其他线程访问,则持有偏向锁的线程将永远不需要触发同步。
    • 但是如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁
    • 只能在单线程中起作用
  • 轻量级锁:为了在无多线程竞争的环境中使用CAS来替代synchronized。减少传统的重量级锁使用操作系统互斥量产生的性能消耗,是为了减少多线程进入互斥的几率。并非替代互斥。

参考资料:

《深入理解Java虚拟机》

http://www.cnblogs.com/chenyangyao/p/5245669.html

深入理解多线程(二)—— Java的对象模型-HollisChuang's Blog

深入理解多线程(三)—— Java的对象头-HollisChuang's Blog

JVM成神之路-Java对象模型相关推荐

  1. JVM成神之路-Java内存模型(JMM)

    Java 内存模型基础 什么是 Java 内存模型(JMM-共享内存模型) 内存模型描述了程序中各个变量(实例域.静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量 ...

  2. JVM成神之路-Java垃圾回收

    Java垃圾回收机制 为什么要进行垃圾回收? 随着程序的运行,内存中存在的实例对象.变量等信息占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至会因为可用内存不足造成一些不必要 ...

  3. cad的lisp程序大集合_大数据成神之路-Java高级特性增强(CopyOnWriteArraySet)

    大数据成神之路 大数据成神之路: 点我去成神之路系列目录^_^ 预计更新500+篇文章,已经更新40+篇~ 本系列的大纲会根据实际情况进行调整,欢迎大家关注~ 导语 CopyOnWriteArrayS ...

  4. Java工程师成神之路java基础知识之集合类(二)

    Java 8中Map相关的红黑树的引用背景.原理等 HashMap的容量.扩容 很多人在通过阅读源码的方式学习Java,这是个很好的方式.而JDK的源码自然是首选.在JDK的众多类中,我觉得HashM ...

  5. JVM成神之路-HotSpot虚拟机-编译原理、JIT、编译优化

    Java编译原理 什么是字节码.机器码.本地代码? 字节码是指平常所了解的 .class 文件,Java 代码通过 javac 命令编译成字节码 机器码和本地代码都是指机器可以直接识别运行的代码,也就 ...

  6. JVM成神之路-JVM内存结构

    线程私有 程序计数器 当前线程所执行的字节码的行号指示器 对于 Java 方法,记录正在执行的虚拟机字节码指令的地址:对于 native 方法,记录值为空(Undefined) 唯一一个Java 虚拟 ...

  7. JVM成神之路-类加载机制-双亲委派,破坏双亲委派

    概述 概念 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接时候用的Java类型. 类的生命周期 类从被加载到虚拟机内存中开始,到卸载出内存 ...

  8. JVM成神之路(二)-- JDK,JER与JVM的关系

    关于文章 JVM系列每一篇文章我都录制了对应的视频,由于CSDN不能直接发送视频资料,如果想要作者录制的免费视频资料,可以加QQ:3139882589.这个是作者QQ,如果有问题想跟作者咨询,也可以加 ...

  9. JVM成神之路-JVM引用模型

    本文通过探析Java中的引用模型,分析比较强引用.软引用.弱引用.虚引用的概念及使用场景,知其然且知其所以然,希望给大家在实际开发实践.学习开源项目提供参考. Java的引用 对于Java中的垃圾回收 ...

最新文章

  1. php7.0 + mysql5.7.10 + nginx7.0 web开发环境搭建(CentOS7)
  2. 用AndroidSDK中的Face Detector实现人脸识别
  3. PAT (Basic Level) Practice (中文)1009 说反话 (20 分)
  4. zephyr 系统--- 内存池使用方法
  5. vxe-table安装和使用
  6. node 加密解密模块_NODE.JS加密模块CRYPTO常用方法介绍
  7. LINQ to SharePoint 试用感受, 欢迎讨论~
  8. 超级网管员系列图书介绍
  9. [GNU LD系列 3.1]一些基本的链接脚本概念
  10. Python-视频爬取示例对小白友好
  11. c#获取Windows Mobile短信
  12. 基于角色的用户权限设计的问题,大家探讨下
  13. 如何使用NSOperations和NSOperationQueues
  14. 用泰勒公式编写一个sin函数--C语言
  15. ffmpeg播放器声音效果2-变速不变调及变调
  16. 笔记本光驱在计算机里不显示器,笔记本怎么解决识别不了光驱
  17. 如何查看suse系统服务器sn,suse 配置 - sncder的个人空间 - OSCHINA - 中文开源技术交流社区...
  18. 为什么自动驾驶使用linux,一旦用上,欲罢不能,为何ADiGO 3.0自动驾驶系统如此让人上瘾?...
  19. HTTPS背后的加密算法
  20. java if语句的用法

热门文章

  1. 刀剑乱舞服务器显示空,刀剑乱舞online常见问题及解决方法
  2. C++ cout打印生成两位16进制数
  3. 蓝鲸环境部署与集成安装:
  4. python下最全的cv2图像处理入门知识!!!!灰度图 RGB图 针对某种颜色做提取、高斯模糊等等
  5. rk3128 手动挂载 U 盘
  6. PHP高频面试题 - 详述一次完整的HTTP请求过程
  7. CSS3之动画模块实现轮播图
  8. 《你究竟有多想成功》
  9. 数据结构查找算法(c语言)
  10. docker进行ElasticSearch集群部署