概述

对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度;

| 类型 | 32位JVM | 64位JVM|

| ------ ---- | ------------| --------- |

| markword | 32bit | 64bit |

| 类型指针 | 32bit |64bit ,开启指针压缩时为32bit |

| 数组长度 | 32bit |32bit |

header.png

compressed_header.png

可以看到

开启指针压缩时,markword占用8bytes,类型指针占用8bytes,共占用16bytes;

未开启指针压缩时,markword占用8bytes,类型指针占用4bytes,但由于java内存地址按照8bytes对齐,长度必须是8的倍数,因此会从12bytes补全到16bytes;

数组长度为4bytes,同样会进行对齐,补足到8bytes;

另外从上面的截图可以看到,开启指针压缩之后,对象类型指针为0xf800c005,但实际的类型指针为0x7c0060028;那么指针是如何压缩的呢?

实际上由于java地址一定是8的倍数,因此将0xf800c005*8即可得到实际的指针0x7c0060028,关于指针压缩的更多知识可参考官方文档。

markword结构

markword的结构,定义在markOop.hpp文件:

32 bits:

--------

hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)

JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)

size:32 ------------------------------------------>| (CMS free block)

PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

64 bits:

--------

unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)

JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)

PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)

size:64 ----------------------------------------------------->| (CMS free block)

unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)

JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)

narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)

unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

[ptr | 00] locked ptr points to real header on stack

[header | 0 | 01] unlocked regular object header

[ptr | 10] monitor inflated lock (header is wapped out)

[ptr | 11] marked used by markSweep to mark an object

由于目前基本都在使用64位JVM,此处不再对32位的结构进行详细说明:

偏向锁标识位

锁标识位

锁状态

存储内容

0

01

未锁定

hash code(31),年龄(4)

1

01

偏向锁

线程ID(54),时间戳(2),年龄(4)

00

轻量级锁

栈中锁记录的指针(64)

10

重量级锁

monitor的指针(64)

11

GC标记

空,不需要记录信息

此处,有几点要注意:

如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以通过System.identityHashCode获取;os::random产生hashcode的规则为:next_rand = (16807seed) mod (2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中;

GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15;

当处于轻量级锁、重量级锁时,记录的对象指针,根据JVM的说明,此时认为指针仍然是64位,最低两位假定为0;当处于偏向锁时,记录的为获得偏向锁的线程指针,该指针也是64位;

We assume that stack/thread pointers have the lowest two bits cleared.

ObjectMonitor* monitor() const {

assert(has_monitor(), "check");

// Use xor instead of &~ to provide one extra tag-bit check.

return (ObjectMonitor*) (value() ^ monitor_value);//monitor_value=2,value最右两位为10,因此异或之后最右两位为0

}

JavaThread* biased_locker() const {

assert(has_bias_pattern(), "should not call this otherwise");

return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));

//~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place)为11111111111111111111110010000000,计算后的结果中,低10位全部为0;

}

由于java中内存地址都是8的倍数,因此可以理解为最低3bit为0,因此假设轻量级和重量级锁的最低2位为0是成立的;但为什么偏向锁的最低10位都是0?查看markOop.hpp文件,发现有这么一句话:

// Alignment of JavaThread pointers encoded in object header required by biased locking

enum { biased_lock_alignment = 2 << (epoch_shift + epoch_bits)

//epoch_shift+epoch_bits=10

};

thread.hpp中重载了operator new:

void* operator new(size_t size) { return allocate(size, true); }

// ======= Thread ========

// Support for forcing alignment of thread objects for biased locking

void* Thread::allocate(size_t size, bool throw_excpt, MEMFLAGS flags) {

if (UseBiasedLocking) {

const int alignment = markOopDesc::biased_lock_alignment;//10

size_t aligned_size = size + (alignment - sizeof(intptr_t));

void* real_malloc_addr = throw_excpt? AllocateHeap(aligned_size, flags, CURRENT_PC)

: os::malloc(aligned_size, flags, CURRENT_PC);

void* aligned_addr = (void*) align_size_up((intptr_t) real_malloc_addr, alignment);

assert(((uintptr_t) aligned_addr + (uintptr_t) size) <=

((uintptr_t) real_malloc_addr + (uintptr_t) aligned_size),

"JavaThread alignment code overflowed allocated storage");

if (TraceBiasedLocking) {

if (aligned_addr != real_malloc_addr)

tty->print_cr("Aligned thread " INTPTR_FORMAT " to " INTPTR_FORMAT,

real_malloc_addr, aligned_addr);

}

((Thread*) aligned_addr)->_real_malloc_address = real_malloc_addr;

return aligned_addr;

} else {

return throw_excpt? AllocateHeap(size, flags, CURRENT_PC)

: os::malloc(size, flags, CURRENT_PC);

}

}

如果开启了偏移锁,在创建线程时,线程地址会进行对齐处理,保证低10位为0

实例数据

实例数据中主要包括对象的各种成员变量,包括基本类型和引用类型;static类型的变量会放到java/lang/Class中,而不会放到实例数据中;

对于引用类型的成员(包括string),存储的指针;对于基本类型,直接存储内容;通常会将基本类型存储在一起,引用类型存储在一起;

例如类Test的成员定义如下:

private static Test t1=new Test();

private Test t2;

private int a=5;

private Integer b=7;

private String c="112";

private BigDecimal d=new BigDecimal("5");

private long e=9l;

body.png

可以看到long e、int a为基本类型,存储在一起;其它的引用类型存储在一起;int占用4bytes,不足8bytes,自动补足到8bytes;

补充知识:java的对象物理结构,以及对象头中MarkWord与锁的关系

java 对象头

我们都知道,Java对象存储在堆(Heap)内存。那么一个Java对象到底包含什么呢?概括起来分为对象头、对象体和对齐字节。

如下图所示:

对象的几个部分的作用:

1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;

2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;

5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

了解了对象的总体结构,接下来深入地了解对象头的三个部分。

一、Mark Word(标记字)

以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。biased_lock和lock一起,表达的锁状态含义如下:

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。

thread:持有偏向锁的线程ID。

epoch:偏向锁的时间戳。

ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

二、Klass Word(类指针)

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。

开启该选项后,下列指针将压缩至32位:

每个Class的属性指针(即静态变量)

每个对象的属性指针(即对象变量)

普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

三、数组长度

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。

64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

以上这篇浅谈java对象结构 对象头 Markword就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

java对象头_浅谈java对象结构 对象头 Markword相关推荐

  1. java方法区对象类型_浅谈Java内存区域与对象创建过程

    一.java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则 ...

  2. java bitset用途_浅谈Java BitSet使用场景和代码示例

    搜索热词 @H_502_0@一.什么是BitSet? @H_502_0@ 注:以下内容来自JDK API: @H_502_0@ BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个b ...

  3. java 多线程同步_浅谈Java多线程(状态、同步等)

    Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...

  4. java手动回收_浅谈java是如何做资源回收补救的

    学习java的过程,我们经常谈论一个对象的回收,尤其是资源类型,如果没有显示的关闭,对象就被回收了,说明出现了资源泄漏.java本身为了防止这种情况,做了一些担保的方式,确保可以让未关闭的资源合理回收 ...

  5. java同名函数_浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); ...

  6. java并发计数器_浅谈java并发之计数器CountDownLatch

    CountDownLatch简介 CountDownLatch顾名思义,count + down + latch = 计数 + 减 + 门闩(这么拆分也是便于记忆=_=) 可以理解这个东西就是个计数器 ...

  7. java list翻转_浅谈Java数据结构中的常见问题

    1.常用数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素间的关系组成.常用的数据有:数组.栈.队列.链表.树.图.堆.散列表. 1)数组:在内存中连续存储多个元素的 ...

  8. java反射 用处_浅谈Java反射

    一.何为反射 反射就是对于任何一个类都能知道这个类的的所有属性和方法,并且对于任何一个对象都能调用他的属性和方法,而且能修改其属性. 二.反射的作用 就我的理解来看,通常我们在写代码的时会非常强调代码 ...

  9. 00005在java结果输出_浅谈Java反序列化漏洞原理(案例未完善后续补充)

    摘要: 0005,这个16进制流基本上也意味者java反序列化的开始:(2)HTTP:必有rO0AB,其实这就是aced0005的base64编码的结果:以上意味着存在Java反序列化,可尝试构造pa ...

最新文章

  1. mybaits四-3:获取保存数据的id
  2. NOIP2016普及组第三题——海港
  3. 远程连接 SuSE Linux 设置方法
  4. Beanstalked的初步了解和使用(包括利用beanstalkd 秒杀消息队列的实现)
  5. layui 数字步进器_图解全新奔驰S级:从“传统豪华”向“数字豪华”转型
  6. ubuntu14.04下安装qt4.8.6 +qt creator
  7. 华为-RH5885 V3 远程KVM
  8. WPF DataGrid 导出Excel
  9. Vue工程引入openlayers组件的方法
  10. Axis2 WS-Security 签名和加密
  11. c语言中judge的用法,judge的用法总结大全
  12. 什么是Promise?Promise有什么好处
  13. my torch voyage
  14. 基于LabVIEW的计时器
  15. 互联网注定被抛弃,未来30年属于硬科技!深层逻辑是这样的
  16. 电脑底部的任务栏跑左侧了怎么调 windows
  17. 嵌入式设备的switch 以及PHY 芯片调试和选型 (2)
  18. 文件在另一个程序中打开,无法删除~【删除文件被占用问题】(保姆级教程,五种解决办法~)
  19. 来来来!关于iOS基础面试咱俩好好唠唠
  20. gui学生信息管理系统java,Java实训·GUI学生信息管理系统

热门文章

  1. 用浏览器测试Get与Post Webservice,Post一直报405错误,而Get能够成功的原因与解决方法
  2. Object not found! The requested URL was not found on this server.... 报错解决方案
  3. 如何右对齐弹性项目?
  4. 如何将Unix时间戳转换为DateTime,反之亦然?
  5. JAVA入门到精通-网络编程补充和QQ案例
  6. Data Structure_Sort Algorithm
  7. ruby on rails 学习笔记
  8. 关于某些域环境下Windows Hello无法使用的解决方法
  9. OSSEC配置文件ossec.conf中添加mysql服务
  10. 英雄联盟(LOL)实战技巧(作者:白银段位)