GC垃圾回收器

本文深入探讨Java的垃圾回收机制,包括引用计数法与可达性分析法的区别,标记清除、标记复制和标记整理三种垃圾收集算法的工作原理,以及分代机制如何优化垃圾回收效率。同时,介绍了几种常见的垃圾回收器类型,如SerialGC、ParNewGC、ParallelGC、ParallelOldGC、CMSGC和G1GC,并提供了GC调优策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

说到java就不得不说到它的虚拟机和他的垃圾回收机制

使用过C语言的程序员都知道,最麻烦的就是内存的控制

而Java就很好的解决了这个问题,使用自带的GC垃圾回收机制,在大多数的情况下,程序员不用自己考虑内存垃圾的回收问题,Java的内存分配与回收全部由JVM垃圾回收进程自动完成。与C语言不同,Java开发者不需要自己编写代码实现垃圾回收。这是Java深受大家欢迎的众多特性之一,能够帮助程序员更好地编写Java程序。

判断回收的算法

首先要进行垃圾回收需要知道哪些是需要回收的,哪些是不用回收的
我们要做的就是回收哪些不需要的垃圾对象,GC的主要工作地方是在java堆中

分辨对象是否可以回收有两种方法:
1.引用计数法
对象被创建时会有一个计数器,对象增加了一个引用与之相连,则次数加一,如果一个引用关系失效则次数减一,到零的时候就是垃圾对象,可以被回收

缺点:对象之间相互调用的话就不会被视为垃圾对象,比如a对象调用b对象,b对象调用a对象,其实这两个对象都是垃圾对象,但是回收器并不会对这两个对象进行回收

2.可达性分析法
通过一种叫GC ROOTS的对象作为起始点,从这个点向下搜索,搜索的路径形成一个引用链,如果有对象不在这条引用链时,则判为垃圾对象,需要进行回收。一般来说静态对象,常量对象,native方法引用的对象都可以当作GC ROOTS

优点:解决了引用计数法带来的问题,就算是对象之间相互调用,只要不在引用链中,就会被当作垃圾回收

垃圾收集算法

判断完一个对象是否时垃圾对象后并不是直接把他回收,不可能搜索到一个回收一个

需要把虚拟机中所有需要回收的垃圾全部收集起来一起回收

1.标记清除

把所有的需要清理的垃圾标记后直接清除

缺点:会造成大量的内存碎片
优点:简单粗暴

2.标记复制

把内存空间分为两块,所有对象只放在其中一块上面,把所有需要清理的垃圾标记后,把没有被标记的对象放到空的内存块中,然后把原来的内存块内的数据全部清空

优点:解决了标记清除法带来的大量内存碎片的问题
缺点:会有一半的内存空间被浪费

3.标记整理

把所有的需要清理的垃圾标记后,把没有被标记的对象整理到一起,再把被标记的对象清理

优点:解决了标记清除的内存碎片的问题,解决了标记复制带来的资源浪费的问题
缺点:需要大量的计算,耗费cpu资源

分代机制

三种清除方法都各有优势,所以就有了分代机制

内存空间被分为:新生代,老生代

新生代是新创建对象被分配区域,基本来说死亡率很高,所以需要的内存空间相比老生代来说是较小的,GC的频率相对较高

老生代是存储生命周期较长的对象,基本来说默认是在新生代被GC垃圾回收机制回收了15次还没有被回收的对象就会被放入老生代,GC的频率相对较少

由于新生代,老生代的回收频率问题所以这两个地方的回收机制都是不一样的,也就有了分代一说

新生代由于需要经常回收所以不适合标记整理,标记整理相对于标记复制来说比较慢,但是标记复制又耗费内存资源,所以新生代使用的是优化过的标记复制算法,优化过后的标记复制分为三个块,对象创建时只放在其中最大的那一块,垃圾回收时对于存活的对象放入那两个空的其中一个,清空原来的块,由于存活率较低,所以默认分配比率是8:1:1,这样利用率就由原来的百分之五十到现在的百分之九十

老生代由于频率较低则使用的是标记整理算法

常见GC类型

java是自带垃圾回收器的,不用我们自己去写,常见的垃圾回收器有以下几种

1.Serial GC

这玩意可以说是最古老的GC,用的还是单线程的
GC过程中 ,时间长,如果发生Full GC,持续时间更长
吞吐量较低
也就是说程序在GC过程中就卡在哪里了,非常影响用户体验

2.ParNew GC

它是Serial GC的多线程版,新生代并行,老年代串行;新生代复制算法、老年代标记-压缩
相比于Serial GC,它的暂停时间就比较短了

3.Parallel GC

这个是用的多线程类似于ParNew GC,Parallel收集器更关注系统的吞吐量,新生代复制算法、老年代标记-压缩

4.Parallel Old GC

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在JDK 1.6中才开始提供

5.CMS GC

这个也是多线程的,也是现在主流的GC垃圾回收器
力求最低的暂停时间
它把需要时间最长的清理阶段做到了和Application线程并行运行
但是标记阶段还是会暂停,但是相对于其他GC来说它的暂停时间就非常短了

6.G1 GC

G1是目前技术发展的最前沿成果之一

可以说集成了上述所有回收器的优点,速度快,吞吐量大,不会产生内存空间碎片,CMS是会产生大量空间碎片的。

分配大对象时不会因为无法找到连续空间而提前触发下一次GC。

GC调优

频率最高的就是Minor GC,代价最大的是Full GC

所以我们要做的就是减少Full GC 的频率

Full GC 出现的原因有以下几点:

1.年老代(Tenured)被写满

调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在年老代创建对象 。

2.内存空间不足

创建一个对象时没有空间了,或者是没有足够的连续内存空间就会触发,如果Full GC 也没用就会报错了

3.System.gc()被显示调用

垃圾回收不要手动触发,尽量依靠JVM自身的机制

GC调优就是一个不断分析,不断调整的一个过程
通过不断的试验和试错,分析并找到最合适的参数,如果找到了最合适的参数,则将这些参数应用到所有服务器。

JVM调优参数参考

1.针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;

2.年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

3.年轻代和年老代设置多大才算合理

更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的耗时;小的年老代会导致更频繁的Full GC
更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
   如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。
   在抉择时应该根据以下两点:
   (1)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。
(2)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间。

4.在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC 。

5.线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。
   理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值