JVM之垃圾回收
1.什么是垃圾
垃圾是指没有被任何其他对象引用的对象。不及时回收会导致内存溢出。
2.java垃圾回收特点
1.自动内存管理
降低内存泄漏和内存溢出的风险
2.哪些区域重点关心垃圾回收
1.堆和方法区
堆:
频繁回收年轻代
少回收老年代
方法区:基本不回收方法区
3.垃圾回收机制
1.垃圾标注阶段
标注垃圾:垃圾标注阶段主要是为了判断对象是否存活
标注方式
1.引用计数法
2.可达性分析算法
1.引用计数法
引用计数法对每一个对象保存一个整形的值,用来记录对象被引用的次数,对象被引用则加一,引用失效则减一
对象的计数器如果为则证明该对象需要被垃圾回收
优点:
1.简单,便于辨识;回收无延迟
缺点:
1.单独储存计数器,浪费空间
2.每次赋值都需要计算,增加了时间的开销
3.无法处理循环引用的问题
如图
1.myObject1引用第一个对象,myObject2引用第二个对象
2.第一个对象计数器和第二个对象计数器都为1
3.第一个对象里的ref引用第二个对象,则第二个对象的计数器加一为2
4.第二个对象里的ref引用第一个对象,则第一个对象的计数器再加一为2
5.当myObject1和myObject2都失效则第一个和第二个对象的计数器都减一都为1
6.此时两个对象客观没有被引用
7.但两个对象间的ref互相引用使得Gc永远不会将他们标记为垃圾从而造成内存泄漏
2.可达性分析算法
可达性分析算法:也称之为根搜索算法、追踪性垃圾收集
1.作用
可达性分析算法也同样高效简单,但可以解决对象之间相互引用(循环引用)造成的内存泄漏问题
JVM中用的垃圾标记算法就是可达性分析算法
原理
实现思路
1.可达性分析算法找到根对象(GC Roots)的起始点,按照从上到下的方式找到被根对象所连接(引用)的目标对象是否可达
2.内存中存活的对象会被根对象直接和间接的连接着,连接的路径称之为引用链。
3.如果对象没有与任何一个根引用链连接,则说明在不可达,将该对象标记为垃圾。
可以作为根对象(GC Roots)有哪些元素?
1.虚拟机栈中的引用对象
2.本地方法栈的引用对象
3.方法区中静态属性的引用变量
4.方法区常量引用变量
5.同步锁syschronized持有的对象
3.最终判断垃圾是否要被清理还需要最终判断
…流程图
1.当对象不在根对象的引用链上,还需要再进行两次标记判断方可清除真正的垃圾
2.第一次标记
筛选的条件是此对象是否有必要执行finalize()方法。
如果对象没有重写finalize()或者已经执行过了,那么会直接进行第二次标记并直接回收。
3.第二次标记
如果对象重写了finalize()方法,会放入F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
解释:
finalize():对象销毁前的回调方法。
2.垃圾回收阶段
1.垃圾回收阶段算法
①标记-清除算法(Mark-Sweep)
通过垃圾标注算法将垃圾进行标注,将其清除
(这里的清除不是真正的清除,而是当有新的对象加载时对其进行替换)
缺点:由于新对象的大小和老对象不相同,可能会造成很多的内存碎片
②复制法
把内存分为两块相等区域,始终有一块内存为空内存,把可用的内存(正在使用的对象)复制
到另一块未使用内存中去(可使内存连续可用),清除之前的那块区域所有对象。
优点
1.内存连续
2.不用使用标记算法
缺点
1.把内存分为两块,始终有一块浪费(内存开销大)
适用对象:内存中垃圾多有用的对象少,那么这种算法效率很高
在新生代,对常规应用的垃圾回收,一次通常可以回收 70% - 99%
的内存空间。 回收性价比很高。所以现在的商业虚拟机都是用这种
收集算法回收新生代
③标记压缩算法
通过垃圾标记算法把垃圾标记出来,把可用的内存(正在使用的对象)复制到内存的一端,
清理边界外的所有空间。
优点:
消除碎片,使对象可以连续储存
消除了复制算法当中,内存减半的高额代价
缺点
效率较低
修改对象位置,还得修改引用此对象的对象
修改的过程需要暂停应用程序Swt
标记清除算法 | 复制算法 | 标记压缩算法 | |
---|---|---|---|
速率 | 中 | 快 | 慢 |
消耗内存 | 中(因为又碎片) | 大(两块内存) | 小 |
是否移动 | 否 | 否 | 是 |
2.实际收集算法
①分代收集算法
前面讲述的三种垃圾收集算法各有利弊,但各个代生命周期、内存大小等各种特点各不相同,所以根据各个代的不同特点去使用 不同的垃圾回收算法
年轻代:区域相对老年代较小,对象生命周期短、存活率低,回收频繁(可以使用复制算法,y)
老年代:
②增量收集算法
当垃圾回收的时候,系统的所有其他线程都处于挂起状态,用户就会出现卡顿的现象,所以,会实行,每次只清理一部分垃圾
然后就切换用户线程,提高用户的体验,增量收集算法其实仍然是标记清除算法和标记压缩算法。
缺点:由于用户线程和垃圾回收线程频繁的切换,导致切换过程中的内存损耗过大,效率下降
3.垃圾回收相关概念
在默认情况下,通过 System.gc()者 Runtime.getRuntime().gc() 的调用,会
显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
然而 System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用(不能确保立即生效)。
JVM 实现者可以通过 System.gc() 调用来决定 JVM 的 GC 行为。而一般情况下,垃圾回收应该是自动进行的,无须手动触发,否则就太过于麻烦了。在一些
特殊情况下,如我们正在编写一个性能基准,我们可以在运行之间调用
System.gc()。
4.Stop the World
Stop-the-World,简称 STW,指的是 GC 事件发生过程中,会产生应用程序的
停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的
感觉,这个停顿称为 STW。
可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿,为
什么需要停顿所有 Java 执行线程呢?
1.分析工作必须在一个能确保一致性的快照中进行
2.一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
3.如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保
证
4.被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉
5.像是网速不快造成电影卡带一样,所以我们需要减少 STW 的发生。
6.STW 事件和采用哪款 GC 无关,所有的 GC 都有这个事件。
7.越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正
常的工作线程全部停掉。