栈上分配
1. 出现原因
Java堆中内存是线程共享的,假设所有对象都从堆中分配的话,所有回收对象的筛选、整理、清除都需要耗费大量的资源,十分不合理,那么对象分配在栈帧中,随栈而生,那么GC回收的所耗费的资源就可以省略,大大提高JVM的效率。
2. 技术支持
逃逸分析:在编程语言的编译优化原理中,分析指针动态范围的方法称之为逃逸分析。它跟静态代码分析技术中的指针分析和外形分析类似。通俗一点讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。
方法逃逸:例如调用参数传递到其他方法中。
线程逃逸:当前线程对象被其他线程访问。
总结:假设对象没有逃逸,那么允许将对象打散分配在栈上。JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。
TLAB分配
TLAB全称是Thread Local Allocation Buffer,即线程本地分配缓存区。由于对象一般分配在堆内存中,堆是线程共享的,每次对象分配都会进行线程同步,在多线程情况下同步操作是会让分配效率大大降低。JVM使用TLAB来避免线程间的冲突,从而提高分配效率。
TLAB本身占用Eden空间,在开启TLAB的情况下,JVM会为每条线程分配一块TLAB的空间。参数-XX:+TLAB开启TLAB,默认开启。TLAB内存占用非常小,默认是Eden的1%,也可以使用-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden的百分比大小。
由于TLAB空间一般不会很大,因此大对象无法在TLAB中进行分配,总是会直接分配在堆内存中。TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。
-XX:+PrintTLAB可以跟踪TLAB的使用情况。一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为。
内存分配
为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,有两种方法。
指针碰撞
内存的使用与未使用由指针作为分界线,指导指针与内存最大值地址重合则内存分配完成。针对规整的内存分配,适用于复制算法和标记整理算法的GC收集器,例如Serial、ParNew、Parallel、G1。
空闲列表
JVM维护一个列表,记录内存的使用情况,根据对象的大小分配内存,并更新列表。针对不规整的内存分配,适用于标记清除算法的GC收集器,例如CMS。
网友评论