前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何优化垃圾回收机制

如何优化垃圾回收机制

作者头像
小土豆Yuki
发布2021-01-18 11:36:05
4660
发布2021-01-18 11:36:05
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

我们知道之所以java比较容易上手,很大的原因是由于我们不需要关注对象的回收和释放,可以减少不少的工作量,但是完全交由虚拟机回收,也会带来回收性的不确定性。

面对不同的业务场景,垃圾回收的调优策略也是不一样的,例如在内存要求苛刻的情况下,需要提高回收策略,在CPU使用率高的情况下,需要降低高并发量时垃圾回收的频率。所以垃圾回收调优是一项必备技能

垃圾回收机制

首先,我们要弄明白三件事

  1. 回收发生在哪里
  2. 对象什么时候回收
  3. 如何回收这些对象

回收发生在哪里

JVM内存模型中,程序计数器,虚拟机栈和本地方法栈这个三个区域是线程私有的,随着线程的创建而创建,销毁而销毁,栈中的帧栈随着方法的进入和退出进行入栈和出栈,每个帧栈中分配多少内存基本是类机构确定下来的时候就已经确定了,因此这三个区域内存的分配和回收是确定的

但是堆和方法区就是垃圾回收的重点,堆中回收的主要是对象的回收,方法区的回收主要是废弃常量和无用的类的回收

对象什么时候可以回收

JVM如何判断一个对象是否可以被回收,一般一个对象不再被引用,就代表这对象可以被回收,目前有两种方式判断对象时候被回收

引用计数器,这种算法是根据对象的引用计数器来判断对象是否被应用,每当对象被引用,引用计数器就会加1,当对象引用时效,引用计数器就减1,当引用计数器值为0时候,就说明该对象不在被引用,可以被回收,虽然此算法简单,但是无法解决循环引用的问题

可达性分析算法,GC Roots是该算法的基础,GC Roots是所有对象的根对象,在JVM加载时候,会创建一些普通对象引用正常对象,这些对象作为正常对象的起始点,在垃圾回收时候,从这个GC Roots开始向下搜索,当一个对象到GC Roots没有任何引用链相连时,就证明这个对象不可应,目前HotSpot虚拟机就是采用这个算法

在JDK1.2之后,java对引用的概念进行了扩展分为了四种

  1. 强引用,被强引用关联的对象永远不会被回收
  2. 软引用,软引用在系统将要发生内存溢出时候,进行回收
  3. 弱引用,只被弱引用关联的对象,只要发生垃圾回收事件,就会被回收
  4. 虚引用,他的唯一作用就是在被回收器回收的时候发出一个系统通知

如何回收对象

了解完回收的条件,那么垃圾回收线程又是如何回收这些对象的,垃圾回收遵循下面两点特性

自动型,Java提供一个系统级的线程来跟踪每一块分配出去的内存,当JVM处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一个空闲的内存块

不可预期性,一旦一个对象没有被引用了,该对象是否立刻被回收呢,答案是不可预期的,因为有可能程序结束后,这个对象扔在内存中。

垃圾收集线程在JVM中是自动执行的,java程序无法强制执行,我们唯一能做的就是调用system.gc方法来建议执行垃圾收集器,但是是否立刻执行,仍然是不可预期的

GC算法

回收算法

优点

缺点

标记-清除

不需要移动对象,简单高效

效率低,GC产生内存碎片

复制

简单高效,不会产生内存碎片

内存使用率低,产生频繁的复制问题

标记-整理

结合上面两种算法优点

仍需移动局部对象

分代收集算法

分区会后

对于长时间存活对象的场景的回收效果不明显,甚至起到分作用

垃圾收集器就是内存回收的具体实现,下面就是具体的垃圾收集器

回收类型

回收算法

特点

Serial New/Serial Old

复制算法/标记-整理

单线程复制回收,简单高效,但会暂停程序导致停顿

ParNew New/ParNew Old

复制算法/标记整理

多线程复制回收,降低停顿时间,但容易增加上下切换

Parallel Scavenge

复制算法

并行回收期,追求高吞吐量,高效利用CPU

CMS

标记-清除

老年代回收期,高并发,低停顿,追求最短GC回收停顿时间,CPU占用比较高,响应时间快,停顿耗时间短

G1

标记-整理+复制算法

高并发,低停顿,可预测停顿时间

GC性能衡量指标

吞吐量:这里吞吐量是指应用程序所花费的时间和系统总运行时间比值,系统总运行时间=应用程序耗时+GC耗时,比如系统运行了100分钟,GC耗时1分钟,吞吐量就是99%,一般吞吐量一般不低于95%

停顿时间:指垃圾收集器正在运行时,应用程序暂停时间,对于串行回收期而言,停顿时间较长,并行回收期,停顿时间较短。但是效率很可能不如串行垃圾收集器,系统的吞吐量也可能降低

垃圾回收效率:通常垃圾回收的频率越低越好,增大对内存空间可以有效降低垃圾回收发生的频率,但是同时意味着堆积的回收对象越多,最终会增加回收时的停顿时间,所以我们只要适当的增大堆内存空间,保证正常的垃圾回收频率即可

GC调用策略

降低Minor GC频率

通常情况下,由于新生代空间较小,Eden区很快填满,就会导致频繁Minor GC,因此增加新生代空间来降低Minor GC的频率

此时我们就有了疑问,扩容Eden区虽然可以降低Minor GC,但是是不是增加了单次Minor GC.

我们知道,单次Minor GC时间是有两部分组成T1(扫描新生代)T2(复制存活对象),假设一个对象的存活对象为500ms,Minor GC的时间间隔是300ms,那么正常情况下,Minor GC时间T1+T2

而当我们增大新生代空间,Minor GC时间间隔会扩大到600ms,此时一个存活的随想就会在Eden会被回收,此时就不存在复制存在对象,所以在发生Minor gc时间就是两次扫描新生代即2T1

可见,扩容后,Minor GC即增加了T1但省去了T2的时间,通常复制对象的成本要远高于扫描成本。

如果在堆内存中存在较多的长期存活的对象,我们扩大新生代的空间,反而会增加Minor GC的时间,如果堆中的短期对象很多,那么扩容新生代空间,单次Minor GC时间不会明显增加,因此单次Minor GC时间更多取决于GC后存活的对象数量,并非Eden区的大小

降低Full GC频率

由于堆内存空间不足或老年代对象太多,会频繁的发生Full GC,因此会带来上下文切换,增加系统的性能开销.我们可以使用下面方式降低Full GC的频率

  1. 减少创建大的对象 平常中我们可能会从数据库中获取一个大的对象,例如插入的对象有60个字段,这种对象如果超过年轻代最大对象的阀值,会被直接分配在老年代,即使创建年轻代,由于年轻代的内存空间有限,通过Minor GC也会进入老年代,这种大对象很容易产生较多的Full GC
  2. 增加堆内存空间 在堆内存不足的时候,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低Full GC频率
  3. 选择合适的GC回收器 如果我们需要操作响应时间必须500ms内需求,就可以选择速度较快的回收期,如CMS和G1就是不错的选择,而当我们需求对吞吐量有要求时候,就可以选择Paraller Scavenge回收期提高系统的吞吐量
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档