Java的堆内存分代是指将不同生命周期的堆内存对象存储在不同的堆内存区域中,这里的不同的堆内存区域被定义为“代”。这样做有助于提升垃圾回收的效率,因为这样的话就可以为不同的"代"设置不同的回收策略。
一般来说,Java中的大部分对象都是朝生夕死的,同时也有一部分对象会持久存在。因为如果把这两部分对象放到一起分析和回收,这样效率实在是太低了。通过将不同时期的对象存储在不同的内存池中,就可以节省宝贵的时间和空间,从而改善系统的性能。
Java的堆由新生代(Young Generation)和老年代(Old Generation)组成。新生代存放新分配的对象,老年代存放长期存在的对象。
新生代(Young)由年轻区(Eden)、Survivor区组成(From Survivor、To Survivor)。默认情况下,新生代的Eden区和Survivor区的空间大小比例是8:2,可以通过-XX:SurvivorRatio参数调整。
新生代
主要用来存放刚创建的对象(new),新生代的内存空间占用堆的1/3。由于对象频繁创建,会导致新生代频繁触发MiniorGC 进行垃圾回收。
新生代可以分为Eden区(伊甸园),From Survivor区,To Survivor区,三者的大小比是8:1:1,可以通过参数-XX:SurvivorRatio=8
设置。
-
Eden区: Java对象出生地(并不是每个new对象都存放在这里,较大的对象可能直接到老年代)
-
From区:第一次gc,存活下来的对象所在的区,也是每次gc对象要移动的区。
-
To区:和from区作用一样,就是from区和to区两个空间不断移动存活的对象。
下面我们说下三个区的关联
大多数情况下,对象在新生代的Eden区中分配,当Eden区没用足够空间进行分配时,虚拟机将发起一次Minor GC。而经过这次Minor GC后仍然存活的对象将全部进入To区,再次GC的时候eden和To区中存活的对象又被复制进入到From区。就这样在每次Minor GC的时候存活的对象反复在From区和To区来回复制,每复制一次对象的年龄就+1,当年龄达到约定的年龄(默认15,-XX:MaxTenuringThreshold=15
)之后就会进入老年代。
老年代
老年代的内存空间占用堆的2/3, 存放一些生命周期较长的对象,像新生代晋升过来的或者较大对象。如果老年代内存不足将进行Major GC(采用标记-清除算法),如果回收之后还是不足就会就会抛出OOM(Out of Memory)异常了。
永久代
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
为什么1.8改成元空间? 1. 字符串常量池存在于永久代中,在大量使用字符串的情况下,非常容易出现OOM的异常。 2. JVM加载的class的总数,方法的大小等都很难确定,因此对永久代大小的指定难以确定。太小的永久代容易导致永久代内存溢出,太大的永久代则容易导致虚拟机内存紧张。 3. 永久代对GC的回收带来了不必要的复杂度,而且回收率偏低。
解释:
新生代GC(Minor GC):发生在新生代的垃圾收集动作,因为Java对象大多都具有朝生夕死的特性,所以Minor GC很频繁,一般回收速度也比较快。
老年代GC(Major GC/Full GC):发生在老年代的GC,出现Major GC 经常会伴有至少一次Minor GC(不是一定),Major GC的速度一般比Minor GC慢10倍以上。
为什么要分代
分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。
试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
什么情况下触发垃圾回收
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。
Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
· 年老代(Tenured)被写满
· 持久代(Perm)被写满
· System.gc()被显示调用
·上一次GC之后Heap的各域分配策略动态变化