查找算法
根搜索算法,
基本思想是:从GC Roots的根节点出发,向下搜索,如果一个对象不能达到GC Roots的时候,说明该对象不再被引用,可以被回收。
补充概念,在JDK1.2之后引入了四个概念:强引用、软引用、弱引用、虚引用。
强引用:new出来的对象都是强引用,GC无论如何都不会回收,即使抛出OOM异常。
软引用:只有当JVM内存不足时才会被回收。
弱引用:只要GC,就会立马回收,不管内存是否充足。
虚引用:它作用就是做一些跟踪记录,辅助finalize函数的使用。
什么样的类需要被回收:
a.该类的所有实例都已经被回收;
b.加载该类的ClassLoad已经被回收;
c.该类对应的反射类java.lang.Class对象没有被任何地方引用。
内存分区
JVM中的堆一般分为三部分,新生代、老年代和永久代。
- 1 新生代
主要是用来存放新生的对象。一般占据堆空间的1/3,由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
新生代分为Eden区、ServivorFrom、ServivorTo三个区。
-
- Eden区:Java新对象的出生地(如果新创建的对象占用内存很大则直接分配给老年代)。当Eden区内存不够的时候就会触发一次MinorGc,对新生代区进行一次垃圾回收。
- ServivorTo:保留了一次MinorGc过程中的幸存者。
- ServivorFrom: 上一次GC的幸存者,作为这一次GC的被扫描者。
当JVM无法为新建对象分配内存空间的时候(Eden区满的时候),JVM触发MinorGc。因此新生代空间占用越低,MinorGc越频繁。MinorGC采用复制算法。
- 2 老年代
老年代的对象比较稳定,所以MajorGC不会频繁执行。
触发MinorGC的条件(清理老年代):
1 在进行MajorGC之前,一般都先进行了一次MinorGC,使得有新生代的对象进入老年代,当老年代空间不足时就会触发MajorGC。
2 当无法找到足够大的连续空间分配给新创建的较大对象时,也会触发MajorGC进行垃圾回收腾出空间。
MajorGC采用标记—清除算法(或者标记—整理算法)
MajorGC的耗时比较长,因为要先整体扫描再回收,MajorGC会产生内存碎片。为了减少内存损耗,一般需要合并或者标记出来方便下次直接分配。
当老年代也满了装不下的时候,就会抛出OOM。
- 3永久代
指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。
Class在被加载的时候元数据信息会放入永久区域,但是GC不会在主程序运行的时候清除永久代的信息。所以这也导致永久代的信息会随着类加载的增多而膨胀,最终导致OOM。
Full GC触发机制:(是清理整个堆空间—包括年轻代和老年代)
1 调用System.gc时,系统建议执行Full GC,但是不必然执行
2 老年代空间不足
3 方法区空间不足
4 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
5 由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,
4 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载。
怎么晋升老年代
Java中对象一般在新生代中进行创建,但是在一定的机制中会触发晋升老年代的机制。相关机制包括:
1、分配担保机制
Eden区满时,进行Minor GC,当Eden和一个Survivor区中依然存活的对象无法放入到Survivor中,则通过分配担保机制提前转移到老年代中。
2、对象过大
若对象体积太大,新生代无法容纳这个对象,就会绕过新生代, 直接在老年代分配, 此参数只对Serial及ParNew两款收集器有效。
参数-XX:PretenureSizeThreshold用来设置这个门限值。
3、长期存活的对象
对象头的Mark Word中包含对象的年龄。当年龄增加到一定的临界值时,就会晋升到老年代中。
该临界值由参数:-XX:MaxTenuringThreshold来设置,默认为15,即对象在经历15次minor gc后会晋升到老年代。
4、动态对象年龄判定
如果在Survivor区中相同年龄的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
GC算法
常见的GC算法:复制、标记-清除和标记-压缩
复制:复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域,当存活的对象较少时,复制算法会比较高效(新生代的Eden区就是采用这种算法),其带来的成本是需要一块额外的空闲空间和对象的移动。
标记-清除:该算法采用的方式是从跟集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,并进行清除。
清除阶段清理的是没有被引用的对象,存活的对象被保留。
标记-清除动作不需要移动对象,且仅对不存活的对象进行清理,在空间中存活对象较多的时候,效率较高,但由于只是清除,没有重新整理,因此会造成内存碎片。
标记-压缩:该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后再更新其引用对象的指针,
垃圾收集器
在JVM中,GC是由垃圾回收器来执行,所以,在实际应用场景中,我们需要选择合适的垃圾收集器,下面我们介绍一下垃圾收集器。
4.1 串行收集器(Serial GC)
Serial GC是最古老也是最基本的收集器,但是现在依然广泛使用,JAVA SE5和JAVA SE6中客户端虚拟机采用的默认配置。比较适合于只有一个处理器的系统。在串行处理器中minor和major GC过程都是用一个线程进行回收的。它的最大特点是在进行垃圾回收时,需要对所有正在执行的线程暂停(stop the world),对于有些应用是难以接受的,但是如果应用的实时性要求不是那么高,只要停顿的时间控制在N毫秒之内,大多数应用还是可以接受的,而且事实上,它并没有让我们失望,几十毫秒的停顿,对于我们客户机是完全可以接受的,该收集器适用于单CPU、新生代空间较小且对暂停时间要求不是特别高的应用上,是client级别的默认GC方式。
4.2 ParNew GC
基本和Serial GC一样,但本质区别是加入了多线程机制,提高了效率,这样它就可以被用于服务端上(server),同时它可以与CMS GC配合,所以,更加有理由将他用于server端。
4.3 Parallel Scavenge GC
在整个扫描和复制过程采用多线程的方式进行,适用于多CPU、对暂停时间要求较短的应用,是server级别的默认GC方式。
4.4 CMS (Concurrent Mark Sweep)收集器
该收集器的目标是解决Serial GC停顿的问题,以达到最短回收时间。常见的B/S架构的应用就适合这种收集器,因为其高并发、高响应的特点,CMS是基于标记-清楚算法实现的。
CMS收集器的优点:并发收集、低停顿,但远没有达到完美;
CMS收集器的缺点:
a.CMS收集器对CPU资源非常敏感,在并发阶段虽然不会导致用户停顿,但是会占用CPU资源而导致应用程序变慢,总吞吐量下降。
b.CMS收集器无法处理浮动垃圾,可能出现“Concurrnet Mode Failure”,失败而导致另一次的Full GC。
c.CMS收集器是基于标记-清除算法的实现,因此也会产生碎片。
4.5 G1收集器
相比CMS收集器有不少改进,首先,基于标记-压缩算法,不会产生内存碎片,其次可以比较精确的控制停顿。