1. JVM G1 对象分配原理
1.1. G1中是怎么分配一个对象的?
系统程序运行时,创建新的对象,会先找新生代的eden区来存储。在G1里面,就是从eden类型的region里面选择一个region来做对象的分配。
如果有两个线程,同时找一个region来分配对象,并且两个线程也同时找到这个region里面的同段内存,这个时候就出现了并发安全问题。
1.2. 如何解决对象创建过程的冲突问题?
常见的思路:加锁!线程1在分配对象的时候,直接对堆内存加个锁!分配完成之后,再由线程2进行对象的分配!此时一定不会出现并发的问题。
为什么要对堆内存进行加锁?因为对象分配的过程是非常复杂的,不仅仅是分配一个对象,还要做引用替换、引用关系处理、region的一些元数据的维护、对象头的一些处理等等。如果只是锁一个region,或者只是锁一段内存是不够的,所以只能锁整个堆内存。
如果按照上述思路,分配效率会特别低!系统运行创建对象会很频繁,没办法满足业务系统正常的运行。
1.3. 无锁化分配 —— G1的快速分配原理TLAB撞针分配
如果要解决并发安全问题,一般有几种思路:
- 使用锁
- 使用CAS自旋模式(和锁的思想类似)
- 使用自己本地的内存,自己改自己的
G1采用本地缓冲区的思想来解决冲突问题。TLAB全称,Thread Local Allocation Buffer。就是每个线程都有一个本地分配缓冲区,专门用于对象的快速分配。改变引用关系等操作,使用另外一套异步机制。
这个缓冲区,保证线程创建对象的时候,尽可能使用TLAB来快速分配对象,实现了无锁化的分配。
1.4. 仅在分配TLAB的时候对堆内存加锁
仅在分配TLAB的时候对堆内存加锁,这样大大减少了锁冲突导致串行化执行的问题。
分配TLAB,可能会跟线程数量是一致。一般并发执行可能几十次,最多上百次,这样对线程来加锁,效率会高很多。
如图所示,只有在线程需要分配TLAB的时候才会对堆内存加一个全局锁。如果不需要分配TLAB就直接快速分配一个对象,大大提升了效率。