-
一、Java堆内存
-
-
这里共介绍四种垃圾回收算法,其中目前较通用使用的是分代回收算法,根据不同的分代对象存活数量,以及空间资源使用不同的算法来回收;
-
JDK 1.7 以及之前为永久代(和元空间为一个东西)
-
JDK 1.8 以及之后为元空间(和永久代为一个东西)
-
JDK 1.7版本对内存如下:
-
-
JDK1.8版本堆内存如下:
-
新生代 Yong
-
-
新生代又分为
-
-
Eden
-
Survivor0(s0)
-
Survivor1(s1)
-
-
新生代用于存放新创建的对象,存储大小默认为堆大小的1/15,特点是对象更替速度快,即存活对象数量低,死亡对象多;
-
-Xmn: 可以设置新生代为固定大小;
-
-XX:NewRation 可以设置新生代和老年代的大小比例;
-
新生代使用标记-清除算法和并行收集器进行垃圾回收,对新生代的垃圾回收称为初级回收(Minor GC);
-
-
初级回收(Minor GC)将新生代分为三块
-
-
Eden
-
Survivor0(与S1交替使用,即同一时刻只保留一个活动的)
-
Survivor1(与S0交替使用,即同一时刻只保留一个活动的)
-
-
-
Minor GC 执行时,JVM操作如下:
-
-
1.程序挂起;
-
2.将Eden和活动Survivor中的存活对象(存活对象指的是仍被引用的对象)复制到另一个非活动的Survivor中(记录对象被复制到另一个Survivor的次数,此时称为年龄数,每次复制+1);
-
3.清除Eden和活动Survivor中对象;
-
4.将非活动Survivor标记为活动,将原来的活动Survivor标记为非活动;
-
5.上述为一轮Minor GC,如此往复多次,默认将年龄数为15的对象(高龄对象)移动到老年代(对于占用较大内存的对象,也会被直接送入老年代);
-
-
-
老年代 Tenured
-
-
老年代存储那些新生代存活下来的对象;
-
老年代发生的GC称为Full GC/Major GC, Full GC/Major GC不会像Minor GC 那么频发,采用标记-清除算法,手机垃圾的时候会产生许多内存碎片(后续有提到优化的标记-整理(标记-压缩)算法),因此此后若有较大的对象进入老年代而无法找到适合的存储空间,就会提前触发一次GC收集,整理内存;
-
-
永久代 Perm Gen /元空间 Meta Space
-
-
永久代(Perm Gen是JDK1.7中的特点,后来JDK1.8取消,称为Meta Space(与堆不相连本地空间)),永久代是JVM方法区的实现方式之一;
-
永久代存放的是应用元数据(应用中的类和方法、静态XX等等);
-
永久代的对象在Full GC/Major GC 的时候进行垃圾回收(程序挂了,或者程序结束,要在永久代分配空间但已经没有足够空间时,也要出发一次Full GC/Major GC);
-
-
二、垃圾回收GC(Garbage Collection GC )算法
-
-
1.标记-清除算法(Mark-Sweep GC)
-
-
标记-清除算法将垃圾回收分为两个阶段:
-
-
标记阶段
-
-
在此阶段首先通过根节点(GC Roots),标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象;
-
-
-
-
-
清除阶段
-
-
在此阶段,清除所有未被标记的对象;
-
-
-
适用场景:
-
-
存活对象较多的情况下比较高效;
-
适用于老年代(即在新生代存活下来的,存活在老年代);
-
-
缺点:
-
-
容易产生内存碎片,再拉一个比较大的对象时(典型情况:该对象的大小大于空闲表的每一块大小,但是小于其中两块儿的和),会提前触发垃圾回收;
-
扫描了整个空间两次(第一次:标记存活对象;第二次:清除没有标记的对象);
-
-
下图是标记清除算法的演示过程:
-
-
根集合引用A,引用C,但是B没有被引用;
-
因此对A,C做标记,B没有做标记,所以在清除阶段,将B回收;
-
-
-
2.复制算法(Copying GC)
-
-
从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块新的内存,之后将原来的那一块内存全部收回;
-
适用场景:
-
-
适用于收新生代;
-
存活对象较少的情况下比较高效;
-
扫描整个空间一次(标记存储对象)并复制移动存活对象;
-
-
缺点:
-
-
需要一块空的内存空间;
-
需要复制移动对象;
-
-
下面是复制算法的演示过程:
-
-
3.标记-整理(压缩)算法(Mark-Compact GC)
-
-
复制算法的高效性是建立在存活对象较少,垃圾对象多的前提下;
-
这种情况在新生代经常发生,但是在老年代更常见的情况下是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高;
-
标记-整理算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化;
-
首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。
-
这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此其性价比较高;
-
过程演示如下图:
-
-
4.分代收集算法(Generational Collection GC)
-
-
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记-整理(压缩)不适用于老年代的问题,将内存氛围各个年代。一般情况下将堆区划分为老年代和新生代;
-
在堆之外有一个代就是元空间(1.7以及之前叫永久代);
-
在不同年代适用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,可以使用标记清除或者标记-整理算法;
-
-
三、垃圾回收器
-
-
G1收集器(支持新生代和老年代, JDK 1,7、1.9...)
-
-
并行和并发;使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行;
-
分代收集;独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间,熬过了多次GC的旧对象,以获取更好的收集效果;
-
空间整合;基于标记-整理算法,无内存碎片产生;
-
可预测的停顿;能建立可预测的停顿时间模型,能让使用者明确在一个长度为M毫秒时间片段内,消耗在垃圾收集上的时间不得超过N毫秒;
-
-
Young Generation(新生代)
-
-
Serial收集器(JDK1.3)
-
-
采用复制算法的单线程的收集器;
-
-
ParNew收集器(JDK 1.4)
-
-
ParNew收集器其实就是Serial收集器的多线程版本;
-
-
Parallel Scavenge收集器(JDK 1.4、JDK 1.8)
-
-
Parallel Scavenge收集器也是一个新生代收集器,也是用复制算法的收集器,也是并行的多线程收集器;
-
Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器;
-
Parallel Scavenge收集器是虚拟机运行的目标则是达到一个可控制的吞吐量;
-
-
-
Tenured Generation(老年代)
-
-
Parallel Old收集器(jdk1.6、JDK 1.8)
-
-
Parallel Scavenges收集器的老年版本,使用多线程和标记-整理(压缩)算法;
-
-
CMS收集器(JDK1.5)
-
-
CMS(Conrruent Mart Sweep)收集器是以获取最短回收停顿时间为目标的收集器,使用标记-清除算法
-
-
1.初始标记:编辑GCRoots能直接关联到的对象,时间很短;
-
2.并发标记:进行GCRoots Tracing(可达性分析)过程,时间很长;
-
3.重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长;
-
4.并发清楚:回收内存空间,时间很长;
-
-
-
Serial Old收集器(JDK 1.5)
-
-
Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”;
-
-
-