# java_obj_layout **Repository Path**: leo_dai/java_obj_layout ## Basic Information - **Project Name**: java_obj_layout - **Description**: Java对象内存布局学习 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-04-01 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # java_obj_layout #### 介绍 Java对象内存布局学习 配合食用 https://www.bilibili.com/video/BV1Tt411J77y/?p=3 #### 认识对象的内存结构 * 对象头 * Mark Word 4byte=32bit/8byte=64位 * Class Metadata Adress 4byte=32bit/8byte=64位 * 如果是数组对象 还包含数组长度 * 对象实例数据 即创建对象时,为对象的成员变量分配的空间 * 对齐数据 对象的大小必须是8字节的整数倍 jdk 1.4 nio jdk 1.6 锁概念 os linux mutex =>jdk1.6 * 单个线程同步代码---偏向锁 (110) * 多个线程执行 * 交替执行 无竞争 轻量级锁(000) * 交替执行 有竞争 重量级锁(010) #### 通过jol打印java对象布局 ##### code: ```java class Person{ private String name; private int age; private Integer no; private boolean adult; } public static void main(String[] args) { //启用指针压缩:-XX:+UseCompressedOops,禁止指针压缩:-XX:-UseCompressedOops final Person person = new Person(); System.out.println("hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); } ``` ##### 结果 ![](pic/22.png) ##### 分析 >0-12 前12个字节是对象头 >12-28 实例数据 >28-32 对其数据(操作系统要求分配的空间必须是8的整数倍) ##### Q1 上面说对象头包含markword 和class metadata address,合起来应该是128位,16个字节,为什么上面只有12个字节? >默认开启了指针压缩 原生对象头大小为16字节,压缩后为12字节 > >通过 -XX:-UseCompressedOops 关闭指针压缩 结果如下图 ![](pic/33.png) ##### Q2 hashcode(7ef20235)为什么和对象头 (01 35 02 f2 7e 00 00 00 49 c8 00 08) 对不上? >小端存储(高字节在高地址) 应该按照 08 00 c8 49 00 00 00 7e f2 02 35 01 读 > >其中markword 00 00 00 7e f2 02 35 01(当前状态为无锁状态) > >00000000(**00**) 00000000(**00**) 00000000(**00**) 01111110(**7e**) > >11110010(**f2**) 00000010(**02**) 00110101(**35**) 00000001(**01**) > >25位:unused 31:hash 1:unused 4:age b iased_lock:1 lock:2 ##### Q3 如何查看锁的变化 * 无锁->偏向锁(同一个线程) * ```java JVM ``` * 运行结果 * 无锁 -> 轻量级锁 * ```java public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":无锁 --------------------start"); final Person person = new Person(); System.out.println(Thread.currentThread().getName()+":hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); System.out.println(Thread.currentThread().getName()+":无锁 --------------------end"); new Thread(new Runnable() { public void run() { synchronized (person){ System.out.println(Thread.currentThread().getName()+": 轻量级锁 --------------------start"); System.out.println(Thread.currentThread().getName()+": +hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); System.out.println(Thread.currentThread().getName()+":轻量级锁 --------------------start"); } } }).start(); } ``` * 运行结果 ![](pic/lightlock.png) 锁状态 00000**001** -> 11011**000** * 无锁-> 重量级锁 * ``` public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()+":无锁 --------------------start"); final Person person = new Person(); System.out.println(Thread.currentThread().getName()+":hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); System.out.println(Thread.currentThread().getName()+":无锁 --------------------end"); new Thread(new Runnable() { public void run() { synchronized (person){ System.out.println(Thread.currentThread().getName()+": 重量级 --------------------start"); System.out.println(Thread.currentThread().getName()+": +hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); System.out.println(Thread.currentThread().getName()+":重量级 --------------------start"); } } }).start(); new Thread(new Runnable() { public void run() { synchronized (person){ System.out.println(Thread.currentThread().getName()+": 重量级1 --------------------start"); System.out.println(Thread.currentThread().getName()+": +hashCode(16进制):"+Integer.toHexString(person.hashCode())); System.out.println(ClassLayout.parseInstance(person).toPrintable()); System.out.println(Thread.currentThread().getName()+":重量级1 --------------------start"); } } }).start(); } ``` * 运行结果 ![](pic/heavylock.png) 锁状态 00000**001** -> 11011**010** ---- #### 指针压缩 >什么是java对象的指针压缩? >1.jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩 >2.jvm配置参数:UseCompressedOops,compressed--压缩、oop--对象指针 >3.启用指针压缩:-XX:+UseCompressedOops,禁止指针压缩:-XX:-UseCompressedOops > >为什么要进行指针压缩? >1.在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力 >2.为了减少64位平台下内存的消耗,启用指针压缩功能 >3.在jvm中,32位地址表示4G个对象的指针,在4G-32G堆内存范围内,可以通过编码、解码方式进行优化,使得jvm可以支持更大的内存配置 >4.堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间 >5.堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好 > >指针压缩的原理是什么? >1.解释器解释字节码,植入压缩指令,进行编码、解码 >2.需要操作系统底层支持:GC堆从虚拟地址0开始分配 > >哪些信息会被压缩? >1.对象的全局静态变量(即类属性) >2.对象头信息:64位平台下,原生对象头大小为16字节,压缩后为12字节 >3.对象的引用类型:64位平台下,引用类型本身大小为8字节,压缩后为4字节 >4.对象数组类型:64位平台下,数组类型本身大小为24字节,压缩后16字节 > >哪些信息不会被压缩? >1.指向非Heap的对象指针 >2.局部变量、传参、返回值、NULL指针 #### 为什么设计synchronized关键字 >在多线程编程中,有可能出现多个线程同时访问同一个共享,可变资源的情况;这种资源可能是:对象 变量 文件等 > >* 共享 资源可以被多个线程同时访问 >* 可变 资源在其生命周期内可以被改变 > >存在问题: > >由于线程执行的过程是不可控的,所有需要采用同步机制来协同对对象可变状态的访问 > >线程安全 > >* 有序性 如果两个线程不能从 **happens-before原则** 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则) >* 原子性 提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock) >* 可见性 一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile) #### Synchronized 原理 * 同步实例方法 锁为当前实例对象 * 同步类方法 锁当前类对象 * 同步代码块 锁的是括号内的对象 > JVM内置锁通过synchronized实现,通过对象的monitor(监视器锁)实现,基于进入和退出monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex Lock(互斥锁)实现,是一个重量级锁 性能低(1.6之前 之后有优化) ![](pic/Snipaste_2020-04-13_11-33-02.png) ![](pic/Snipaste_2020-04-13_14-19-30.png) #### 认识对象的内存结构 * 对象头 比如hash码 对象所属的年代 对象锁 锁状态标志 偏向锁(线程ID) 偏向时间 数组长度(数组对象)等 * 对象实例数据 即创建对象时,为对象的成员变量分配的空间 * 对象填充 对象的大小必须是8字节的整数倍 #### 认识对象头 ![](pic/Snipaste_2020-04-13_14-19-30.png) #### 锁的升级过程(过程不可逆) ![](pic/Snipaste_2020-04-13_14-21-35.png)