lt;bgt;什么是垃圾?lt;/bgt;
没有任何指针指向的对象就是垃圾(循环引用依赖除外)
lt;bgt;判断对象是否为垃圾lt;/bgt;
引用记数法
给每一个对象都加一个引用计数器,每当有一个引用指向该对象,那么对象的引用计数器加一。
缺点:无法解决循环依赖问题
可达性分析算法(默认算法)
从GC ROOT作为起始点,开始向下遍历,搜索路径称为“引用链”,当一个对象到GC ROOT有一条完整的引用链,那么说明此对象就是可达的。
GC ROOTS
堆的周边对象(虚拟机栈、本地方法栈、方法区)
方法区中静态常量引用的对象
方法区中常量引用的对象
临时GC ROOTS,在进行垃圾回收时,老年代可以指向新生代中的对象并临时作为GC ROOTS。
lt;bgt;四大引用lt;/bgt;(5)
强引用:不会被回收
Strong Reference:垃圾收集器永远不会回收掉被强引用引用者的对象
软引用:内存不足即回收
Soft Reference:一些有用但非必需的对象,如果内存不足,那么就会回收软引用,如果内存还不足,那么就会报内存溢出异常
内存不足一般包括OOM和老年代空间不足
使用软引用情况较多,因为软引用能够加快JVM对垃圾回收的速度,可以维护系统的运行安全,防止内存溢出等问题。
弱引用:发现即回收
Weak Reference:强度比软引用还弱一些。不论内存是否溢出,垃圾收集器都会回收。
一般很少使用
虚引用:追踪对象的回收过程
Phantom Reference:在这个垃圾被垃圾收集器回收时给系统发一个通知。
必须和引用队列一起使用
一般很少使用
终结器引用:实现对象的finalize方法
在GC时,终结器引用入队列。由finalizer线程通过终结器引用找到被引用对象的finalize方法。
lt;bgt;不可达的对象”非死不可“?lt;/bgt;
真正宣告一个对象死亡,至少要经历两次垃圾回收过程。
不可达的对象第一次进行标记并进行一次筛选,筛选的条件是 此对象是否有必要执行finalize方法。如果对象没有 finalize 方法或者以及执行过finalize方法,意味着没有必要执行 finalize 方法
如果有必要执行 finalize 方法,被判定为垃圾的对象会被放在队列中进行第二次标记,此过程如果还没有指向它的引用,那么就会被回收。
lt;bgt;如何判断废弃的常量、类?lt;/bgt;
废弃常量:如果没有任何引用指向常量的话,那么就是废弃常量。
废弃的类:方法区主要回收无用的类。需要同时满足左边三个条件....
该类的所有 实例 都已经被回收,堆中不存在该类的任何实例
加载该类的 ClassLoader 已被回收
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法通过反射访问该类的方法
lt;bgt;垃圾回收算法lt;/bgt;(3)
标记 - 清除算法
过程:1、首先标记所有的可达对象;2、清除没有被标记的对象;
缺点:1、标记、清除阶段效率都不高;2、碎片化内存。可能会导致无法分配大对象。
复制算法
过程:1、将内存分为两份,每次只使用其中的一份,把可达的对象复制到一块内存上;2、清除另一半的空间;
缺点:避免了内存碎片化,但内存缩小了一半
新生代的垃圾回收算法
在新生代中,大部分的对象都是”朝生夕死“的,所以只需付出少量的复制成本就可以完成GC
因为新生代中的对象98%是”朝生夕死“的,所以不需要1:1来分配。Eden区和两个Survivor区的比例为8:1:1,每次只使用Eden区和一块Survivor区
当回收时,首先在Eden区和一块S1区分配对象,进行Minor GC时,首先把存活的对象复制到为空的S2区,再清除Eden区和S1区的所有对象
老年代为新生代分配担保
标记 - 整理算法
过程:1、首先将可达的对象复制到内存的另一侧;2、清除另一侧的所有对象
老年代的垃圾回收算法
老年代中大部分的对象存活时间较长,并且没有额外的空间对其进行分配担保,所以一般采用”标记-清楚“或”标记-整理“算法
lt;bgt;内存分配与回收策略lt;/bgt;(5)
对象首先在每个线程的TLAB上分配,TLAB默认开启,如果TLAB分配不下,就在Eden区分配
为什么要有TLAB?
1、JVM对象的创建是非常频繁的,在并发环境下划分内存是不安全的。
2、避免多个线程操作同一地址。如果没有TLAB的话,那就需要使用加锁,影响分配速度
大多数情况下,对象首先在Eden区分配,如果内存不足,会进行一次Minor GC
大对象直接进入老年代,避免了在新生代中来回复制,比如字符串和数组
长期存活的对象进入老年代
每一个对象都有一个年龄计数器【0-15】,每经过一次Minor GC并且存活下来,年龄计数器值+1,当超过15岁,对象直接进入老年代
进入老年代的年龄可以通过参数-XX:MaxTenuringThreshold设置
动态对象年龄判断
对象并不一定是年龄到达15岁才会进入老年代。如果Survivor区中相同年龄的对象大小超过内存大小的一半,那么年龄大于等于该年龄的对象将直接进入老年代
空间分配担保
当对象进行一次Minor GC时,首先会检查 ”老年代中连续内存的大小是否 大于 当前新生代中所有对象的大小“ ?
如果大于,正常进行Minor GC;否则虚拟机会去检查以往每次Minor GC后存活对象的大小是否 小于 老年代中连续空间的大小?
如果小于,进行Minor GC,即使是有风险的;否则老年代会先进行一次Full GC,以便给新生代进行分配担保。
只要老年代中的连续内存大小 大于 新生代中的所有对象大小 或者 大于 历次Minor GC之后存活对象的平均数大小 会进行 Minor GC,否则进行 Full GC
1、对象首先尝试栈上分配,2、失败的话就在TLAB上分配,3、TLAB分配满后在Eden区上,也有可能进入老年代
Minor GC和Full GC
Minor GC
Minor GC发生在新生代中的GC,Eden区满会触发 Minor GC,Survivor满不会触发。因为大多数的对象是 ”朝生夕死“ 的,所以Minor GC比较频繁
Full GC
发生在老年代的垃圾回收,一般会伴随这至少一次的Minor GC,速度比Minor GC慢10倍以上。
Full GC的触发条件(5)
调用 System.GC(),建议虚拟机进行一次Full GC,但不一定会真的执行。不建议使用这种方式
可通过 -XX:+DisableExplicitGC 来禁止RMI调用System.gc()
老年代的空间不足
尽量不要创建大的字符串和数组
通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉
通过 -XX:MaxTenuringThreshold 调大对象 进入老年代的年龄
空间分配担保失败
进行Minor GC之前,如果老年代中的连续内存大小 小于 新生代中所有的对象大小
如果老年代中连续内存的大小 小于 新生代中历次Minor GC后存活对象的平均数
Concurrent Mode Failure
执行 CMS GC 的过程中 同时有对象放入老年代,而此时老年代中的空间不足,便会报CMF错误
JDK 1.7及以前永久代空间不足
垃圾回收器(7)
CMS(4)
1、初始标记
标记一下 GC ROOTS 能够关联到的对象,速度很快
2、并发标记
遍历整个GC Roots Tracing 对象图
3、重新标记
为了修订并发标记期间因用户程序继续运行而导致变动的那部分对象
4、并发清除
不需要停顿掉用户线程。删除标记阶段判断已经死亡的对象
缺点:1、吞吐量低;
2、无法处理浮动垃圾,并发清除阶段因用户进程继续运行而导致变动的对象;
3、标记 - 清除算法导致空间内存碎片化;
G1(4)
G1 可以将新生代和老年代中的对象一起回收,G1 把堆分成大小相等的独立区域,
通过记录每个 独立区域 的垃圾回收时间和回收所获得的内存大小,维护一个优先列表,优先回收价值最大的 Region
每个 Region 都有一个 Remembered Set ,用来记录引用此对象的 Region 对象。
1、初始标记
2、并发标记
3、最终标记
修正在并发标记期间因用户继续运行而导致变动的那部分对象
4、筛选回收
对每个 Region 的回收时间和回收所获得的空间进行排序,优先回收价值大的 Region
优点:1、并行与并发;
2、分代收集:G1可以对整个GC堆进行收集,保留了逻辑上的新生代和老年代;
3、空间整合:整体来看采用 ”标记-整理“算法,两个 Region 使用复制算法;
4、可预测的停顿:用户线程不执行,根据允许的时间,优先回收价值最大的Region
缺点:1、垃圾收集产生的内存占用和程序运行时的额外负载都比CMS更高;
2、从经验上看,在小内存上CMS的表现大概率会优先于G1,而G1在大内存应用上发挥其优势
以下情况G1比CMS更好
超过50%的Java堆被活动数据占用
对象的提升频率或年代提升频率变化很大
GC停顿时间过长(长至0.5-1s)