Java垃圾回收机制(GC)

2023-11-20

一、要回收哪些区域

在JVM内存模型中,有三个是不需要进行垃圾回收的:
程序计数器、JVM栈、本地方法栈。
因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区需要进行GC

二、如何判断对象是否存活

1、引用计数法
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
优点:实现简单,判断效率高
缺点:很难解决对象之间的相互循环引用,所以java语言并没有选用引用计数法管理内存

2、可达性分析算法(根搜索算法)
可达性分析算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
这里写图片描述:
在这里插入图片描述

在Java语言中,可作为GC Roots的对象包括下面几种:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。

三、方法区如何判断是否需要回收

通过可达性分析,那些不可达的对象并不是立即被销毁,他们还有被拯救的机会。
如果要回收一个不可达的对象,要经历两次标记过程。首先是第一次标记,并判断对象是否覆写了 finalize 方法,如果没有覆写,则直接进行第二次标记并被回收。如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。
方法区如何判断是否需要回收
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
① 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
② 加载该类的ClassLoader已经被回收;
③ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

四、常用的垃圾回收算法

1、标记-清除算法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,此算法一般没有虚拟机采用。
优点1:解决了循环引用的问题
优点2:与复制算法相比,不需要对象移动,效率较高,而且还不需要额外的空间
不足1:每个活跃的对象都要进行扫描,而且要扫描两次,效率较低,收集暂停的时间比较长。
不足2:产生不连续的内存碎片
这里写图片描述:
在这里插入图片描述

2、复制算法
将内存分成两块容量大小相等的区域,每次只使用其中一块,当这一块内存用完了,就将所有存活对象复制到另一块内存空间,然后清除前一块内存空间。这样一来就不容易出现内存碎片的问题。
1、复制的代价较高,所以适合新生代,因为新生代的对象存活率较低,需要复制的对象较少;
2、需要双倍的内存空间,而且总是有一块内存空闲,浪费空间
这里写图片描述:
在这里插入图片描述

3、标记-整理算法
思想:在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
不会产生内存碎片,但是依旧移动对象的成本。
这里写图片描述:
在这里插入图片描述

4、分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
这里写图片描述:
在这里插入图片描述

内存被分为下面三个区域:
① 新生代:Enden、form survicor space、to survivor space。
② 老年代
③ 永久代:方法区
新生代的回收算法
包含有Enden、form survicor space、to survivor space三个区,绝大多数最新被创建的对象会被分配到这里,大部分对象在创建之后会变得很快不可达。
① 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
② 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
③ 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
④ 新生代发生的GC也叫做Minor GC,Minor GC发生频率比较高(不一定等Eden区满了才触发)。
老年代的回收算法
① 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
② 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
永久代的回收算法
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区。

五、常见的垃圾收集器

这里写图片描述:
在这里插入图片描述

(1)Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。
(2)Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。
(3)ParNew收集器(停止-复制算法) 
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
(4)Parallel Scavenge收集器(停止-复制算法) 
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。
(5)Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
(6)CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

CMS的垃圾收集过程分为4步:

**1)初始标记:**需要“Stop the World”,初始标记仅仅只是标记一下GC Root能直接关联到的对象,速度很快。
2)并发标记:是主要标记过程,这个标记过程是和用户线程并发执行的。
**3)重新标记:**需要“Stop the World”,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(停顿时间比初始标记长,但比并发标记短得多)。
**4)并发清除:**和用户线程并发执行的,基于标记结果来清理对象。
在这里插入图片描述

那么问题来了,如果在重新标记之前刚好发生了一次MinorGC,会不会导致重新标记阶段Stop the World时间太长?

答:不会的,在并发标记阶段其实还包括了一次并发的预清理阶段,虚拟机会主动等待年轻代发生垃圾回收,这样可以将重新标记对象引用关系的步骤放在并发标记阶段,有效降低重新标记阶段Stop The World的时间。

*CMS垃圾回收器的优缺点分析:
*
优点:CMS以降低垃圾回收的停顿时间为目的,很显然其具有并发收集,停顿时间低的优点。

缺点
对CPU资源非常敏感,因为并发标记和并发清理阶段和用户线程一起运行,当CPU数变小时,性能容易出现问题。
标记-清除方式会产生内存碎片

(7)G1(Garbage-First)收集器:

G1收集器将新生代和老年代取消了,取而代之的是将堆划分为若干的区域,每个区域都可以根据需要扮演新生代的Eden和Survivor区或者老年代空间,仍然属于分代收集器,区域的一部分包含新生代,新生代采用复制算法,老年代采用标记-整理算法。

通过将JVM堆分为一个个的区域(region),G1收集器可以避免在Java堆中进行全区域的垃圾收集。G1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据回收时间来优先回收价值最大的region。

G1收集器的特点:

并行与并发:G1能充分利用多CPU,多核环境下的硬件优势,来缩短Stop the World,是并发的收集器。
分代收集:G1不需要其他收集器就能独立管理整个GC堆,能够采用不同的方式去处理新建对象、存活一段时间的对象和熬过多次GC的对象。
空间整合:G1从整体来看是基于标记-整理算法,从局部(两个Region)上看基于复制算法实现,G1运作期间不会产生内存空间碎片。
可预测的停顿:能够建立可以预测的停顿时间模型,预测停顿时间。
和CMS收集器类似,G1收集器的垃圾回收工作也分为了四个阶段:

初始标记
并发标记
最终标记
筛选回收
其中,筛选回收阶段首先对各个Region的回收价值和成本进行

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java垃圾回收机制(GC) 的相关文章

  • JPA 中的复合键

    我想创建一个具有自动生成的主键的实体 而且还有一个由其他两个字段组成的唯一复合键 我如何在 JPA 中执行此操作 我想这样做是因为主键应该用作另一个表中的外键 并且使其复合并不好 在下面的代码片段中 我需要命令和模型是唯一的 pk当然是主键
  • createImage(int width, int height) 的问题

    我有以下代码 作为游戏的一部分每 10 毫秒运行一次 private void gameRender if dbImage null createImage returns null if GraphicsEnvironment isHea
  • 通过SOCKS代理连接Kafka

    我有一个在 AWS 上运行的 Kafka 集群 我想用标准连接到集群卡夫卡控制台消费者从我的应用程序服务器 应用程序服务器可以通过 SOCKS 代理访问互联网 无需身份验证 如何告诉 Kafka 客户端通过代理进行连接 我尝试了很多事情 包
  • JVisualVM/JConsole 中的 System.gc() 与 GC 按钮

    我目前正在测试处理 XML 模式的概念验证原型 并围绕一个非常消耗内存的树自动机外部库 我已经获得了源代码 构建 我想绘制 真实峰值 堆 随着模式大小的增加 不同运行的内存消耗 使用的指标符合我的目的并且不会影响问题 或者至少是它的合理近似
  • 如何在 Antlr4 中为零参数函数编写语法

    我的函数具有参数语法 如下面的词法分析器和解析器 MyFunctionsLexer g4 lexer grammar MyFunctionsLexer FUNCTION FUNCTION NAME A Za z0 9 DOT COMMA L
  • Integer.parseInt("0x1F60A") 以 NumberformatException 结束

    我尝试从数据库中获取长字符串内的表情符号代码 格式如下 0x1F60A 所以我可以访问代码 但它将是String 起初 我尝试通过执行以下操作来转换变量tv setText beforeEmo getEmijoByUnicode int e
  • 需要使用 joda 进行灵活的日期时间转换

    我想使用 joda 解析电子邮件中的日期时间字符串 不幸的是我得到了各种不同的格式 例如 Wed 19 Jan 2011 12 52 31 0600 Wed 19 Jan 2011 10 15 34 0800 PST Wed 19 Jan
  • 如何使用 Hibernate (EntityManager) 或 JPA 调用 Oracle 函数或过程

    我有一个返回 sys refcursor 的 Oracle 函数 当我使用 Hibernate 调用该函数时 出现以下异常 Hibernate call my function org hibernate exception Generic
  • 如何检测 Java 字符串中的 unicode 字符?

    假设我有一个包含 的字符串 我如何找到所有这些 un icode 字符 我应该测试他们的代码吗 我该怎么做呢 例如 给定字符串 A X 我想将其转换为 AYXY 我想对其他 unicode 字符做同样的事情 并且我不想将它们存储在某种翻译映
  • 套接字的读写如何同步?

    我们创建一个套接字 在套接字的一侧有一个 服务器 在另一侧有一个 客户端 服务器和客户端都可以向套接字写入和读取 这是我的理解 我不明白以下事情 如果服务器从套接字读取数据 它在套接字中是否只看到客户端写入套接字的内容 我的意思是 如果服务
  • 读取电子邮件的文本文件转换为 Javamail MimeMessage

    我有一个电子邮件原始来源的文本文件 直接从 gmail 复制 如果您单击 查看原始文件 您就会看到它 我想读入该文件并将其转换为 MimeMessage 如果您好奇为什么 我设置了 JavaMaildir 并且需要用电子邮件填充它的收件箱以
  • 使用 Mockito 模拟某些方法,但不模拟其他方法

    有没有办法使用 Mockito 模拟类中的某些方法 而不模拟其他方法 例如 在这个 诚然是人为的 Stock我想嘲笑的班级getPrice and getQuantity 返回值 如下面的测试片段所示 但我想要getValue 执行乘法 如
  • 使用架构注册表对 avro 消息进行 Spring 云合约测试

    我正在查看 spring 文档和 spring github 我可以看到一些非常基本的内容examples https github com spring cloud samples spring cloud contract sample
  • 流中的非终结符 forEach() ?

    有时 在处理 Java Stream 时 我发现自己需要一个非终端 forEach 来触发副作用但不终止处理 我怀疑我可以用 map item gt f item 之类的方法来做到这一点 其中方法 f 执行副作用并将项目返回到流中 但这似乎
  • HashMap 值需要不可变吗?

    我知道 HashMap 中的键需要是不可变的 或者至少确保它们的哈希码 hashCode 不会改变或与另一个具有不同状态的对象发生冲突 但是 HashMap中存储的值是否需要与上面相同 为什么或者为什么不 这个想法是能够改变值 例如在其上调
  • QuerySyntaxException:无法找到类

    我正在使用 hql 生成 JunctionManagementListDto 类的实际 Java 对象 但我最终在控制台上出现以下异常 org hibernate hql internal ast QuerySyntaxException
  • 返回 Java 8 中的通用函数接口

    我想写一种函数工厂 它应该是一个函数 以不同的策略作为参数调用一次 它应该返回一个函数 该函数根据参数选择其中一种策略 该参数将由谓词实现 嗯 最好看看condition3为了更好的理解 问题是 它没有编译 我认为因为编译器无法弄清楚函数式
  • Java Swing:需要一个高质量的带有复选框的开发 JTree

    我一直在寻找一个 Tree 实现 其中包含复选框 其中 当您选择一个节点时 树中的所有后继节点都会被自动选择 当您取消选择一个节点时 树中其所有后继节点都会自动取消选择 当已经选择了父节点 并且从其后继之一中删除了选择时 节点颜色将发生变化
  • 在 Google App-Engine JAVA 中将文本转换为字符串,反之亦然

    如何从字符串转换为文本 java lang String to com google appengine api datastore Text 反之亦然 Check Javadoc http code google com appengin
  • 泛型、数组和 ClassCastException

    我想这里一定发生了一些我不知道的微妙事情 考虑以下 public class Foo

随机推荐

  • 重试机制的实现(4m,10m,10m,1h,2h,6h,15h)

    项目场景 由于我们现在所做的项目有有很多的外放接口供代理商调用 但是有些接口的响应并不是实时返回的 此时我们就需要使用回调接口的方式 将信息响应给代理商 在这期间可能会出网络不稳定等其他情况 导致回调接口调用失败 所以需要特定的回调重试机制
  • 数据库系统原理---封锁

    封锁 封锁粒度 MySQL中提供了两种封锁粒度 行级锁和表级锁 应该尽量的只锁定需要修改的那部分数据 而不是所有的资源 锁定的数据量越少 发生锁争用的可能性就越小 系统的并发程度就越高 加锁需要消耗资源 锁的各种操作 包括锁的获取 释放锁
  • org.elasticsearch.bootstrap.StartupException: java.lang.IllegalArgumentExceptio

    这是我转载的一片文章 解决了我的问题 所以记录一下 话不多说 见正文吧 elasticsearch安装踩过的那些坑 坑1 failed to send join request to master 原因 你复制了某个节点的elasticse
  • supervisor系列:5、日志

    supervisor系列 5 日志 文章目录 supervisor系列 5 日志 1 活动日志 1 1 活动日志输出示例 1 2 活动日志等级 1 3 活动日志轮换 2 子进程日志 2 1 捕获模式 supervisord执行的主要任务之一
  • 一文带你从IntelliJ IDEA中一键生成Controller、Service、Dao、Model层代码,真的不看看吗?

    前言 EasyCode插件介绍与安装 简介EasyCode是基于IntelliJ IDEA开发的代码生成插件 支持自定义任意模板 Java html js xml 只要是与数据库相关的代码都可以通过自定义模板来生成 支持数据库类型与java
  • LoadRunner用户验证码和验证码使用脚本

    Action 定义Md5变量用来存放使用md5加密后的字符串 char Md5 33 存放包含用户登录信息的URL char str 174 存放用户账户信息 char str1 92 存放当前时间 int t1 int a char Me
  • 外观设计模式

    外观设计模式 根据实际案例分析外观模式 Facade 示例 业务与设计模式落地案例 根据实际案例分析外观模式 Facade 案例 假设一个放电影功能 在播放电影时 需要放下窗帘 打开空调 打开播放机 打开音响 考虑用户观影时间较长时 提供暂
  • frp实现内网穿透(一) 最简配置

    frp实现内网穿透 一 最简配置 内网环境现在可以说非常的普遍 如果连接控制的内网环境设备的话 就变得非常不方便 像端口映射 dmz主机已经不能满足我们的要 不过好在现在实现内网穿透的工具也很多 在这里就记录下一这款配置简单 功能强大的内网
  • 线性代数-初等行变换与初等行矩阵

    定义 初等行变换 在矩阵的行上进行倍加 倍乘 对换变换 初等行矩阵 在单位矩阵上应用初等行变换得到的矩阵 初等行矩阵乘上矩阵 就相当于在矩阵上实施了对应的初等行变换 以矩阵为例 倍加 将第二行乘2加在第三行上 r3 2 r2 r3 所用的初
  • VMWare安装Windows10出现以下问题

    VMWare虚拟机安装Win10出现 Attempting to start up from 直接上图 选择ISO镜像以后 开启虚拟机出现以下界面 等待一会没有操作以后出现以下界面 解决方法 1 关闭虚拟机电源 一定要关闭 2 找到该虚拟机
  • 阿里巴巴为什么不用 ZooKeeper 做服务发现?

    原文地址 https yq aliyun com articles 599997
  • Git 多个GitHub账号配置和多个Hexo博客地址配置

    文章目录 需求场景 方法 TL DR 非图形界面方法 1 生成密钥 2 配置config 3 添加新的 SSH 密钥 到 SSH agent 4 验证配置 5 配置仓库设置 5 1 删除全局配置 5 2 设置local配置 6 示例 6 1
  • Linux-centos花生壳实现内网穿透

    Linux centos花生壳实现内网穿透 官网教程 1 安装花生壳 下载网址 点击复制就可以复制下载命令了 wget https dl oray com hsk linux phddns 5 2 0 amd64 rpm O phddns
  • 递归寻找第n位数字

    编写递归函数digit n j 返回整数n的从右边开始的第j位数字 首先来看非递归法 只需用n 10 j 1 10即可 include
  • linux连接Xshell

    一 下载和安装Xshell 1 下载Xshell 官网下载地址 有时需要科学上网 https www netsarang com en all downloads 点击下载 教育版 2 安装 和软件安装方式一样 二 Xshell连接linu
  • 快乐数(Java)

    编写一个算法来判断一个数 num 是不是快乐数 快乐数 定义为 对于一个正整数 每一次将该数替换为它每个位置上的数字的平方和 然后重复这个过程直到这个数变为 1 也可能是 无限循环 但始终变不到 1 如果 可以变为 1 那么这个数就是快乐数
  • 瀑布流布局2

    要实现瀑布流布局中让图片先排满第一行再排第二行 你可以使用 CSS 的多列布局 CSS multi column layout 来控制 目前你的布局使用了 column count 和 column gap 来定义列数和列之间的间隙 但这会
  • 记一次ElasticSearch 更改 mapping 字段类型的过程

    我的个人博客 逐步前行STEP 首先 es不支持直接更改mappinng 所以 更改 mapping 实质上是重建索引 操作步骤如下 1 为当前这个索引old index设置一个别名my index curl XPOST localhost
  • 数据类型分类

    分为四种类型
  • Java垃圾回收机制(GC)

    一 要回收哪些区域 在JVM内存模型中 有三个是不需要进行垃圾回收的 程序计数器 JVM栈 本地方法栈 因为它们的生命周期是和线程同步的 随着线程的销毁 它们占用的内存会自动释放 所以只有方法区和堆需要进行GC 二 如何判断对象是否存活 1