垃圾收集器需要知道哪些对象需要回收。
判断对象需要回收大致分两种:
1、引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时,计数器值就减一,任何时刻计数器为零的对象就是不可能再被使用的。
缺点:当两个对象相互引用时,即使两个对象需要回收,引用计数法无法回收他们。
2、可达性分析算法
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
可作为GC Roots跟的对象包括以下几种:
1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程呗调用的方法堆栈中使用到的参数,局部变量、临时变量等。
2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
3、在方法区中常量引用的对象,譬如字符串常量池中的引用。
4、在本地方法栈中JNI(本地方法)引用的对象。
5、Java虚拟机内部的引用,如基本数据类型对应的class对象,一些常驻的异常对象等,还有系统类加载器。
6、所有被同步锁持有的对象。
7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调,本地代码缓存等等。
Java的对象引用
强引用:
类似“Object obj = new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用:
用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
弱引用:
也是描述非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
虚引用:
幽灵引用、幻影引用
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是未来能在这个对象被收集器收时收到一个系统通知。
被可达性分析算法标记为不可达的对象并不一定立即被回收
当被标记为不可达对象时,实际是进入一个缓刑阶段,真正标记一个对象为不可达,实际要经历两个阶段,
第一阶段判断对象为不可达并标记,
第二阶段判断对象有没有覆盖finalize()方法,或者finalize()已经被虚拟机调用,那么视为没有必须执行。
方法区的收集
方法区的回收条件:
1、该类所有的实例加载器都已经被回收,也就是Java堆中不存在该类及任何派生子类的实例。
2、加载该类的类加载器已经回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGI、JSP的重加载等,否则通常是很难达成的。
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集算法
引用计数式垃圾收集
追踪式垃圾收集
1、分代收集理论
弱分代假说:绝大数对象都是朝生夕灭
强分代假说 :熬过越多垃圾收集的对象就越难以消灭
名词解释
新生代收集(Minor GC / Young GC):指目标只是新生代的垃圾收集。
老年代收集(Major GC / Old GC):指对象只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
另外 ,“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,需要按上下文区分到底是老年代的收集还是整堆收集。
混合收集(Mieed GC):指目标是收集整个新生代以及部分老年代 的垃圾收集。目前只有G1收集器会有这种行为。
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
回收算法
标记-清除算法
缺点:
1、执行效率不稳定:
如果Java堆中包含大量对象,如果其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随着对象数量增长而降低
2、内存碎片:
标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
标记-复制算法
缺点:
1、将可用内存缩小为了原来的一半,空间浪费未免太多了
标记-整理算法
缺点:
1、需要暂停用户线程来标记、清理可回收的对象
安全点
也就是具有让程序房室结执行的特征
1、方法调用
2、循环跳转
3、异常跳转
等指令序列复用,只有这些功能的指令才会产生安全点
安全区域
能够确保某一段代码片段之中,引用关系不会发生变化,因为这个区域中任意地方开始垃圾收集都是安全的。可以把安全区域看做被扩展拉伸了的安全点。
记忆集与卡表
记忆集缩减了GC Roots扫描范围的问题。
记录精度的方式中,卡精度是一种称为卡表的方式去实现记忆集。
并发的可达性分析
为了降低用户线程的停顿,引入三个标记法
白色:
表示对象尚未被垃圾收集器访问过,显然在可达性分析刚刚开始的阶段,所有的对象都是白色,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
黑色:
表示对象已经被垃圾收集器访问过,且这个对象的所有引用都扫描过。黑的对象代表已经扫描过,它是安全存活的,如果其他对象引用指向了黑色对象,无需重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
灰色:
表示对象已经被垃圾回收器访问过,但这个对象上至少存在一个引用还没有被扫码过。
Serial收集器
新生代收集器 ,垃圾收集过程:
1、用户线程
2、GC线程(新生代采用复制算法,暂停所有用户线程)
3、GC线程(老年代采用标记-整理算法,暂停所有用户线程)
适用于:
单线程、客户端模式下
ParNew收集器
新生代收集器 ,垃圾收集过程:
1、用户线程
2、GC线程(新生代采用复制算法,暂停所有用户线程)
3、GC线程(老年代采用标记-整理算法,暂停所有用户线程)
使用于:
多线程
Parallel Scavene收集器
新生代收集器 ,垃圾收集过程:
适用于:
关注吞吐量的场景(吞吐量=运行用户代码时间➗(运行用户代码时间+运行垃圾收集时间))
Serial Old收集器
老年代收集器,单线程,标记-整理算法,客户端模式下
Parallel Old收集器
老年代,多线程,标记整理
CMS
老年代收集器,标记清除
1、初始标记(需要stop the world)
2、并发标记(可以与用户线程并发执行)
3、重新标记(需要stop the world)
4、并发清除(可以与用户线程并发执行)
缺点:
因为是并发清除,上面说到了,会产品内存碎片导致会触发Full GC
Garbage First
俗称G1收集器,
它衡量的不是垃圾属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异
1、G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域,每个regio你都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
region中还有一类特殊的Humongous区域,专门存放大对象的,大对象的定义是超过region空间的一半。
收集的思路是:
G1收集器去跟踪各个region里面的垃圾堆积的价值大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次有限处理回收价值收益最大的那些region,这就是Garbage First名字的由来。
这种使用region划分内存空间,以及具有优先级的区域回收方式,保证了G1 收集器在有限的时间内获取尽可能高的收集效率。
G1收集器解决跨region引用对象是通过记忆集避免全堆作为GC Roots扫码。
回收过程:
1、初始化标记
2、并发标记(可以与用户线程并发执行)
3、最终标记
4、筛选回收
CMS与G1收集器的区别
CMS 是基于标记清除算法
G1 整理来看是基于标记整理算法,局部来看是基于标记复制算法
G1的算法不管从何种角度都不会产生内存碎片,垃圾收集玩之后能提供规整的可用内存。
G1的垃圾收集产生的内存占用还是运行时额外执行负载都要比CMS高。
G1和CMS都使用卡表来处理跨代指针。
衡量垃圾回收的三个指标
1、内存占用
2、吞吐量
3、延迟
尽可能选择低延迟
对象优先在Eden分配
大对象直接进入老年代
长期存活的对象将进入老年代(对象在新生代没熬过一次Minor GC,年龄加1,默认15次则进入老年代)
特殊情况:
1、动态对象年龄判断:如果再Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等15次。
2、空间分配担保:发生Minor GC之前,虚拟机必须检查老年代最大可用的连续空间是否大于新生代所有对象空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,看下是否允许分配担保,如果允许,那么继续坚持老年代最大可用的连续空间十分大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC 是有风险的。如果小于,或者不允许,那么改为进行一次Full GC。