1.jdk1.8环境
2.引入OpenJDK的jar包
org.openjdk.jol
jol-core
0.9
代码
public static void main(String[] args) {
Object o = new Object();
System.out.println("new Object:" + ClassLayout.parseInstance(o).toPrintable());
A a = new A();
System.out.println("new A:" + ClassLayout.parseInstance(a).toPrintable());
a.setFlag(true);
a.setI(1);
a.setStr("ABC");
System.out.println("赋值 A:" + ClassLayout.parseInstance(a).toPrintable());
}
static class A{
private boolean flag;
private int i;
private String str;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
概念
对象在内存中分为三个部分
1. 对象头(Header)
2. 实例数据(Instace Data)(可为空)
3. 对齐填充(Padding)(非必须)
分析
1. 新建Objec对象时,在内存里占用16个字节,其中Header占12个(markword占8个+classpointer占4个),没有实例数据,补充对齐4个。
2. 新建对象A时,中Header占12个(markword占8个+classpointer占4个),实例数据中 boolean占一个字节,会补齐三个,int占4个,String占8个,无需补充对齐。
结论
1.新建Object对象,会在内存占用16个字节,其中Header占12个(markword占8个+classpointer占4个),没有实例数据,补充对齐4个。
2.如果对象头+实例数据的字节数能被8整除,则不需要补充对齐。
附:
-XX:+UseCompressedClassPointers(64位虚拟机ClassPointer是8个字节(64位),默认此压缩类指针指令是开启的,占4个字节(32位))
-XX:+UseCompressedOops (默认压缩类普通对象指针是开启的,占4个字节(32位))
第一个字节中8bit
代码
public static void main(String[] args) {
Object lightObject = new Object();
try {
Thread.sleep(6000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object biasedLockObject = new Object();
System.out.println("---------------------------------------加锁前---------------------------------------");
System.out.println("偏向锁:" + ClassLayout.parseInstance(biasedLockObject).toPrintable() + "\n轻量级锁:" + ClassLayout.parseInstance(lightObject).toPrintable());
System.out.println("---------------------------------------加锁后---------------------------------------");
synchronized (biasedLockObject) {
System.out.println("偏向锁:" + ClassLayout.parseInstance(biasedLockObject).toPrintable());
}
synchronized (lightObject) {
System.out.println("轻量级锁:" + ClassLayout.parseInstance(lightObject).toPrintable());
}
System.out.println("---------------------------------------释放锁---------------------------------------");
System.out.println("偏向锁:" + ClassLayout.parseInstance(biasedLockObject).toPrintable() + "\n轻量级锁:" + ClassLayout.parseInstance(lightObject).toPrintable());
}
概念
1. 默认情况,偏向锁有时延,直接建立对象为无锁-》轻量级锁-》无锁
2. 增加延迟,偏向锁-》偏向锁-》偏向锁,同一个的对象每次运行时,加锁前对象头一样,加锁后和释放后是不一样的,因为线程把对象头里线程Id改为了自己线程的Id
3. 轻量级锁执行过程:在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储对象目前的MarkWord的拷贝(Displaced Mark Word),然后,虚拟机将使用CAS操作阐释将对象的Mark Word更新为指向Lock Record的指针,如果成功,该线程就拥有了该对象的锁,失败,则首先会检查对象的Mark Word是否已经指向当线程的栈帧,有就代表已拥有,直接进入同步,否则,被其他线程占用
4. 轻量级锁解锁过程:通过CAS过程将对相当前的MarkWord与线程中的Displaced MarkWord替换回来,如果替换成功,则同步成功,失败,则说明有其他线程尝试获取过锁,就要在释放锁的同时,唤醒等待线程
分析
1. JVM默认,偏向锁有时延,sleep一段时间后,就会开启偏向锁,也可以设定参数-XX:BiasedLockingStartupDelay=0
2. 上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程 偏向锁不可重偏向 批量偏向 批量撤销
3. 轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,偏向级锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。这个锁会偏向于第一个获得锁的线程,如果在接下来执行的过程中,该锁都没有被其它线程获取,则持有偏向锁的线程将永远不需要在进行同步。
4. 当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式,同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步模块时,虚拟机都可以不再进行任何同步操作(例如 Locking UnLocking以及对Mark Word的Update等)。当有另一个线程尝试获取这个锁的时候,偏向模式就宣告结束。
根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标记为“01”)或轻量级锁定(标记为“00”)的状态,后续的同步操作就如轻量级锁那样执行。
static Object object = null;
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("线程0");
Thread.sleep(6000);
object = new Object();
System.out.print("初始偏向锁->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
sysn("线程1");
Thread.sleep(1000);
System.out.print("线程1占用,未有其他线程尝试获取锁->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
sysn("线程2");
Thread.sleep(1000);
System.out.print("线程1占用,线程2尝试获取锁后->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
Thread.sleep(10000);
System.out.println("释放锁->" + "线程名称:" + Thread.currentThread().getName() + ClassLayout.parseInstance(object).toPrintable());
}
private static void sysn(String threadName) {
new Thread(() -> {
Thread.currentThread().setName(threadName);
synchronized (object) {
System.out.print(threadName + "占用中->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
分析
1. 初始化对象可偏向状态,线程1占用时,变为偏向锁,其他线程也尝试占用时,膨胀为重量级锁,没有线程占用之后,对象状态还是膨胀状态
static Object object = null;
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("线程0");
object = new Object();
System.out.print("无锁->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
sysn("线程1");
Thread.sleep(1000);
System.out.print("线程1占用,未有其他线程尝试获取锁->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
sysn("线程2");
Thread.sleep(1000);
System.out.print("线程1占用,线程2尝试获取锁后->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
Thread.sleep(10000);
System.out.println("释放锁->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
}
private static void sysn(String threadName) {
new Thread(() -> {
Thread.currentThread().setName(threadName);
synchronized (object) {
System.out.print(threadName + "占用中->" + "线程名称:" + Thread.currentThread().getName() + "\n" + ClassLayout.parseInstance(object).toPrintable());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
分析
1. 初始化对象为无锁状态,线程1占用时,变为轻量级锁,其他线程也尝试占用时,膨胀为重量级锁,没有线程占用之后,对象状态还是膨胀状态
自旋锁避免线程切换的开销,党要占用处理器的时间,锁被占用时间短,自旋等待好,占用时间长,白白消耗处理器资源,自选默认10次
代码上要求同步,但是被检测到不可能存在共享数据竞争 String.concatString()
如果虚拟机检测到一系列操作都是对同一个对象反复加锁和解锁,将会把加锁的范围扩展到这个操作序列外部例如多个append()