JVM虚拟机——垃圾回收

Java与C++等语言最大的技术区别就是Java有自动化的垃圾回收机制(GC)。

  • :栈中的生命周期跟随线程,无需关注;
  • :堆中的对象是垃圾回收的重点;
  • 方法区/元空间:也会发生垃圾回收,但效率比较低。

判断对象的存活

引用计数法

给对象添加一个引用计数器,当对象增加一个引用时计数器+1,引用失效时计数器-1,引用计数为0的对象可以被回收(python在用,主流的虚拟机没有在用)。
优点:快、方便、实现简单;
缺点:对象互相引用时,很难确定对象是否该回收。

可达性分析(Java中使用)

通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到“GC Roots”没有任何引用链相连时,则证明此对象是不可用的。
作为GC Roots的对象包括下面几种:

  • 当前虚拟机栈中局部变量表中的引用的对象;
  • 当前本地方法栈中局部变量表中的引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中的常量引用的对象。

finalize

finalize虽然可以完成对对象的拯救,但是JVM不保证一定能执行。

引用(Reference)

Reference中存储的数据代表的是另一块内存的起始地址。

强引用

Object obj = new Object(),这就属于强引用。如果有GC Roots的强引用,垃圾收集器宁愿抛出OOM,使程序异常停止,也不会回收强引用对象。

软引用SoftReference

垃圾收集器在内存充足的时候不会回收它,在内存不足时会回收它。可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存,当系统内存不足时,缓存中的内容是可以被释放的。

弱引用WeakReference

垃圾收集器在扫描到该对象时,无论内充足与否,都会回收该对象的内存。所以用弱引用关联的对象,只能活到下一次垃圾回收之前。在WeakHashMap和ThreadLocal中使用过。

虚引用(PhantomReference)

幽灵引用,最弱的引用,被垃圾回收的时候会收到一个通知。和没有任何引用一样。虚引用主要用来跟踪对象被垃圾回收的活动。

GC(Garbage Collection)

Minor GC

特点:发生在新生代上,发生的较频繁,执行速度较快。
触发条件:Eden区空间不足

Full GC

特点:主要发生在老年代上,较少发生,执行速度较慢。
触发条件:老年代空间不足;空间分配担保失败;调用System.gc();JDK1.7及以前的方法区空间不足;

配置的一些参数

-Xms 堆区内存初始内存分配的大小
-Xmx 堆区内存可被分配的最大上限
-XX:+PrintGCDetails 打印GC详情
-XX:+HeapDumpOnOutOfMemoryError 当堆内存空间溢出时输出堆的内存快照 -XX:SurvivorRatio Survivor区和Eden区的比值

PS:GC overhead limit exceeded 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。

垃圾回收算法

复制算法

将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。这一块用完了,就把活着的对象复制到另一块上面,然后再把已使用的内存一次性清理掉,这样一次对半区进行清理,不会产生内存碎片,只需要按顺序分配内存即可。实现简单,运行高效。代价是将内存缩小为原来的一半。
研究表明:新生代的对象90%是“朝生夕死”的,所以一般回收占据10%的空间就够用了。所以将内存分为一块较大的Eden区和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,回收时,将两块区域的活着的对象复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比是8:1,也就是每次新生代可用内存空间为90%,只有10%的浪费。

标记-清除算法

过程:首先标记所有需要回收的对象,然后统一回收被标记的对象。
缺点:效率低,标记和清除效率都不高。标记清除后会产生大量不连续的内存碎片,可能会导致在以后分配大对象时触发另一次垃圾回收。

标记-整理算法

首先标记出所有需要回收的对象,在标记完成后,先让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

垃圾收集器

分代收集

根据各个年代的特点选择不同的垃圾回收算法,新生代使用复制算法,老年代使用标记-清除或标记整理算法。
PS:jps -v显示当前使用的垃圾收集器
新生代,每次垃圾收集时都有大批的对象死去,只有少量存活,用复制算法,只需要付出少量对象的复制成本就可以完成收集。而老年代中对象存活率高,没有额外空间进行分配担保,必须使用“标记-清除”或“标记-整理”算法来进行回收。

各种垃圾收集器

在这里插入图片描述

Serial/Serial Old

最古老的,单线程,独占式的垃圾收集器,适合单CPU的服务器。

ParNew

和Serial基本没区别,唯一的区别:多线程,多CPU的,停顿时间比Serial少。除了性能原因外,主要是因为除了Serial收集器,只有它能与CMS收集器配合工作。

Parallel Scavenge(ParallerGC)/Parallel Old

关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机共运行了100分钟,其中垃圾收集花掉1分钟,吞吐效率就是99%。

Concurrent Mark Sweep (CMS)

此收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器非常适合这类应用。
CMS收集器是基于“标记-清除”算法实现的。

垃圾回收过程
  • 初始标记:仅仅是标记了一下GC Roots能直接关联到的对象,速度很快,需要停顿。
  • 并发标记:从GC Roots开始对堆中的对象进行可达性分析,找到存活对象,在整个回收中耗时最长,不需要停顿。
  • 重新标记:为了修正并发标记期间因为程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿,这个提顿时间比初始标记要长些,但远比并发标记时间短。
  • 并发清除:不需要停顿。

优点
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
缺点
1.CPU资源敏感:因为并发阶段多线程占据CPU资源,如果CPU资源不足,效率会明显降低。
2.浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。在1.6的版本中老年代空间使用率阈值(92%)。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
3.会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片。

G1垃圾收集器
内部布局改变

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
算法:标记-整理(old,humongous)和复制回收算法(survivor)。

GC模式

Young GC:选定所有新生代里的Region。通过控制新生代的region个数,即新生代内存大小,来控制young GC的时间开销。(复制回收算法)
Mixed GC:选定所有新生代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。Mixed GC不是full GC,它只能回收部分老年代的Region。如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。
全局并发标记(global concurrent marking)

  • 初始标记:仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程(STW),但耗时很短。
  • 并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
  • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行。
  • 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

特点
空间整合:不会产生内存碎片。
可预测的停顿
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(根据回收所获得的空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
PS:GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值