1)GC算法(各种算法的优缺点以及应用场景)
2)内存对象的循环引用及避免
3)内存回收机制、GC回收策略、GC原理时机以及GC对象
4)垃圾回收机制与调用System.gc()区别
对象被判定为垃圾的标准
没有被其他对象引用
那些内存需要回收?
(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法)
什么时候回收?
(堆的新生代、老年代、永久代的垃圾回收时机,MinorGC 和 FullGC)
如何回收?
(三种经典垃圾回收算法(标记清除算法、复制算法、标记整理算法)及分代收集算法 和 七种垃圾收集器)
一. 如何确定一个对象是否可以被回收?
1、 引用计数算法:判断对象的引用数量
通过判断对象的引用数量来决定对象是否可以被回收。
任何引用计数为0的对象实例可以被当作垃圾收集。
优点:执行效率高,程序执行受影响较小。
缺点:无法检测循环引用情况,导致内存泄漏。
2、 可达性分析算法:
判断对象的引用链是否可达通过判断对象的引用链是否可达来决定对象是否可以被回收。
可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,通过一系列的名为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的,如下图所示。在Java中,可作为 GC Root 的对象包括以下几种:
- 虚拟机栈(栈帧中的局部变量表) 中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中Native方法引用的对象;
- 活跃线程的引用对象;
二. 垃圾收集算法
1、标记清除算法
标记-清除算法分为标记和清除两个阶段。
该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收。
- 效率问题:标记和清除两个过程的效率都不高;
-
空间问题:标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,因此标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2、复制算法
复制算法将可用内存按容量划分为大小相等的两块,
每次只使用其中的一块。
当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,
然后再把已使用过的内存空间一次清理掉。
事实上,现在商用的虚拟机都采用这种算法来回收新生代。
3、标记整理算法
标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)
不用设置两块内存互换、避免内存的不连续性
4、分代收集算法
不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。
对象如何晋升到老年代
- 经历一定Minor次数依然存活的对象
- Survivor区中存放不下的对象
- 新生成的大对象(-XX:+PretenureSizeThreshold)
常用的调优参数
- -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
- -XX:NewRatio:老年代和年轻代内存大小的比例
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值。
垃圾回收有两种类型,Minor GC 和 Full GC。
-
Minor GC:对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。
- Full GC:也叫 Major GC,对整个堆进行回收,包括新生代和老年代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:
老年代被写满、
永久代(Perm)被写满和System.gc()被显式调用等。
CMS GC时出现promotion failed,concurrent mode failure
使用RMI来表示RPC或管理的JDK应用,每小时执行一次Full GC
Safepoint
- 分析过程中对象引用关系不会发生变化的点
- 产生Safepoint的地方:方法调用;循环跳转;异常跳转等
- 安全点数量得适中
垃圾收集器
- Serial收集器(-XX:+UseSerialGC, 复制算法): 新生代单线程收集器必须暂停所有工作线程,标记和清理都是单线程,优点是简单高效,client模式下默认的年轻代收集器;
- Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本,进行垃圾收集时,必须暂停所有工作线程。简单高效,Client模式下默认的老年代收集器;
- ParNew收集器 (-XX:+UserParNewGC复制算法): 新生代并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
- Parallel Scavenge收集器 (复制算法):
比起关注用户线程停顿时间,更关注系统的吞吐量
在多核下执行才有优势,Server模式下默认的年轻代收集器。
新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景; - Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
- CMS(Concurrent Mark Sweep)收集器(标记-清除算法):
初始标记:stop-the-world。
并发标记:并发追溯标记,程序不会停顿。
并发与清理:查找执行并发标记阶段从年轻代晋升到老年代的对象。
重新标记:暂停虚拟机,扫描CMS堆中的剩余对象。
并发整理:清理垃圾对象,程序不会停顿。
老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。 - G1(Garbage First)收集器 (标记-整理算法):
Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
引用
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
当内存空间还足够时,则能保留在内存之中;如果内存在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。
强引用 就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类引用。 只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。通过 =null来回收。
软引用 用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
- 对象处在有用但非必须的状态
- GC会回收该引用的对象的内存
- 可以用来实现告诉缓存
弱引用 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。非必须的对象,比软引用更弱一些。GC时会被回收。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。适用于引用偶尔被使用且不影响垃圾收集的对象。
虚引用 是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
- 不会决定对象的生命周期
- 任何时候都可能被垃圾收集器回收
- 跟踪对象被垃圾收集器回收的活动,起哨兵作用
- 必须和引用队列ReferenceQueue联合使用
String str = new String("");
ReferenceQueue queue = new Reference();
PhantomReference ref = new PhantomReference(str,queue);
网友评论