本文共 4061 字,大约阅读时间需要 13 分钟。
上一节内容回顾 :
回收 针对的是线程共享内存(堆,方法区)
判断对象是否存活?
a. 引用计数法(Python,C++)
b. 可达性分析算法(Java,C#)
核心思想:
GC Roots
对象开始向下搜索,若到指定对象有路可走(即"可达"),认为此对象存活;GC Roots
对象到目标对象均不可达,认为目标对象已经不再存活.在Java语言中,可作为GC Roots
的对象包含下面几种 :
虚拟机栈中的临时变量引用的对象
本地方法栈中JNI(Native方法)引用的对象
方法区中类静态变量引用的对象
方法区中常量引用的对象
JDK 1.2之后关于引用的扩充:强软弱虚,引用强度依次扩充
强引用 :
程序中普遍存在的,
GC Roots
对象所指向的引用都属于强引用
Person per = new Person();
只要当前对象被任意一个强引用指向,即便内存不够用也不能回收此对象
软引用 :
有用但不必须的对象(缓存对象)
当前内存够用时,不回收对象;
当前内存不够用时,回收对象.
JDK 1.2之后用SoftReference
来表示软引用
Person per = new Person(); //必须先创建强引用对象 SoftReferencesoftReference = new SoftReference<>(per); per = null; System.gc;
弱引用 :
描述非必须对象,强度若于软引用
JDK 1.2之后用WeakReference
来表示弱引用
Person per = new Person(); //必须先创建强引用对象WeakReferenceweakReference = new WeakReference<>(per);per = null;//该对象指向为空,可以被回收System.gc;
虚引用 :
最弱的引用关系,完全对对象的生存周期不造成影响,并且无法通过虚引用取得对象
为对象设置虚引用的目的 :该对象在被GC回收之前由系统发回 回收通知
JDK 1.2之后用PhantomReference
来表示虚引用
当一个对象到GC Root
不可达时,并不是"当场去世",而是进行自我拯救
finalize()
对象的一次自我拯救机会
JVM在进行GC之前,需要判断回收对象所在的类 是否覆写了finalize()
?
finalize()
finalize()
未被JVM调用,JVM则会调用finalize()
;对象在此调用过程中与GC Roots
有路可达,此对象不再被回收(自救成功)finalize()
已被JVM调用,此对象被回收 package iqqcode.Study.GCRoots;import java.util.concurrent.TimeUnit;/** * @Author: Mr.Q * @Date: 2019-09-05 19:37 * @Description:finalize()对象的自我拯救 * */public class FinalizeTest { private static FinalizeTest test; @Override protected void finalize() throws Throwable { System.out.println("finalize method execute!"); test = this; //test等于当前调用finalize()的对象 } public static void main(String[] args) throws InterruptedException { test = new FinalizeTest(); test = null; //此时test对象没有被强引用指向,无引用指向 //JVM检查 是否覆写且调用过finalize() //对象在此调用过程中与`GC Roots`有路可走,此对象不再被回收 System.gc(); TimeUnit.SECONDS.sleep(1); if(test == null) { System.out.println("Now I'm dead..."); }else { System.out.println("I'm Alive!"); } test = null;//此时无引用指向,可以被垃圾回收 //JVM检查 覆写且调用过finalize() //不再调用finalize(),判断对象死亡 //finalize() 对象的一次自我拯救机会 System.gc(); TimeUnit.SECONDS.sleep(1); if(test == null) { System.out.println("Now I'm dead..."); }else { System.out.println("I'm Alive!"); } //TODO }}
方法区回收(永久代回收)------废弃常量和无用类
方法区的GC频率非常低
算法分为"标记"和"清除"两个阶段 :
标记-清除算法的不足主要有两个 :
gc
频繁发生堆(所有对象和数组对象)
- "复制"算法是为了解决"标记-清理"的效率问题.
- 它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象 复制 到另一块上面,然后再把已经使用过的内存区域一次清理掉
新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1 : 1的比例来划分内存空间
而是将内存(新生代内存)分为一块较大的 Eden(伊甸区) 空间和两块较小的 Survivor(幸存区) 空间,每次使用 Eden 和其中一块Survivor(Survivor区域一个称为From区,另一个称为To区域)
画图说明
HotSpot 实现的复制算法流程如下:
Eden
区满的时候,会触发第一次Minor gc
,把还活着的对象拷贝到Survivor From
区,然后清理掉Eden
区的所有空间. 当Eden区即将满时,再次触发Minor gc
,会扫描Eden
区和From
区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To
区域,并将Eden
和From
区域清空Eden
又发生Minor gc
的时候,会对Eden
和To
区域进行垃圾回收. 存活的对象复制到From
区域,并将Eden
和To
区清空. 之后依次循环From
和To
区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代核心思想相较于标记清除算法------整理阶段先让存活对象向一端移动,而后清理掉端边界以外的内存
Question : 为何老年代不采用复制算法?
Answer :新生代中的绝大多数对象都是朝生夕死,所以每次复制存活的对象较少;而老年代中大部分对象都存活,反复复制反而效率低下
将堆空间分为新生代(-Xmn)与老年代(堆的大小 - 新生代)空间,其中新生代采用复制算法,老年代采用标记整理算法
面试题 : 请问了解Minor GC和Full GC么,这两种GC有什么不一样吗?
Minor GC
又称为新生代 GC : 指的是发生在新生代的垃圾收集. 因为Java对象大多都具备朝生夕灭的特性,因此 Minor GC
(采用复制算法)非常频繁,一般回收速度也比较快.Full GC
又称为 老年代 GC 或者 Major GC
: 指发生在老年代的垃圾收集. 出现了 Major GC
,经常会伴随至少一次的 Minor GC
(并非绝对,在 Parallel Scavenge
收集器中就有直接进行 Full GC
的策略选择过程). Major GC
的速度一般会比 Minor GC
慢10倍以上.转载地址:http://itlmz.baihongyu.com/