首先谈java对象的创建 |
我们在语言层面上,创建一个对象仅仅是一个new关键字可以解决的。
但是在JVM层面上是怎么创建一个对象的呢??
当 jvm 遇到 new 指令
1,检查指令的参数能否在常量池中定位到一个类的符号引用。并检查这个符号引用所代表的类是否已经被加载,解析,初始化过。
2,没有,就进行相应的类加载。
3,类加载完成后就知道对象所需内存大小,为对象进行内存空间分配,在将指针移动内存大小的位置(指针碰撞的分配方式)。
为什么类加载完成之后就知道对象所需要内存大小?
类加载完成之后,加载类的继承体系关系得以明确,父级继承链中所有非静态域成员的 FieldsLayout 和 size 就已知了,这个size就是当前加载类的 base 值;对于当前加载类,根据域成员的定义顺序,依次迭代成员域数组_fields,根据数组中的每一个域成员的类型,在 base 值的基础上累加该类型的偏移量……这样就能计算出来所有非静态成员的 size 总和;考虑到 VM 所属平台是否是64bit,或是32bit,指针大小是8字节还是4字节,在上述迭代过程中会对被迭代的每一个non_static_field做内存对齐处理(加上内存对齐导致的padded_offset)——以上过程称之为layout_fields;最后考虑到对象头的存在,再加上对象头的header_size(),就可以决定使用 new 指令创建对象时所需要 allocate 的堆内存大小了
什么是 “指针碰撞” 的分配方式?
假设 java 堆中内存都是绝对规整的,被使用的内存放在一边,空闲的内存放在一边,有一个指针指向交界处。根据类加载知道的对象 size 信息,JVM给这个要被 new 的对象分配 size 的内存,然后指向交界区的指针向前移动 size 大小的距离,这个过程就叫指针碰撞。
问题来了
指针碰撞的方式给创建的对象分配内存,看似只修改了一个指针指向的位置,就好像很安全的样子!其实在并发情况下并不是线程安全的。比如:
JVM一个线程正在给A对象分配内存,指针还没有来的及修改,其它为B对象分配内存的线程,还引用这之前的指针指向。这样就出问题了。
解决方法
1)对分配空间的动作进行同步处理——就是保证分配内存和修改指针两个动作原子性。
2)就是我们TLAB空间了(终于让本篇主角出场了)
TLAB自述 |
Tlab(ThreadLocalAllocBuffer)是一个本地线程分配缓冲区,是线程的一块私有内存,如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer,如果需要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer容量不够的时候,再重新从Eden区域申请一块继续使用,这个申请动作还是需要原子操作的。
TLAB的目的是在为新对象分配内存空间时,让每个Java应用线程能在使用自己专属的分配指针来分配空间,均摊对GC堆(eden区)里共享的分配指针做更新而带来的同步开销。
TLAB的本质其实是三个指针管理的区域:start,top 和 end,每个线程都会从Eden分配一块空间,例如说100KB,作为自己的TLAB,其中 start 和 end 是占位用的,标识出 eden 里被这个 TLAB 所管理的区域,卡住eden里的一块空间不让其它线程来这里分配。
而 top 就是里面的分配指针,一开始指向跟 start 同样的位置,然后逐渐分配,直到再要分配下一个对象就会撞上 end 的时候就会触发一次 TLAB refill(重新申请一个TLAB)
想了解更加底层可以参考:http://www.kejixun.com/article/170523/330012.shtml
TLAB的难题 |
1,TLAB空间大小是固定的,但是这时候一个大对象,我TLAB剩余的空间已经容不下它了。
2,TLAB空间还剩一点点没有用到,有点舍不得。
以上这种问题还是很低级的啊,JVM开发人员对这种很容易发生的问题,还是做了处理的。TLAB有一个参数 _refill_waste_limit——最大浪费空间。
当剩余的空间小于最大浪费空间,那该TLAB属于的线程在重新向Eden区申请一个TLAB空间。进行对象创建,还是空间不够,那你这个对象太大了,去Eden区直接创建吧!
当剩余的空间大于最大浪费空间,那这个大对象请你直接去Eden区创建,我TLAB放不下没有使用完的空间。
上面的问题这样解决了,但是新问题也来了啊!
3,Eden空间够的时候,你再次申请TLAB没问题,我不够了,Heap的Eden区要开始GC,然后吧啦吧啦GC涉及经典问题又带来了一路。
4,TLAB允许浪费空间,导致Eden区空间不连续,积少成多。以后还要人帮忙打理。
TLAB内心苦啊,本来为了线程安全而生,却还是有这么多问题缠身
这个TLAB事情也告诉了我们,学习一个知识,是为了提高自己。但是学习之路并不是平的,会带来很多的问题,所以要丰富自己,像JVM不停的版本迭代,吸纳新知识,就能好的解决知识的漏洞和欠缺点。
下一步该回顾我了解的GC了。