今天聊聊Java的内存分配和内存回收,内存的回收也就是前几节讲的垃圾回收,有兴趣的可以看下前面的文章,今天主要看下内存的分配。
先来回忆下Java运行时的数据区,如下图所示。Java对象所占的内存是在下面哪个区上分配的?堆,恭喜你答对了^_^。
回忆点1:堆的内存布局
我们在讲垃圾收集算法的时候,有提到过分代收集算法(JVM系列(4)-垃圾收集算法)。也就是按对象的生存时间将其划分为新生代和老年代,不同的代,采用不同的垃圾收集算法。
新生代采用的是复制算法,老年代采用的是标记-清除算法或者标记整理。
在复制算法中,我们又讲到,按1:1的比例划分区间太浪费,因为利用率只有50%。有机构研究表明,我们可以按8:1的比例划分区间,Eden占8,两个Survivor各自占1。新生代的总可用空间为Eden(8) + Survivor(1) = 9。
综上,整个堆的内存布局如下所示。
回忆点2:-Xms/-Xmx/-Xmn参数意思
-Xms:表示堆空间的最小大小
-Xmx:表示对空间的最大大小
-Xmn:表示新生代的大小
因为堆划分为新生代和老年代,所以老年代的大小 =(-Xmx) - (-Xmn);
例如:
-Xms20M表示堆最小为20M
-Xmx60M表示堆最大为60M
-Xmn15M表示新生代为15M
老年代最小为20M - 15M = 5M
老年代最大为60M - 15M = 45M
知识点1:什么是Minor GC和Full GC?
新生代GC就是Minor GC,也就是发生在新生代的垃圾收集动作;
特点是回收速度块。
老年代GC就是Full GC也称为Major GC,也就是发生在老年代的垃圾收集动作;
特点是回收速度慢,比Minor GC慢10倍以上。
知识点2:对象优先在Eden分配
Eden就是下面用红框标出的区域,当Eden没有足够的空间进行分配时,虚拟机会发起一次Minor GC。
先来看个例子,设置VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M
由设置的参数值,我们得到如下数据:
堆空间大小为20M
新生代大小为10M(Eden = 8M, Survivor = 1M, Survivor = 1M)
老年代大小为20M -10M = 10M
对象优先在Eden上分配,在分配完c时,Eden已经占用了6M空间,还剩下2M空间,但是接下来d变量的大小是4M,剩余的2M不够分配了。所以此时呢,就会出发一次Minor GC。再延伸的讲点,因为GC时,a,b,c对象还都是存活的,按照复制算法的定义,a,b,c要被复制到Survivor区,但Survivor区大小只有1M,装不下。那怎么办呢?采用分配担保机制,将对象复制到老年代,老年代有10M大小,够用。
知识点3:大对象直接进入老年代
为什么要将大对象直接放入老年代?
我们知道GC是耗时的操作,新生代的GC频率又很频繁,如果把大对象分配到Eden区,就可能导致在还有很多空闲空间的时候,就得触发GC以大扫除足够的空间来安置它们。
用什么办法可以直接将大对象在老年代中分配呢?
可以通过-XX:PretenureSizeThreshold参数进行设置,大于设置值的对象,就会直接在老年代中分配。
注意:-XX:PretenureSizeThreshol只对Serial和ParNew两款收集器有效。
知识点4:长期存活的对象将进入老年代
我们知道堆空间分为年轻代和老年代,不同的代区采用的收集算法不同,如何能将不同的对象合理的放入不同的代区,以达到最好的回收效果,是我们追求的目标。
虚拟机中有个参数-XX:MaxTenuringThreshold表示在对象熬过多少次Minor GC后,就将其放入老年代。
这里面有个知识点是动态年龄判断,也就是并不是必须达到某个年龄之后,才进入老年代。如果在Survivor区中相同年龄的所有对象大小的总和大于Survivor区空间的一半,则年龄大于或者等于该年龄的对象可以直接进入老年代,而不需要等到-XX:MaxTenuringThreshold中要求的年龄。
知识点5:空间分配担保
我们知道年轻代采用的是复制算法,而复制算法,为了提供内存利用率,并没有按照1:1的比例分配内存,而是按照8:1或者其它值来设置。这样就可能遇到以下情况,Minor GC后,存活的对象还有很多,大小大于Survivor的空间,Survivor空间容纳不了,那怎么办?那就采用空间分配担保,将对象复制到老年代中,如果老年代剩余的空间也容纳不了,那就进行一次Full GC。
今天先到这儿,明天继续^_^。