JVM------GC

1、什么是GC?为什么GC?在哪GC?

程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。(这里肯定不需要什么GC了用完就直接弹出去)
而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。

通过上面这段引用我们可以得出:

为什么GC:当然是因为现在还没有无限的内存

什么是GC:就是对JVM内存中一些不用的对象、数组、类、变量、常量等进行回收。(以下主要讲的是针对实例对象的GC,其他的GC与对象GC略有不同)

在哪GC: 方法区(静态变量、常量、类信息、运行时常量池)、堆区(实例、数组)

2、怎么GC

1)判断对象死活:

拓展—死亡对象是什么:

Java虚拟机在进行死亡对象判定时,会经历两个过程。如果对象在进行可达性分析后没有与GC Roots相关联的引用链,则该对象会被JVM进行第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,如果当前对象没有覆盖该方法,或者finalize方法已经被JVM调用过都会被虚拟机判定为“没有必要执行”。如果该对象被判定为没有必要执行,那么该对象将会被放置在一个叫做F-Queue的队列当中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它,在执行过程中JVM可能不会等待该线程执行完毕,因为如果一个对象在finalize方法中执行缓慢,或者发生死循环,将很有可能导致F-Queue队列中其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。如果在finalize方法中该对象重新与引用链上的任何一个对象建立了关联,即该对象连上了任何一个对象的引用链,例如this关键字,那么该对象就会逃脱垃圾回收系统;如果该对象在finalize方法中没有与任何一个对象进行关联操作,那么该对象会被虚拟机进行第二次标记,该对象就会被垃圾回收系统回收。值得注意的是finaliza方法JVM系统只会自动调用一次,如果对象面临下一次回收,它的finalize方法不会被再次执行。

引用计数算法: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。实现简单,判定效率高,但它很难解决对象之间相互循环引用的问题。因此,虚拟机也不是通过引用计数算法来判断对象是否存活的。

public class ReferenceCountingGC{
  public Object object = null;
  
  private static final int OenM = 1024 * 1024;
  private byte[] bigSize = new byte[2 * OneM];
 
  public static void testCG(){
     ReferenceCountingGC objA = new ReferenceCountingGC(); 
      ReferenceCountingGC objB = new ReferenceCountingGC(); 
      
      objA.object = null;
      objB.object = null;
 
     System.gc();
}
}

结果:在上述代码段中,objA与objB互相循环引用,没有结束循环的判断条件,运行结果显示Full GC,就说明当Java虚拟机并不是使用引用计数算法来判断对象是否存活的。

可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达),则证明此对象是不可用的。

可作为GC Roots的对象包括下面几种:

   虚拟机栈中引用的对象;方法去中类静态属性引用的对象;方法区中常量引用的对象;本地方法中Native方法引用的对象。
2)常用垃圾回收算法—垃圾回收器的理论

1、标记 —— 清除算法(Mark-Sweep)

一个个标记,然后一个个清除

缺点:效率低、空间会产生大量碎片、有大对象进来时就存不了了

2、复制算法(Copying)

把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。

缺点:会造成空间利用率低下、因为在新生代中存活区的对象很少,用不了二分之一

改进: 因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。
所以可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和From Survivor 中还存活的对象一次性复制到另一块To Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。

但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:对象达到一定岁数后就进入老年代,每在两个Survivor 区移动一次,就长大一岁。(不同垃圾回收器对进入老年代年龄的规定不同)

模型图如下:
在这里插入图片描述
注:

永久代(permanent generation):
像一些类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池(jdk7之后移出永久代),已确定的符号引用和虚方法表等等,它们几乎都是静态的并且很少被卸载和回收,在JDK8之前的HotSpot虚拟机中,类的这些“永久的”数据存放在一个叫做永久代的区域。永久代一段连续的内存空间,我们在JVM启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小。但是jdk8之后取消了永久代,这些元数据被移到了一个与堆不相连的本地内存区域

3、标记-整理算法(Mark-Compart)

把小垃圾碎片挪动到一起、标记–》移动–》检查–》再标记–》清除

缺点:效率低,

3)垃圾回收器—垃圾回收的实例

在这里插入图片描述
图片上面一排是新生代区的垃圾回收器 —MGC(Minor GC),下面一排是老年代的垃圾回收器 —FGC(Major GC / Full GC)。

Serial

Serial收集器是最基本、发展历史最悠久的收集器。是一个单线程收集器、意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束(这就是stop the word(STW))。

ParNew

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。

应用场景:
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。有一个很重要的原因是除了Serial收集器外,目前只有它能与CMS收集器配合工作。

CMS

CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除 算法实现。(这个回收器是一个里程碑的作业,从这开始后面才开始出现并行垃圾回收器)

缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片
在这里插入图片描述

初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象
并发标记(CMS concurrent mark):进行 GC Roots Tracing
重新标记(CMS remark):修正并发标记期间的变动部分
并发清除(CMS concurrent sweep)

Parallel(平行) Scavenge

回收线程与其他线程同步运行得垃圾回收器,不会STW。Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)。
由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器。

G1

面向服务端的垃圾回收器。
优点:并行与并发、分代收集、空间整合、可预测停顿。

运作步骤:

  1. 初始标记(Initial Marking)
  2. 并发标记(Concurrent Marking)
  3. 最终标记(Final Marking)
  4. 筛选回收(Live Data Counting and Evacuation)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值