对象在内存中的内存布局是什么样的?
一个对象在new出来之后在内存中主要分为4部分:
-
markword:hashcode、GC分代年龄、锁状态标志,线程持有的锁,偏向线程
-
class pointer:指向对象的class文件指针
-
instance data:记录 了对象的变量数据
-
padding:对齐,对象在64位服务器版本中,规定对象内存必须要能被8字节整除,如果不能整除,那么就靠对齐来补。
描述synchronized和ReentrantLock的底层实现和重入的底层原理。
synchronized:
每个对象有一个监视器(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权:
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
- 如果线程已经占有monitor,只是重新进入,则monitor的进入数+1。
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor的所有权。
谈谈AQS,为什么AQS底层是CAS+volatile?
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
描述下锁的四种状态和锁升级过程?
- 无锁 01 :指没有对资源进行锁定,所有线程都能访问并且修改同一个资源,但同时只有一个线程能修改成功。
- 偏向锁 01 :偏向锁是指当一段同步代码一直被同一个线程访问时,不存在多个线程的竞争,那么该线程在后续访问时候会自动获取锁,从而降低获取锁带来的消耗。
- 轻量级锁 00 :轻量级锁的主要是指当前锁为偏向锁时,被另外的线程访问,这时候偏向锁回升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
- 重量级 10 :重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。由操作系统来负责线程之间的调度。
锁升级过程
1.当对象没有被锁时,就是一个普通对象,锁标志为01,是否偏向为0;
2.当对象被当作同步锁并且有一个线程A抢到了锁,锁标志为01,是否偏向为1,此时进入偏向锁状态;
3.当线程B试图获取这个锁时候,发现同步锁处于偏向状态但线程id记录的不是B,这时B会使用CAS操作试图获得锁,如果获取成功,偏向线程id设置为B。如果获取失败代表有竞争,当前偏向锁升级为轻量级锁。锁标志位00。轻量级锁获取锁失败,会使用自旋获取锁。
4.当自旋超过一定次数(自旋10次),或者线程较多(超过CPU核心的一半),这时候轻量级锁会升级至重量级锁,锁标志位10。在这个状态下,未抢到锁的线程会被阻塞。
Object o = new Object() 在内存中占用多少字节?
- 类指针压缩开启时:MarkWord8字节,类指针4字节,数组长度4字节,不需要补齐,一共16个字节。
- 类指针压缩关闭时:MarkWord8字节,类指针8字节,数组长度4字节,需要补齐4个字节,一共24个字节。
自旋锁是不是一定比重量级锁效率高?
如果线程数太多,比如上来就是10000个,那么这里CAS要转多久才可能交换值,同时CPU光在这10000个活着的线程中来回切换中就耗费了巨大的资源,这种情况下自然就升级为重量级锁,直接叫给操作系统入队管理,那么就算10000个线程那也是处理休眠的情况等待排队唤醒。
打开偏向锁是否效率一定会提升?
不一定,在明确知道有线程竞争的情况下,因为偏向锁是存在竞争的,启动偏向锁不是一个合适的选择。
上了偏向锁之后多个线程争抢共享资源的时候要进行锁升级到轻量级锁,这个过程还的把偏向锁进行撤销在进行升级,所以导致效率会降低。
重量级锁到底重在哪里?
JVM把任何和线程有关的操作全部交给操作系统去做,例如调度锁的同步直接交给操作系统去执行,而在操作系统中要执行先要入队,另外操作系统启动一个线程时需要消耗很多资源,消耗资源比较重。