垃圾回收策略:针对线程共享内存(堆,方法区)
-
判断对象是否存活的方法:
-
引用计数法(Python,c++智能指针):
1)内容:给每个对象增加一个引用计数器,每当有一个引用指向当前对象时,计数器+1,当引用失效时,计数器就-1,任何时刻计数器为0的对象就是不能再被使用的,即对象已死。
2). 缺点:无法解决循环引用问题(我中有你,你中有我) -
可达性分析法(Java,C#,Lisp):通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为“引用链”,当一个对象到“GC Roots”没有任何的引用链相连时(称为不可达),说此对象是不可用的。
- 哪些对象可以作为GC Roots:
- 虚拟机栈与本地方法栈中的临时变量指向的对象
- 类中静态变量引用的对象
- 类中常量引用的对象
-
JDK1.2 之后关于引用的扩充:(强软弱虚,引用强度依次递减)
- 强引用:程序中普遍存在的,GC Roots指向的引用都属于强引用
CricleReferenceProblem obj1 = new CricleReferenceProblem();
只要当前对象被任意一个强引用指向,即便内存不够也不能回收此对象。 - 软引用:描述一些有用但不是必须的对象,比如缓存对象;
强度:当当前系统内存够用时,仅被软引用指向的对象还存活;若当前内存不够用,则下次GC时,会将所有仅被软引用指向的对象进行GC。
SoftReference来表示软引用
public class SoftReferenceTest{
private static final int_1MB = 1024*1024;
private byte[] data = new byte[2*_1MB];
public static void main(String[] args) {
SoftReferenceTest test = new SoftReferenceTest();
SoftReference<SoftReferenceTest> softReference = new SoftReference<>(test);
test = null;
System.gc();
}
}
- 弱引用
描述非必须对象,但是强度要弱于软引用。
仅被弱引用指向的对象,只能存活到下次GC之前,当GC开始时,不管内存是否够用,都会回收仅被弱引用指向的对象。
jdk1.2:WeakReference类来表示弱引用。 - 虚引用
被称为幽灵引用或幻影引用,它是最弱的一种引用
一个对象是否有虚引用的存在,完全不会对其生存周期产生影响,也无法通过虚引用得到一个对象实例。
设置虚引用的唯一目的:该对象被gc之前会由系统发回回收通知。
jdk1.2 用PhantomReference类表示虚引用
-
当一个对象到GC Roots不可达,就一定会当场去世?(finalize()对象自我拯救)
答:不一定,JVM在进行GC之前,需要判断即将回收的对象所在的类是否覆写了Object类中的finalize()方法,
若没覆写,则此对象直接被回收
若覆写了:1.若finalize()方法未被JVM调用,会调用finalize()方法,若对象在此次调用过程中与GC Roots有对象可达,则此对象不再被回收。
2.若finalize()方法已被JVM调用,此对象直接被回收。 -
简述Java中final,finally,finalize
- final:终结器
被final修饰的类不能有子类
被final修饰的值不能修改
被final修饰的方法不能被覆写 - finally:
用在异常体系中,finally代码块里的代码一定会被执行
作用:保证重点代码一定会被执行(流的关闭,数据库资源的关闭) - finalize:
Object类中的一个方法,对象的自我拯救;
作用:JVM在进行GC之前,需要判断即将回收的对象所在的类是否覆写了Object类中的finalize()方法。若没覆写,则此对象直接被回收。若覆写了:
1.若finalize()方法未被JVM调用,会调用finalize()方法,若对象在此次调用过程中与GC Roots有对象可达,则此对象不再被回收。
2.若finalize()方法已被JVM调用,此对象直接被回收。
- final:终结器
-
已经死亡的对象如何进行垃圾回收:
- 方法区的回收(永久代回收):回收废弃常量和无用的类。GC频率非常低,几乎不怎么回收。
- 废弃常量: 假如一个字符串“abc”进入了常量池,但当前系统没有任何一个String对象引用常量池的“abc”常量,也没有在其他地方引用这个字面量,如果此时发生GC并且有必要的话,这个“abc”常量会被系统清理出常量池。常量池中的其他类(接口),方法,字段的符号引用也与此类似
- 无用的类:同时满足下面三个条件才是无用类
该类所有实例都已被回收
加载该类的ClassLoader(类加载器)已经被回收
该类对应的Class对象没有在任何其他地方被引用,无法在任何地方通过反射访问该类的方法
- 方法区的回收(永久代回收):回收废弃常量和无用的类。GC频率非常低,几乎不怎么回收。
-
堆上的垃圾回收:
- 堆(所有对象和数组对象)
- 新生代:对象默认先在新生代产生,大部分对象在此区域存放,该区域特点:对象“朝生夕死”(存活率很低)
- 老年代:存活率很高
- 标记清除法:
- 标记出需要回收的所以对象。
- 清除阶段统一回收被标记的对象。
- 缺陷:效率不高,标记清除会产生大量的不连续空间碎片,导致gc频繁发生。
- 复制算法(新生代垃圾回收算法)超重要:
核心思想:将可用内存按照容量划分为大小相等的两块,每次只使用其中一块,当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。经调查,新生代中98%的对象存活率都很低,所以不需要1:1来划分内存空间,将新生代内存划分为一块较大的Eden(伊甸园)和两块内容较小的Survivor(幸存者)空间,默认比例8:1:1,每次使用Eden和其中一块Survivor(两个Survivor一个称为From区,另一个称为To区域)。- step1:对象默认都在Eden区产生,当Eden空间即将满时,触发第一次Minor GC(新生代GC),将Eden区所有存活对象复制到from区,然后一次性清理掉Eden区的所有空间。
- step2:当Eden空间再次即将满时,触发Minor GC,此时需要将Eden和From区的所有存活对象复制到To区,然后一次清理掉Eden和From区的所有空间。
- step3:之后的新生代GC,重复阶段2,(From和To区来回作为备用区)。
- step4:当一个对象一直在From和To区来回交换软干次(默认15次),就将此对象置入老年代空间。
- 标记整理算法(老年代的垃圾回收算法)
核心思想:相较于标记清除,先在整理阶段让存活对象向一端移动,然后清理掉存活对象边界之外的所有空间。- 为何老年代不采用复制算法:老年代存活率很高,利用复制算法存活对象很多,效率极低。
- 分代收集策略(Java采用的)
- 将堆空间分为新生代(-Xmn)与老年代(堆的大小-Xmn)空间,其中新生代次用复制算法,老年代采用标记整理算法。
- 堆(所有对象和数组对象)
-
请问了解Minor GC和Full GC吗,这两种GC有什么区别:
答:1)Minor GC称为新生代GC,是发生在新生代的垃圾回收,Java对象大多存活率低,所以Minor GC非常频繁的采用复制算法,一般回收速度也很快。
2)Full GC称为老年代GC或Major GC,是发生在老年代的垃圾回收,出现Major GC通常会伴随至少一次的Minor GC(并非绝对),Major GC 的速度一般比Minor GC慢10倍以上。 -
垃圾回收算法:
- 对象的分配策略:
- 对象默认在新生代的Eden区产生
- 大对象直接进入老年代(-XX:PretenureSizeThreshold)
- 长期存活对象(默认15)进入老年代。
- 动态年龄判断:若From或To区的相同年龄对象总和超过Survivor空间的一半,将所有此年龄对象直接晋级老年代。
- 对象的分配策略:
-
JVM内置的检测工具
- jps: JVM Process Status Tool—显示指定系统内所有的HotSpot虚拟机进程,返回当前操作系统中所有JVM进程ID。jsp -l(输出包名.类名)
- jmap: JVM Memory Map—查看当前JVM的内存情况,尤其是堆上的情况。jmap -head PID(查看PID的JVM的堆情况)
- jstack: 查看当前JVM的线程栈情况,常用于解决线程卡死问题。