当前位置: 首页 > news >正文

JVM(二)——垃圾回收

三、垃圾回收

1、如何判断对象可以回收

1)引用计数法

定义:
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
但是存在循环引用的问题:
在这里插入图片描述

2)可达性分析算法

一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过
程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,
或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的
    参数、局部变量、临时变量等
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如
    NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

3)四种引用

在这里插入图片描述

  1. 强引用:强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object
    obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

  2. 软引用 (SoftReference):在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
    可以配合引用队列来释放软引用自身

  3. 弱引用 (WeakReference):在垃圾回收时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
    可以配合引用队列来释放弱引用自身

  4. 虚引用 (PhantomReference):无法通过虚引用获取一个对象实例。必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存

  5. 终结器引用 (FinalReference):无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象

软引用的例子:
设置堆内存大小为 20m,直接list.add(byte[]),会报 java.lang.OutOfMemoryError: Java heap space 异常,因为是强引用,无法垃圾回收。
使用 SoftReference 可以添加到 list 集合中,不会报错,但是执行到内存不足时,会进行垃圾回收。

/*** 演示软引用* -Xmx20m -XX:+PrintGCDetails -verbose:gc*/
public class Demo2_3 {private static final int _4MB = 4 * 1024 * 1024;public static void main(String[] args) throws IOException {/*List<byte[]> list = new ArrayList<>();for (int i = 0; i < 5; i++) {list.add(new byte[_4MB]);}System.in.read();*/soft();}public static void soft() {// list --> SoftReference --> byte[]List<SoftReference<byte[]>> list = new ArrayList<>();for (int i = 0; i < 5; i++) {SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}System.out.println("循环结束:" + list.size());for (SoftReference<byte[]> ref : list) {System.out.println(ref.get());	// null}}
}

在这里插入图片描述
输出结果中软引用被为null,可以通过引用队列释放软引用自身

/*** 演示软引用, 配合引用队列*/
public class Demo2_4 {private static final int _4MB = 4 * 1024 * 1024;public static void main(String[] args) {List<SoftReference<byte[]>> list = new ArrayList<>();// 引用队列ReferenceQueue<byte[]> queue = new ReferenceQueue<>();for (int i = 0; i < 5; i++) {// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);System.out.println(ref.get());list.add(ref);System.out.println(list.size());}// 从队列中获取无用的 软引用对象,并移除Reference<? extends byte[]> poll = queue.poll();while( poll != null) {list.remove(poll);poll = queue.poll();}System.out.println("===========================");for (SoftReference<byte[]> reference : list) {System.out.println(reference.get());}}
}

在这里插入图片描述

弱引用的例子:

/*** 演示弱引用* -Xmx20m -XX:+PrintGCDetails -verbose:gc*/
public class Demo2_5 {private static final int _4MB = 4 * 1024 * 1024;public static void main(String[] args) {//  list --> WeakReference --> byte[]List<WeakReference<byte[]>> list = new ArrayList<>();for (int i = 0; i < 10; i++) {WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);list.add(ref);for (WeakReference<byte[]> w : list) {System.out.print(w.get()+" ");}System.out.println();}System.out.println("循环结束:" + list.size());}
}

2、垃圾回收算法

1)标记清除(Mark-Sweep)

  • 速度较快
  • 会造成内存碎片

在这里插入图片描述

2)标记整理(Mark-Compact)

  • 速度慢
  • 没有内存碎片

在这里插入图片描述

3)复制(Copy)

  • 不会有内存碎片
  • 需要占用两倍内存空间

在这里插入图片描述

3、分代垃圾回收

在这里插入图片描述

老年代:常时间使用的对象,垃圾回收很久发生一次。单线程的垃圾收集器Serial Old(串行),多线程并发Parallel Old(吞吐量优先)使用的垃圾回收方法是 标记-整理,多线程并发的垃圾收集器CMS(Concurrent Mark Sweep)(响应时间优先)使用的垃圾回收方法是 标记-清除

新生代:经常需要更换的对象,垃圾回收频繁,"朝生夕灭"的特点。垃圾收集器(Serial)使用的方法是 复制

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的
    对象年龄加 1并且交换 from to
  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
  • 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长

1) 相关JVM参数

在这里插入图片描述

4、垃圾回收器

  1. 串行
    • 单线程
    • 堆内存较小,适合个人电脑
  2. 吞吐量优先
    • 多线程
    • 堆内存较大,多核 cpu
    • 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
  3. 响应时间优先
    • 多线程
    • 堆内存较大,多核 cpu
    • 尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5

1)串行

-XX:+UseSerialGC = Serial + SerialOld
在这里插入图片描述

Serial收集器:它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强
调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。它依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器,有着优于其他收集器的地方,那就是简单而高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的。

2)吞吐量优先

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
在这里插入图片描述

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 运行垃圾收集时间)

Parallel Scavenge收集器:老年代的垃圾回收器,支持多线程并发收集,基于标记-整理算法实
现。

3)响应时间优先

-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
在这里插入图片描述

CMS(Concurrent Mark Sweep) 收集器: 是一种低延迟、响应时间优先的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为
关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非
常符合这类应用的需求。

5、G1 (Garbage First)

使用场景:

  • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200ms
  • 超大堆内存,会将堆划分为多个大小相等的 Region
  • 整体上是标记 + 整理算法,两个区域之间是复制算法
    相关 JVM 参数
    -XX:+UseG1GC
    -XX:G1HeapRegionSize=size
    -XX:MaxGCPauseMillis=time

1)G1 垃圾回收阶段

在这里插入图片描述

2)Young Collection

会 STW
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3)Young Collection + CM(并发标记)

  • 在 Young GC 时会进行 GC Root 的初始标记
  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定

-XX:InitiatingHeapOccupancyPercent=percent (默认45%)
在这里插入图片描述

4)Mixed Collection

会对 E(伊甸园区)、S(幸存区)、O(老年代)进行全面垃圾回收

  • 最终标记(Remark)会 STW
  • 拷贝存活(Evacuation)会 STW

-XX:MaxGCPauseMillis=ms
在这里插入图片描述

5)Full GC

  • SerialGC
    • 新生代内存不足发生的垃圾收集 -minor gc
    • 老年代内存不足发生的垃圾 - full gc
  • ParallelGC
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • CMS
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足
  • G1
    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足

6)跨代引用

  • 新生代回收的跨代引用(老年代引用新生代)问题
    在这里插入图片描述
  • 卡表与 Remembered Set
  • 在引用变更时通过 post-write barrier + dirty card queue
  • concurrent refinement threads 更新 Remembered Set

在这里插入图片描述

7)Remark

  • pre-write barrier + satb_mark_queue

重新标记阶段
在垃圾回收时,收集器处理对象的过程中

  • 黑色:已被处理,需要保留的
  • 灰色:正在处理中的
  • 白色:还未处理的
    在这里插入图片描述

6、垃圾调优

C:\Program Files\Java\jdk1.8.0_271\bin\java -XX:+PrintFlagsFinal -version | findstr "GC"
可以根据参数去查询具体的信息

1)调优领域

  • 内存
  • 锁竞争
  • cpu 占用
  • io
  • gc

2)确定目标

低延迟/高吞吐量? 选择合适的GC

  • CMS,G1, ZGC
  • ParallelGC
  • Zing

3) 最快的 GC是不发生 GC

  • 查看 FullGC 前后的内存占用,考虑下面几个问题
    • 数据是不是太多?
      • resultSet = statement.executeQuery(“select * from 大表 limit n”)
    • 数据表示是否太臃肿?
      • 对象图
      • 对象大小 16 Integer 24 int 4
    • 是否存在内存泄漏?
      • static Map map =
      • 第三方缓存实现

4)新生代调优

  • 新生代的特点
    • 所有的 new 操作的内存分配非常廉价
      • TLAB thread-lcoal allocation buffer
    • 死亡对象的回收代价是零
    • 大部分对象用过即死
    • Minor GC 的时间远远低于 Full GC

一般对新生代调优,可以调优的空间大

  • 越大越好吗?
    -Xmn (设置堆空间中新生代的内存大小)
    Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is
    performed in this region more often than in other regions. If the size for the young generation is
    too small, then a lot of minor garbage collections are performed. If the size is too large, then only
    full garbage collections are performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.

回答:新生代的空间越大,吞吐量越大,(Full GC的次数少了),但是空间大到一定程度时,吞吐量会下降,因为空间太大,一旦触发 Full GC 时,垃圾回收时间会大大增加。

  • 新生代能容纳所有【并发量 * (请求-响应)】的数据
  • 幸存区大到能保留【当前活跃对象 + 需要晋升对象】
  • 晋升阈值配置得当,让长时间存活的对象尽快晋升
    -XX:MaxTenuringThreshold=threshold
    -XX:+PrintTenuringDistrubution
    

5)老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
    • -XX:CMSInitiatingOccupancyFraction=percent 值越低,老年代触发垃圾回收的时机越早

6)案例

案例1:Full GC 和 Minor GC 频繁
案例2:请求高峰期发生 Full GC,单次暂停时间特别长(CMS)
案例3:老年代充裕情况下,发生 Full GC(jdk1.7前)

相关文章:

  • 新网站收录时间是多久,新建网站多久被百度收录
  • 沃尔玛百货有限公司 企业网页设计制作 企业html网页成品 跨国公司网页设计开发 web前端开发,html+css网页设计素材,静态html学生网页成品源码
  • BIM自动化简介
  • 主流公链 - Monero
  • 2024年github开源top100中文
  • AI时代Python金融大数据分析实战:ChatGPT让金融大数据分析插上翅膀
  • 【数据分享】1929-2023年全球站点的逐月平均海平面压力(Shp\Excel\免费获取)
  • C# 打印输出以及文件输入输出
  • 在VMware Workstations 中安装windows7并安装vmware tools(解决升级到SP1和VMCI无法安装问题)
  • java复原IP 地址(力扣Leetcode93)
  • 框架结构模态分析/动力时程分析Matlab有限元编程 【Matlab源码+PPT讲义】|梁单元|地震时程动画|结果后处理|地震弹性时程分析| 隐式动力学
  • OpenCV4.9关于矩阵上的掩码操作
  • Stable Diffusion XL之核心基础内容
  • Reactor设计模式和Reactor模型
  • ChatGPT 商业金矿(下)
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 2017-08-04 前端日报
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • ES学习笔记(12)--Symbol
  • Git学习与使用心得(1)—— 初始化
  • Golang-长连接-状态推送
  • java概述
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • MySQL几个简单SQL的优化
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 使用SAX解析XML
  • 算法---两个栈实现一个队列
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 正则学习笔记
  • $L^p$ 调和函数恒为零
  • (1)(1.11) SiK Radio v2(一)
  • (1)常见O(n^2)排序算法解析
  • (C语言)逆序输出字符串
  • (TOJ2804)Even? Odd?
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (十六)一篇文章学会Java的常用API
  • (万字长文)Spring的核心知识尽揽其中
  • (一)基于IDEA的JAVA基础12
  • (转)3D模板阴影原理
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)负载均衡,回话保持,cookie
  • (状压dp)uva 10817 Headmaster's Headache
  • *2 echo、printf、mkdir命令的应用
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .Net CF下精确的计时器
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args