3.1 概述
-
哪些内存需要回收
-
什么时候回收
-
如何回收
-
程序计数器,虚拟机栈,本地方法栈随着线程而生而灭,栈中栈帧随着方法进入和退出而进行出栈和进栈,这几个地方的内存分配和回收具有确定性,,java堆和方法区不一样,只有程序运行时才知道创建哪些对象,内存分配和回收都是动态的;
3.2 对象已死吗
-
怎样确定?
3.2.1 引用计数法
-
给对象一个引用计数器,有地方引用就加1,引用失效就减1,为0时就不会再使用;
-
弊端:两个对象相互引用,会导致计数器永远不会为0
3.2.2 可达性分析算法
-
GC Roots的对象作为起始点,向下搜索,搜索走过的路径叫做引用链,当一个对象到GC Roots没有任何引用链则此对象不可用;他们到GC Roots是不可达的,会被判定为可回收对象;
-
GC Roots根包含虚拟机栈(栈帧中局部变量表)引用的对象,方法区中静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象;
3.2.3 再谈引用
-
强引用:垃圾收集器永远不会回收,最常见的引用,Object obj = new Object();
-
软引用:有用但不是必须的对象,当要发生内存溢出时,会对这些对象进行二次回收,内存还不够才发生内存溢出;SoftReference
-
弱引用:非必需的对象,只能生存在下一次垃圾回收之前,垃圾回收时不管是否内存足够,都会进行回收;WeekReference
-
虚引用:又叫幽灵引用或者幻影引用,无法根据引用获取到对象实例,唯一作用在垃圾回收时收到一个系统通知;PhantomReference
3.2.4 生存还是死亡
-
通过可达性分析算法确定后不可达的对象,也不一定立马死亡,需要进行2次标记,当对象没有与GC Roots存在引用链时,则会进行第一次标记和第一次筛选,筛选的条件是是否有必要执行finalize()方法,当对象没有覆盖finalize()方法和虚拟机已经执行过此方法,则都认为没必要再执行;
-
若对象被判定为有必要执行finalize()方法时,则会加入一个F-queue队列中,并启动一个低优先级的定时任务去执行队列中的任务,在这里会进行第二次规模的标记,若在执行finalize()方法时,对象重新与GC Roots存在引用链,则移除即将回收的集合;注意⚠️不建议在finalize()中做一些操作;
3.2.5 回收方法区
-
包含两部分:废弃常量和无用的类
-
废弃常量:没有任何地方引用到这个常量;
-
无用的类:类的所有实例都被回收,加载该类的classLoader被回收,该类对应的java.lang.Class对象没有任何地方引用,无法在任何地方通过反射访问到该类的方法;
-
虚拟机可以对满足以上条件的无用类进行垃圾回收,但是不是必须回收,可以通过一些参数进行配置;
3.3 垃圾收集算法
3.3.1 标记-清除算法
-
分为标记和清除两步骤,通过对象标记(可达性分析算法)将回收的对象进行标记,再进行清除;
-
弊端:1.标记和清除两个步骤效率都不高;2会产生大量的空间碎片,导致再有大对象需要存放时,会再一次触发垃圾回收;
3.3.2 复制算法
-
将内存划分为容量相等的两块,每次只使用一块,当这一块内存用完后,就将还活着的对象复制到另一块内存中,当前这块内存全部进行清空;
-
优点:解决了效率问题和内存不连续的问题;
-
缺点:内存利用率不高,每次只用一半;
-
目前的商业虚拟机都使用复制算法来回收新生代,研究表明新生代的虚拟机98%对象都是朝生夕死,所以新生代包含一块Eden和两小块S urvivor,Eden:Survivor= 8:1,每次只用Eden和其中一块Survivor,垃圾回收将Eden和Survivor剩余存活的对象放到另一个Survivor中,所以利用率有90%,只有10%空间浪费,但是也无法保证每次垃圾回收都剩余10%以下,当超过另一个Survivor空间时,需要分配担保机制直接进入老年代;
3.3.3 标记-整理算法
持续更新中。。。



