1.什么是垃圾回收,什么是垃圾?
(1)Garbage Collection(GC),java进程在启动后会创建垃圾回收线程,来对内存中无用的对象进行回收
(2)垃圾也就是不再使用或不再被引用的对象
2.垃圾回收的时机
(1)创建对象时需要分配内存,如果内存不足就会触发GC
(2)java.lang.Object中有一个finalize() 方法,作用是对对象进行垃圾回收之前,此方法可以标记对象的可回收状态
3.垃圾回收机制:可达性分析算法
通过一系列的称为GC Roots的对象为起始点,从这些节点开始向下搜索,搜索过所走过的路径称为GC Roots 引用链,如果一个对象到GC Roots没有任何引用链相连,就证明此对象是可以回收的。如下图,object5和object6虽然相互引用,但是由于他们到GC Roots都不可达,因此会被判定为可回收的对象。
4.Java的引用类型
- 强引用:
指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。 - 软引用:
用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。 - 弱引用:
用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。 - 虚引用:
称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用(PhantomReference)关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
5.垃圾回收所需的内存分析
5.1:方法区(jdk1.7)/元空间(jdk1.8)
- 方法区在GC中一般称为永久代(Permanent Generation)
- 元空间存在于本地内存,GC也是即对元空间垃圾回收
- 永久代或元空间的垃圾回收主要回收两部分内容:废弃常量和无用的类,此区域的垃圾回收性价比低
5.2:堆
- Java堆是垃圾收集管理的主要区域,也被称为GC堆
- 从内存回收角度来看,现在收集器大多都采用分代收集算法,所以Java对还可细分为:
1.新生代:又可分为Eden区,From Survivor空间,To Survivor空间(Eden区占新生代内存空间80%,两个S区各占10%)
- 新生代的垃圾回收又称为:Young GC(YGC),Minor GC
- 因为Java对象大多都具备朝生夕灭的特性,所以YGC的频率特别高,回收速度也较快
2.老年代(Old Generation,Tenured Generation):
- 老年代垃圾回收又称为Major GC
- 如果出现了Major GC通常(并不是绝对)都伴随着一次Minor GC
- Major GC的速度一般会比Minor GC慢10倍以上
3.Full GC:Full GC:在不同的语义条件下,对Full GC的定义也不同,有时候指老年代的垃圾回收,有时候指全堆(新生代+老代)的垃圾回收,还可能指有用户线程暂停(Stop-The-World)的垃圾回收(如GC日志中)。
class Person{
public static String ok="OK";l
public static Person p=new Person();
}
public class Main{
//方法的调用会产生大量朝生夕灭的对象,结果:
//1.在新生代产生大量可回收的对象
//2.新生代空间不足,需要进行YGC
//3.YGC之后,新生代内存会很多
//4.可能导致Major GC
public static void test(){
//1.类加载:产生类变量和相关对象
//2.创建局部变量p(存在方法栈帧中)
//3.new Person();(在堆上产生一个对象)
//4.产生引用:p指向堆对象
Person p=new Person();
}//销毁main线程该次调用的方法栈帧--->p不存在了--->person对象没有了引用(GC Roots不可达)
public static void main(String[] args){
test();//创建main线程对方法调用的栈帧
//Person.p=null/Person.p=null/其他对象可清除该对象的引用
}
}
6.垃圾回收的过程(基于堆的垃圾回收)
- Eden区空间不足,触发Minor GC,因为用户线程创建的对象首先会在Eden区分配内存,当Eden区空间不足时,就会触发Minor GC:将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间(Survivor区被分成两个模块,s0和s1区,其中必定有一个是空的)。
- 垃圾回收结束后,用户线程又会创建对象并分配在Eden区,如果Eden空间不足,重复上图步骤进行Minor GC
- Minor GC也会出现异常,就是如果留空的s区内存空间不足以存放GC过后的存活对象,该怎么办?
答:存活对象通过分配担保机制进入老年代(分配担保机制类似于银行贷款,如果你需要向银行贷款,银行不会轻易借给你,要看你有没有偿还能力,并且还需要一个担保人,担保人的作用就是当你偿还不了贷款时,可以从担保人的账户中直接扣钱) - 年老的对象直接进入老年代:虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且把对象年龄设为1。对象在Survivor空间中每"熬过"一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将晋 升到老年代中
- 大对象直接进入老年代
- 老年代空间不足,触发Major GC
7.垃圾回收算法
- 标记-清除算法(Mark-Sweep)
1.老年代收集算法
2.分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
3.两个不足:
3.1 :效率问题,标记和清除两个过程的效率都不高
3.2:空间问题,标记清除之后会产生大量不连续的内存碎片,空间 碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
- 复制算法(Copying)
1.新生代的收集算法
2.将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
3.只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
- 标记-整理算法(Mark-Compact)
1.老年代收集算法
2.标记过程仍与"标记-清除"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存
- 分代收集算法
1.当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
2.一般是把Java堆分为新生代和老年代。
3.新生代中98%的对象都是"朝生夕死"的,所以并不需要按照复制算法所要求1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间,每次使用Eden和其中一块Survivor(两个Survivor区域一个称为From区,另一个称为To区域)。HotSpot默认Eden与Survivor的大小比例是8 : 1,也就是说Eden :Survivor From : Survivor To = 8 : 1 : 1。所以每次新生代可用内存空间为整个新生代容量的90%,只有10%的内存会被”浪费“。
4.在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。
8.垃圾收集造成的影响:Stop-The-World(STW:用户线程暂停)
- 原因:用户线程和GC线程并发,并行的执行,垃圾回收首先要进行标记可回收对象,那么在用户线程并发执行的时候,有可能重新有引用指向已标记的对象
- GC时,什么情况下会造成STW?
2.1:新生代GC:都会STW,影响较小,因为Minor GC时间非常快,几乎忽略不计
2.2:老年代GC:根据阶段来看是否会STW,影响非常大,因为老年代空间大,需要回收的对象也多,耗时长
9.垃圾回收器的指标
- 考虑内存区域:新生代【复制算法】,老年代【标记整理算法,标记清除算法】
- 吞吐量,用户体验
1.吞吐量:指标和STW总的时间有关,STW时间越长吞吐量越低,越长吞吐量越好
2.用户体验:指标和STW单词停顿时间有关,越长体验越差,越短体验越好
10.垃圾收集器
- 新生代垃圾收集器
1.Serial:单线程,复制算法,STW
2.ParNew:多线程,复制算法,STW,和老年代的CMS垃圾收集器搭配使用在用户体验优先程序中
3.Parallel Scanvenge
(1)多线程,复制算法,STW
(2)吞吐量优先:主要是后台型任务,不涉及用户使用的程序
(3)自适应调节策略:JVM设置这个参数=true后,JVM可以监控性能,并动态的设置内存相关参数(比如年龄阈值,新生代内存大小,Eden区与S区的比例)
-老年代垃圾收集器
- Serial:
(1)单线程,STW
(2)标记整理算法
(3)作为老年代CMS收集器的后备方案,在CMS并发收集发生Concurrent Mode Failure时使用 - **Parallel Old **:
(1)多线程,STW
(2)标记-整理算法
(3)吞吐量优先,搭配Parallel Scanvenge一起使用 - CMS:
(1)多线程,标记清除算法
(2)用户体验优先
(3)分为四个阶段:
初始标记:标记和GC Roots不能直接相关联的对象,速度很快,STW(以上图左半图来讲,具体标记上图的Object5)
并发标记:GC Roots Tracing(标记Object7)
重新标记:STW,解决第二阶段用户线程并发执行,导致已经标记的对象(可回收)被重新引用(有GC Roots变量指向该对象)
并发清除:并发清除可回收对象
四个阶段中:
1.:1,3执行时间最快—暂停用户线程时间最少
2.:2,4,执行时间较慢,但是可以和用户线程并发执行
3.:总的来说,CMS垃圾回收的工作是和用户线程并发执行的
(4)缺陷:
1.:CMS垃圾回收工作会抢占CPU资源,导致用户线程执行时间变少
2.:浮动垃圾问题
2.1产生原因:CMS第四阶段,用户线程并发执行,又可能导致有对象进入老年代,而老年代剩余空间可能不足,到发生Major GC----Concurrent Mode Failure
2.2解决方案:使用Serial Old进行垃圾回收
2.3标记清除算法会导致老年代产生大量不连续碎片空间,如果进入老年代的对象没有连续空间可以存放,那么会触发Full GC
在清除过程中,会有如下情况:
1.2,6不一定清除掉,因为,垃圾回收线程和用户线程并发执行,可能有引用指向2,6
2.创建对象时,导致有对象进入老年代
3.老年代可能内存不足以分配给进去老年代的对象 - G1:全堆垃圾收集器
(1)整体看,基于标记整理算法,局部看,基于复制算法
(2)用户体验优先
(3)内存划分:堆内存划分为很多region块,每个region都动态指定为Eden区,S区,T区(老年代内存)
(4)实现步骤:
1.初始标记:可以和Minor GC同时执行,STW
2.并发标记:Garbage First,清理老年代区
3.最终标记:STW,和CMS第三阶段算法不同
4.筛选回收:以为和CMS算法不同,采取clean up/copy和新生代类似的整理工作。可以和Minor GC同时执行,STW