在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下图所示:
对象头
对象头又包括两部分信息,第一部分用于存储对象自身的运行时数据(Mark Word),如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另外一部分是类型指针(Klass pointer),即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
值得注意的是:类元信息存在于方法区,类元信息有区别与堆中的synchClass字节码对象,synchClass可以理解为类加载完成后,JVM将类的信息存在堆中,然后使用反射去访问其全部信息(包括函数和字段),然而在JVM内部大多数对象都是使用C++代码实现的,对于JVM内部如果需要类信息,JVM就会通过对象头的类型指针去拿方法区中类元信息的数据。
实例数据:存放类的属性数据信息,包括父类的属性信息。
对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
synchronized用的锁是存在Java对象头里的。如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit。如下图所示:
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下图所示:
在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据,如下图所示:
在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下图所示:
现在虚拟机基本是64位的,而64位的对象头有点浪费空间,JVM默认会开启指针压缩,所以基本上也是按32位的形式记录对象头的。也可以通过下面参数进行控制JVM开启和关闭指针压缩:
为什么JVM需要默认开启指针压缩呢?
原因在于在对象头上类元信息指针Klass pointer在32位JVM虚拟机中用4个字节存储,但是到了64位JVM虚拟机中Klass pointer用的就是8个字节来存储,一些对象在32位虚拟机用的也是4字节来存储,到了64位机器用的都是8字节来存储了,一个工程项目中有成千上万的对象,倘若每个对象都用8字节来存放的话,那这些对象无形中就会增加很多空间,导致堆的压力就会很大,堆很容易就会满了,然后就会更容易的触发GC,那指针压缩的最主要的作用就是压缩每个对象内存地址的大小,那么同样堆内存大小就可以放更多的对象。
为什么JVM中的对象由年轻代进入老年代的默认分代年龄是15?
对象头中有4个字节用于存放对象分代年龄的,4个字节就是2的四次方等于16,其范围就是0~15,所以也就很好理解对象在GC的时候,JVM对象由年轻代进入老年代的默认分代年龄是15了。
synchronized 通过当前线程持有对象锁,从而拥有访问权限,而其他没有持有当前对象锁的线程无法拥有访问权限,保证在同一时刻,只有一个线程可以执行某个方法或者某个代码块,从而保证线程安全。synchronized 可以保证线程的可见性
,synchronized 属于隐式锁
,锁的持有与释放都是隐式的,我们无需干预。synchronized最主要的三种应用方式:
修饰实例(类)方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁。如果多个线程不同对象访问该方法,则无法保证同步。
修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。锁的是包含这个方法的类,也就是类对象,这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
在应用Sychronized关键字时需要把握如下注意点:
方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)。
手动指定锁定对象,也可是是this,也可以是自定义的锁
public class SynchronizedObjectLock implements Runnable{
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
public class SynchronizedObjectLock implements Runnable{
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
synchronized (block1) {
System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
}
synchronized (block2) {
System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
结果:
synchronized修饰普通方法,锁对象默认为this
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
实例代码:
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在普通方法上,默认的锁就是this,当前实例
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
// t1和t2对应的this是两个不同的实例,所以代码不会串行
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
// t1和t2对应的this是两个不同的实例,所以代码不会串行
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
实例代码:
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}