Java虚拟机运行时数据区一般分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。
如果对Java堆进行细分地话,又可以分为新生代(包含Eden空间、From Survivor空间、ToSurvivor空间)和老年代。
由来
不喜欢看历史的可以先看后面内容。之所以将Java堆又细分为新生代和老年代的原因不是因为《Java虚拟机规范》对这一块数据区域有进一步地划分。
而是Sun/Oracle JDK和Open JDK中默认使用的HotSpot虚拟机在G1垃圾收集器出现以前,所有使用的垃圾收集器全部都是基于“分代收集理论”来设计的。
也就是说把Java堆固定划分为两个区域,针对各自区域中存储数据的特点,一种垃圾收集器工作在新生代,另一种垃圾收集器工作在老年代,二者配合共同完成垃圾收集工作。所以对Java堆的进一步细分完全是为了方便进行垃圾收集。
但是到了今天,再这么说就值得商量了。用《深入理解Java虚拟机:JVM高级特性与最佳实践(第三版)》作者的原话讲就是:
但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采用分代设计的新垃圾收集器,在按照上面的提法就有很多需要商榷的地方了。
我理解的意思就是说,面试官想跟你聊新生代、老年代,你就跟他聊,反正咱也不是不懂,不聊就算了,但是感觉多半会聊啊,还是掌握的好。。。
三种空间的领地
既然把新生代又划分为Eden空间、From Survivor空间、ToSurvivor空间,那么肯定是有比例的,谁占多大的地,以免发生领地纠纷。
在HotSpot虚拟机中默认Eden和From Survivor、ToSurvivor的大小比例是8:1:1。所以上图为了展示清楚,画的不是很准确。
分配
我们把整个Java堆比喻成一个大房子,虚拟机比喻成房子的主人小V,来看看内存分配与垃圾回收是怎么一回事。
而且这些空间的名字太长,我们约定Eden空间——伊甸空间,From Survivor空间——from空间,To Survivor空间——to空间。
1.Eden空间+From Survivor空间
小V今天心情非常好买了好多东西(new了很多对象),回到家后他把东西都放在了这个房间,他对房间的使用就是这么安排的,“你管我!哼!”。
即:每次分配内存只使用伊甸和from空间。
2.ToSurvivor空间
有一天伊甸+from房间装满了。虽然小V住的是豪宅,但也经不住他是个土豪,买的东西太多了,房间再怎么大,总有装满的一天。但是他又想买东西了,这个时候就只能把一些对他不重要的东西丢掉(GC),好腾出空间放新东西。
小V虽然是土豪但是一点都不蠢,他收拾伊甸+from房间的方法是这样的。把一些对他还有用的东西放到to这个房间存起来,之后把伊甸+from房间的东西全部丢掉,心里想着“这都是哪一年的衣服了!咱可是时尚圈的弄潮儿”,注意这句话其实是暗示哦,暗示这个对象实例已经达到了可回收的标准(也就是可达性分析算法)。
即:发生垃圾收集时,将伊甸和from中仍然存活的对象一次性复制到to空间上,然后直接清理掉伊甸和from空间。
3.老年代
(1)床大放不下
收拾房间可是一件脏累活而且不总是那么顺利,这不,小V就遇到了麻烦。他发现伊甸+from这个房间有一张他买的双人大床,他想着“这个床还是有用的哈,虽然不一定马上用得着,但女盆朋来了,折腾起来好像更方便(伏笔)”。
那就放到to空间嘛,但是好像放不下,毕竟两者之间比例是9:1,放不下也正常。
那怎么办呢?房间虽小,豪宅可不小,那就放到别的房间,搬到老年代房间去。(当然为了防止这个床大到连老年代都放不下,会在虚拟机会在对新生代GC之前进行判断的,这里不详说,详情可参考)
刚才的伏笔决对是正经的,暗示这个对象实例还有用哈,并没有达到回收标准。
即:当to空间不足以容纳一次GC之后存活的对象时,就需要依赖老年代进行分配担保。
(2)大象放哪里?
这一次小V买了一头大象,这真是实名副其实的大对象。这头大象对小V来说决对是一个让他头疼的事,主要有下面两个理由。
一、即便伊甸+from房间还有很多空间,但是这头大象一来,他很大可能就要开始收拾房间了;二、后期还有可能要把这头大象搬到to房间或老年代去,简直欲哭无泪啊!!!
那么干脆直接放到老年代啊。
即:HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在伊甸区及from、to区之间来回复制,产生大量的内存复制操作。
(3)经典款
前面已经讲过发生垃圾收集时,将伊甸和from中仍然存活的对象一次性复制到to空间上,然后直接清理掉伊甸和from空间。
在这之后小V又收拾了几次房间,每次都能在to房间看见同一件衣服,小V心想着“都过去这么久了,这件衣服还是那么好看,不愧是经典款,那么我就把你放到老年代房间里去吧。”
即:对象通常在Eden区里诞生,如果经历第一次GC后仍然存活,并且能被to空间容纳的话(容纳不了就是“床大放不下”的情况了),该对象会被移动到to空间中,并且将其对象年龄设为1岁。
对象在to区每熬过一次GC,年龄就增加一岁,当它的年龄增加到一定程度(默认15),就会晋升到老年代中。
(4)小V就是这么决定的
在往to房间堆放好东西之后,小V发现这一次搬进来的东西就占了整个房间一半的地方,小V决定把这些东西移到老年代房间去。
即:如果在to空间中相同年龄所有对象大小的总和大于to空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。