JVM对象创建与内存分配机制深度剖析

  作者:图灵javaer


在线预览


划分内存的方法:


  • “指针碰撞”(Bump the Pointer)(默认用指针碰撞)




  • “空闲列表”(Free List)




解决并发问题的方法:


  • CAS(compare and swap)


  • 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)

每个线程在Java堆中预先分配一小块内存。通过­XX:+/­UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启­XX:+UseTLAB),­XX:TLABSize 指定TLAB大小,默认大小为伊甸园区的1%


设置对象头


对象在内存中存储的布局可以分为3块区域:

对象头(Header)

实例数据(Instance Data)

对齐填充(Padding)







两者class区别:




逃逸分析分配对象:



  • test1方法的user可被外部引用,不能分配到栈上,不然运行完test1方法就会被回收。
  • test2方法的user不可被外部引用,在对象大小未超出栈大小的情况下,可以分配到栈上,因为随着方法执行的结束,则对象会被销毁。


JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)


大对象直接进入老年代


大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 (单位是字节)  -XX:+UseSerialGC  ,再执行下上面的第一个程序会发现大对象直接进了老年代


长期存活的对象将进入老年代


既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。


对象在Eden区分配


Eden与Survivor区默认8:1:1

JVM默认有这个参数-XX:+UseAdaptiveSizePolicy(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变化可以设置参数-XX:-UseAdaptiveSizePolicy


对象动态年龄判断


年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代

50%(-XX:TargetSurvivorRatio可以指定)


老年代空间分配担保机制


年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM"当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”


流程图:







相关推荐

评论 抢沙发

表情

分类选择