微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

jvm----------比较全的垃圾回收算法及垃圾收集器

一、如何确定是垃圾?

1、引用计数法

对象如果没有与之关联的引用,计数器为0的对象,就是可回收的对象。(目前python就使用)
优点:判定效率高,实现简单。
缺点:不完全准确,无法回收循环引用的对象,容易内存泄漏。

2、可达性分析(根可达)

通过一系列GC Roots的对象作为起始点,从这些根节点开始向下搜,搜索所有走过的路叫做引用连,当一个对象到GC Roots没有任何的引用链相连时,则说明此对象不可用。
优点:解决相互循环引用问题。

注:不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程,可以通过finalize()自救。

3、GC Roots对象

GC Roots对象包括:
虚拟机栈栈帧中本地变量表引用的对象;
方法区中类静态属性引用;
方法区中常量引用的对象;
本地方法栈中JNI引用的对象;

所有被同步锁持有的对象等;
jvm中跨代引用的对象等;
…还有其他的几种,常用的前四种。

二、垃圾回收算法?

java是自动回收内存的,C、c++等都要手工命令回收。

1、复制算法

按内存容量将内存划分为大小相等的两块,每次使用一块,这一块满后,将尚存活的复制到另一块上去,把它使用的内存清空掉。
优点:实现简单,不易产生碎片;
缺点:可用内存被压缩为原来的一半,且存活对象多的话,此算法效率大大降低。

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

标记要回收的对象,回收被标记的对象占用的空间。
缺点:内存碎片化严重;

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

标记要回收的对象,标记后不是清楚标记对象,而是将存活的对象移向内存的一端,然后清楚边界外的对象。
优点:没有碎片化;

4、分代收集算法

目前大部分JVM采用的【新生代、老年代、永久代】
根据对象存活的不同生命周期,将内存划分为不同的域,一般情况下,垃圾回收主要回收堆空间(因为几乎大部分对象都在堆空间,特例逃逸分析:栈上分配),所以将堆划分为新生代(1/3)、老年代(2/3)。

老年代:大对象直接放在老年代;长期存活的对象进入老年代;
每次只有少量对象需要被回收,存活率高,比较稳定。所以老年代选择标记整理算法或者标记清除算法。老年代的垃圾回收叫Major GC

新生代:存放新生对象,对象朝生夕死,大量对象被回收,少量存活所以复制成本低,所以新生代选择复制算法。新生代的垃圾回收叫Minor GC(复制-清空-互换)。

永久代:方法区的永生代,用来存储class类、常量、方法描述等,对永生代的回收主要是废弃的常量和无用的类。垃圾较少,收益一般较小,所以垃圾回收主要回收堆空间。1.8之后叫做元空间

java内存模型

在这里插入图片描述

  • 其中新生代又划分为一个eden区,两个survivor区,默认比例为8:1:1。
  • 新生对象分配在eden区。如果eden区的垃圾经过一次GC幸存,就复制到survivor区,从eden–>survivor 对象年龄+1,survivor–>eden 对象年龄+1,如果最后存活对象的年龄达到15将被移至老年代。
  • 动态年龄:按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。eg:survivor区已经有一半年龄为4的对象了,占用了一半survivor区内存了,也将这些对象移至老年代。

注:Major GC前一般会先进行Minor GC,Minor GC频繁被触发,Major GC不会,无法找到足够大的连续空间分配给新创建的较大对象时,也会提前触发Major GC;Major GC速度一般比Minor GC慢10倍以上;

分代收集算法就是根据不同的区域对象生命周期特点选择不同的回收算法;
垃圾回收主要回收堆内存;
survivor区晋升年龄阈值有两种情况,年龄15或者达到survivor区的50%。

三、垃圾收集器

目前主流的7个:
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;

下面一个个介绍下这些收集器特点及作用:

1、Serial(单线程,复制算法,新生代)

Serial在收集垃圾时,必须暂停其他所有工作线程,直至垃圾回收结束,对于单个cpu来说,没有线程交互的开销,效率高。
因此Serial是java虚拟机运行在Client模式下默认的新生代垃圾收集器。

2、ParNew(Serial的并行的多线程版,复制算法,新生代)

ParNew除了使用多线程进行垃圾回收以外,其他行为和Serial一样,也要暂停所有工作线程。
ParNew默认开启和cpu数目相同的线程,可以通过参数-XX:ParallelGCThreads限制线程数量,是很多虚拟机在Server模式下新生代默认的垃圾收集器。

3、Parallel Scavenge(并行的多线程版,复制算法,新生代)

重点关注程序可达到的一个可控制的吞吐量,有自适应调节策略,提升用户的
体验。是1.8默认的新生代收集器。

吞吐量=cpu运行用户代码时间/cpu总消耗时间 

自适应调节策略:
Parallel Scavenge收集器能够配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或GCTimeRatio参数(更关注吞吐量)给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。

1)、java -XX:+PrintFlagsFinal 可以看到1.8默认的是 UseParallelGC
ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)
** 2)、自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。**

4、Serial Old(单线程,标记整理算法,老年代)

主要运行在Client模式下,java虚拟机默认的老年代垃圾收集器,
在Server模式下两种用途:
①:jdk1.5之前与新生代的Parallel Scavenge搭配使用;
②:作为老年代中CMS收集器的后备垃圾收集方案。

5、Parallel Old(并行的多线程,标记整理算法,老年代)

若同样考虑老年代的吞吐量,可以考虑搭配新生代的Parallel Scavenge使用。

6、CMS(Concurent Mark Sweep)(并发的多线程,标记清除算法、老年代)

主要目的是获取最短垃圾回收停顿时间,可以为交互较高的程序提高用户体验。是目前老年代中唯一一个标记清除算法而不是标记整理算法的垃圾收集器。
缺点:cpu敏感、浮动垃圾、内存碎片

CMS分为四个阶段:
1)初始标记(暂停):只是标记一下GC Roots能直接关联的对象,速度快,暂停所有工作线程;
2)并发标记(并发):进行GC Roots跟踪,和用户线程一起工作;
1)重新标记(暂停):修正并发标记期间,因程序运行导致的标记变动那部分对象的标记,暂停所有工作线程;
1)并发清除(并发):清除GC Roots不可达对象,和用户线程一起工作。

所以总体上看,CMS收集器的内存回收和用户线程是一起并发执行的。eg:web程序,B/S服务。

7、G1(Garbage first)

G1最突出改进:
1)基于标记整理算法,没有碎片;
2)可以非常精准的控制停顿时间,在不牺牲吞吐量的前提下,实现低停顿的回收,让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收上的时间不得超过N毫秒。

G1是避免全区域的垃圾收集,而是将java堆划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。

8、垃圾收集器的搭配

上述7种垃圾收集器的搭配使用,有连接的可以搭配。

注意几个概念:单线程、并行、并发是不一样的。
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态; 如ParNew、Parallel Scavenge、Parallel Old;
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),如CMS、G1;

CMS、G1的并发标记 :采用了三色标记法:分别是白色、灰色和黑色。三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。
并发标记容易产品漏标问题,CMS从根从新扫描,G1快照方式对比差异,具体此处不多延伸。

四、安全点与安全区域

1、安全点

不是在任何时候都可以随便GC的,当系统要进行垃圾回收时,业务线程不是立马停下来的,可想而知立马停下可能会有问题,为了准确安全地回收内存,JVM是在Safe Point点时才进行回收,
就是业务线程去按照某种策略轮询检查这个变量一旦发现是安全点(Safe Point)就主动挂起,那样当JVM达到Safe Point就可以安全准确的GC了。

安全点主要在以下位置设置:
1). 循环的末尾
2). 方法返回前
3). 调用方法的call之后
4). 抛出异常的位置

2、安全区域

若用户线程sleep了等,不能主动检测变量走向安全点,显然JVM也不可能等待程序唤醒,这时候就需要安全区域了。
安全区域是指一段代码片中,引用关系不会发生变化,在这个区域任何地方GC都是安全的,安全区域可以看做是安全点的一个扩展。线程执行到安全区域的代码时,首先标识自己进入了安全区域,这样GC时就不用管进入安全区域的线层了,线程要离开安全区域时就检查JVM是否完成了GC Roots枚举,如果完成就继续执行,如果没有完成就等待直到收到可以安全离开的信号。

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐