文章目录
- 1. Java对象结构
- 2. 代码实践
- 3. Mark Word的结构信息
- 4. 大小端问题
- 5. 四种内置锁的概念
1. Java对象结构
先上图:
- 对象头
Mark Word: 标记字,存储自身运行时的数据,例如GC标志位、哈希码、锁状态等信息
Class Pointer: 类对象指针,存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例
Array Length: 数组长度,如果对象是一个Java数组,那么此字段必须有,用于记录数组长度的数据,不是数组对象可以没有,可选字段 - 对象体
对象体包含对象的实例变量(成员变量),用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐(4字节的整数倍) - 对齐字节
对齐字节也叫作填充对齐,其作用是用来保证Java对象所占内存字节数为8的倍数;对象头是8的倍数,当对象体实例变量数据不是8的倍数时,就需要进行字节填充使得整体是8的倍数
相关部分的作用:
- Mark Word(标记字)字段主要用来表示对象的线程锁状态,另外还可以用来配合GC存放该对象的hashCode
- Class Pointer(类对象指针)字段是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例
- 对象体用于保存对象属性值,是对象的主体部分,占用的内存空间大小取决于对象的属性数量和类型
- 对齐字节并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用
在32位JVM虚拟机中,Mark Word和Class Pointer这两部分都是32位的;在64位JVM虚拟机中,Mark Word和Class Pointer这两部分都是64位的
2. 代码实践
前提知识:
- Mark Word长度:32位的JVM中是32位,64位的JVM中是64位
- Class Pointer:32位的JVM中是32位,64位的JVM中是64位(开启指针压缩后是32位)
- Array Length:32位
如何开启指针压缩:
-XX:+UseCompressedOops
: 开启-XX:-UseCompressedOops
: 关闭
导入依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
1. 没有属性的对象
package innerlock;
import org.openjdk.jol.info.ClassLayout;
public class InnerLockTest {
public static void main(String[] args) {
InnerLockTest objInnerLockTest=new InnerLockTest();
System.out.println(ClassLayout.parseInstance(objInnerLockTest).toPrintable());
}
}
关闭指针压缩
开启指针压缩
2. 带有基本类型属性的对象
package innerlock;
import org.openjdk.jol.info.ClassLayout;
public class InnerLockTest {
//8种基本类型
byte a=1;
short b=2;
int c=10;
long d=100;
float f=1.0f;
double g=2.0;
char h='#';
boolean i=false;
public static void main(String[] args) {
InnerLockTest objInnerLockTest=new InnerLockTest();
System.out.println(ClassLayout.parseInstance(objInnerLockTest).toPrintable());
}
}
关闭指针压缩
开启指针压缩
3. 带有引用类型的对象
package innerlock;
import org.openjdk.jol.info.ClassLayout;
public class InnerLockTest {
String str1="hello";
String str2="world";
public static void main(String[] args) {
InnerLockTest objInnerLockTest=new InnerLockTest();
System.out.println(ClassLayout.parseInstance(objInnerLockTest).toPrintable());
}
}
关闭指针压缩
开启指针压缩
3. 数组类型对象
package innerlock;
import org.openjdk.jol.info.ClassLayout;
public class InnerLockTest {
int a=1;
public static void main(String[] args) {
InnerLockTest []objInnerLockTest=new InnerLockTest[5];
System.out.println(ClassLayout.parseInstance(objInnerLockTest).toPrintable());
}
}
关闭指针压缩
开启指针压缩
对象数组中即使对象里面有实例属性,但是数组中依旧保存内存指针
所以对于数组对象而言,开启指针压缩的情况下(Mark Word: 8 Class Pointer:4 Array Length: 4),此时对象头刚好是8的倍数,不需要填充;不开启指针压缩的情况下(Mark Word: 8 Class Pointer:8 Array Length: 4),此时对象头需要填充, 整体是否需要填充取决于数组的大小,因为对象头已经是8的倍数,数组的每个元素是引用类型的4字节,偶数长度则不需要填充,奇数长度则需要填充。
如果是基本类型的数组,那么数组元素的大小取决于基本类型的大小,对象头的填充方式和对象数组一样
3. Mark Word的结构信息
Java内置锁的4种类型: 无锁,偏向锁,轻量级锁,重量级锁
4. 大小端问题
- 大端模式是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
- 小端模式是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
example:将十六进制数0X1234abcd写入以0x0000开始的内存地址中
5. 四种内置锁的概念
1. 无锁状态
Java对象刚创建时还没有任何线程来竞争,说明该对象处于无锁状态(无线程竞争它),这时偏向锁标识位是0,锁状态是01
2. 偏向锁状态
偏向锁是指一段同步代码一直被同一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。内置锁偏爱某个线程。 Mark Word会记录内置锁自己偏爱的线程ID,内置锁会将该线程当作自己的熟人
3. 轻量级锁状态
当有两个线程开始竞争这个锁对象时,情况就发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争。
当锁处于偏向锁,又被另一个线程企图抢占时,偏向锁就会升级为轻量级
当A线程持有资源时,B线程过来竞争,发现A已经占有了,B不会立即阻塞挂起,而是进入一种“自旋”状态,等A释放后就立即获取锁。线程自旋是消耗CPU的,因此可以设置1个自旋时间上限,当自旋一定时间后还没有等到别的线程释放资源,就进入阻塞挂起状态,锁就会变成重量级锁。
4. 重量级锁
重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也叫同步锁(synchronized就是重量级锁)