【JVM】JVM 垃圾收集器与内存分配策略

2023-11-17

JVM 垃圾收集器与内存分配策略

JVM内存区域可知Java运行时内存的各个区域。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,当方法结束或者线程结束时,内存就会跟着被回收了。

而只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,所以Java堆方法区这两个区域内存的分配和回收时动态的,垃圾收集器也只关注这部分内存的管理。


一.对象存活判断

1.引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时候计数器值为零的对象就是不可能再被使用的。

  • 优点:实现简单,效率高
  • 缺点:无法解决对象相互循环引用的问题——会导致对象的引用虽然存在,但是已经不可能再被使用,却无法被回收。
2.可达性分析算法

通过一系列的称为”GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索走过的路径称为引用链(Reference Chain), 当一个对象到GC Roots不可达(也就是不存在引用链)的时候, 证明对象是不可用的。如下图: Object5、6、7 虽然互有关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象。

image.png

在Java, 可作为GC Roots的对象包括:

  • 在虚拟机栈中引用的对象
  • 在方法区中类静态属性引用的对象
  • 在方法区中常量引用的对象
  • 在本地方法栈中JNI(Native方法)引用的对象
  • Java虚拟机内部的引用
  • 所有被同步锁(synchronized关键字)持有的对象

二.如何回收(垃圾收集算法)

分代收集理论:

  1. 弱分代假说:绝大多数对象都是朝生夕灭的。
  2. 强分代假说:熬过多次垃圾收集的对象就越难以消亡。

Java堆划分为新生代和老生代两个区域。在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。

1.标记-清除算法

分为标记和清除两个阶段先标记出需要回收的对象(可达性分析算法或者引用计数算法),在标记完成后统一回收所有被标记的对象。

缺点:

  • 效率问题,标记和清除效率都不高。
  • 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
    image.png
2.标记-复制算法

将可用内存划分为大小相等的两块,每次只使用其中的一块。当这块用完了,就将还存活的复制到另一块上,然后将已使用过的另一半内存一次性清除。

新生代中的对象98%都是朝生夕死的。将内存分为较大Eden和两个较小的survivor空间。每次使用Eden和其中一个survivor,回收时将存活的对象一次性地复制到另一块survivor中,再清理掉Eden和已用过的survivor。

HotSpot虚拟机Eden与Survivor默认的大小比例为8:1:1。即只让10%的新生代被浪费的,survivor空间不够时,需要依赖其他内存(老年代)进行分配担保,即让对象进入老年代。

优点:

  • 对整个半区进行回收,不会出现空间碎片。
  • 如果内存中多数对象都是可回收,就只需复制少数的存活对象。

缺点:

  • 如果内存中多数对象都是存活的,将产生大量的内存间复制的开销。
  • 可用内存缩减了一半,造成空间浪费。

image.png

3.标记-整理算法

复制在对象存活率较高时效率很低。根据老年代的特点提出该算法。标记过程同标记清除一样,但不是直接对可回收对象进行清理,而是让存活对象朝着一端移动,然后直接清理掉边界以外的内存。

标记-整理与标记-清除的差异在于整理是移动式的回收算法,清除是非移动的。

image.png

根据各年代特点分别采用最适当的GC算法。

在新生代中每次垃圾收集都能发现大批对象已死, 只有少量存活,因此选用标记-复制算法, 只需要复制少量存活对象就可以完成收集。

在老年代因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用**“标记—清理标记—整理”**算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存。即:

  • 新生代:存活率低,使用复制算法
  • 老年代:存活率高,使用“标记-整理”或“标记-清除”算法

三.垃圾收集器

  • 新生代:Serial收集器  ParNew收集器  Parallel Scavenge收集器
  • 老年代:Serial Old收集器  Parallel Old收集器  CMS收集器

image.png

新生代:
1.Serial收集器

标记复制。单线程收集器,只会使用一个处理器或一条收集线程进行垃圾收集,而且在垃圾收集时,必须暂停其他所有工作线程,直到收集结束。优点:简单高效。

image.png

2.ParNew收集器

标记复制。是Serial收集器的多线程并行版本,同时使用多条线程进行垃圾收集,其余与Serial一样。目前唯一能与CMS收集器配合工作。

image.png

3.Parallel Scavenge收集器

标记复制。并行收集的多线程收集器。CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而PS收集器的目的则是达到一个可控制的吞吐量。吞吐量即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间))。

image.png

老年代:
4.Serial Old收集器

标记整理。Serial 收集器的老年代版本,单线程收集器。

5.Parallel Old收集器

标记整理。Parallel Old是Parallel Scavenge的老年代版本,多线程收集器。

6.CMS收集器(Concurrent Mark Sweep并发标记清除)

标记清除。

并发收集,回收停顿时间短。

步骤:

  1. 初始标记:停掉用户其他线程,仅标记GCRoots能直接关联到的对象,速度很快。
  2. 并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,耗时长但不需要停顿用户线程,与垃圾收集线程一起并发执行。
  3. 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。耗时比初始标记长一点,但远低于并发标记。
  4. 并发清除:清理删除掉那些标记阶段判断为死亡的对象,因为标记清楚算法不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。

总体而言,CMS收集器的内存回收过程是与用户线程一起并发执行的。

image.png

缺点:

  • CMS对处理器资源非常敏感。
  • CMS无法处理浮动垃圾(Floating Garbage),可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生。
  • CMS是标记清除,会产生大量碎片空间,对大对象内存分配带来麻烦。
7.G1收集器(Garbage First)

与其他收集器不同,G1把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要扮演新胜达的Eden空间、Survivor空间,或者老年代空间。收集器对不同角色的Region采用不同的策略去处理。优先处理回收价值收益最大的那些Region,也就是Garbage First的由来。

  • 从整体来看:“标记-整理” 算法
  • 从局部(两个Region之间)来看:“复制”算法

四.内存分配与回收策略

堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,如下图所示:

image.png

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的。

  • 新生代GC(Minor GC):发生在新生代的垃圾收集动作,非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC/Full GC):发生在老年代的垃圾收集动作,一般会伴随Minor GC 速度一般比Minor GC慢上10倍以上。

对象的内存分配从大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下也有可能直接分配在老年代中,分配的规则并非百分之百固定的,细节取决于当前使用的是哪一种垃圾收集器的组合,还有虚拟机中与内存相关的参数设置。

1.对象优先分配在Eden

大多数情况下,对象在新生代的Eden区分配,当Eden区没有足够空间时,虚拟机将发起一次Minor GC。

2.大对象直接进入老年代

所谓大对象是指需要大量连续内存空间的对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发GC以获取足够的连续空间来“安置”它们。

3.长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能够被Survivor容纳,将被移动到Survivor空间中,并且对象年龄为1.对象在Survivor区每“熬过”一次Minor GC,年龄就增加一岁,当年龄增加到一定程度(默认为15岁),就将被晋升到老年代中。对象晋升老年代年龄的阈值,可以通过参数设置。

4.动态对象年龄判定

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到要求的年龄

5.空间分配担保

发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果成立,那么Minor GC确保是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管是有风险的,如果小于或者设置不允许冒险,那这时也要进行一次Full GC。

参考
Full GC

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

【JVM】JVM 垃圾收集器与内存分配策略 的相关文章

  • Java - 因内存不足错误而关闭

    关于如何最好地处理这个问题 我听到了非常矛盾的事情 并且陷入了以下困境 OOME 会导致一个线程崩溃 但不会导致整个应用程序崩溃 我需要关闭整个应用程序 但不能 因为线程没有剩余内存 我一直认为最佳实践是让它们离开 这样 JVM 就会死掉
  • 如何从Firebase Firestore实时更新文档中获取修改后的字段或数据? [复制]

    这个问题在这里已经有答案了 我有多个文档 我的问题是我无法获取修改的特定数据 我正在获取完整的文档 db collection employees whereEqualTo OID OID addSnapshotListener new E
  • “java.io.IOException:连接超时”和“SocketTimeoutException:读取超时”之间有什么区别

    如果我设置一个套接字 SoTimeout 并从中读取 当读取时间超过超时限制时 我会收到 SocketTimeoutException 读取超时 这是我的例子中的堆栈 java net SocketTimeoutException Read
  • 获取文件的锁

    我想在对特定文件开始 threo read 时获取文件上的锁定 以便其他应用程序无法读取已锁定的文件并希望在线程终止时释放锁定文件 您可以获得一个FileLock https docs oracle com javase 8 docs ap
  • Java 7 默认语言环境

    我刚刚安装了 jre7 我很惊讶地发现我的默认区域设置现在是 en US 对于jre6 它是de CH 与jre7有什么不同 默认区域设置不再是操作系统之一吗 顺便说一句 我使用的是Windows7 谢谢你的回答 编辑 我已经看到了语言环境
  • Android 中的列表(特别是 RecyclerView 和 CardView)如何工作

    请原谅我问这个问题 但我是 Android 开发新手 尽管我正在尝试了解developer android com 网站上的基础知识 但大多数示例 即使他们说它们是为 Android Studio 构建的 尚未设置为使用 Gradle 因此
  • HAProxy SSL终止+客户端证书验证+curl/java客户端

    我希望使用我自己的自签名证书在 HAProxy 上进行 SSL 终止 并使用我创建的客户端证书验证客户端访问 我通过以下方式创建服务器 也是 CA 证书 openssl genrsa out ca key 1024 openssl req
  • Reactive Spring 不支持 HttpServletRequest 作为 REST 端点中的参数?

    我创建了一个 RestController 如下所示 RestController public class GreetingController RequestMapping value greetings method RequestM
  • FileNotFoundException - Struts2 文件上传

    Strange FileNotFoundException使用Struts2上传文件时 这是 JSP 的一部分
  • 为什么 MOVE CURSOR 在 OS X Mountain Lion 上不显示?

    我正在做一个项目 想看看 Swing 提供的每个光标是什么样子的 public class Test public static void main String args JFrame frame new JFrame frame set
  • 在 MongoDB 和 Apache Solr 之间同步数据的简单方法

    我最近开始使用 MongoDB 和 Apache Solr 我使用 MongoDB 作为数据存储 并且希望 Apache Solr 为我的数据创建索引 以实现应用程序中的搜索功能 经过一些研究 我发现 基本上有两种方法可以在 MongoDB
  • Spring Data JPA:查询如何返回非实体对象或对象列表?

    我在我的项目中使用 Spring Data JPA 我正在演奏数百万张唱片 我有一个要求 我必须获取各种表的数据并构建一个对象 然后将其绘制在 UI 上 现在如何实现我的 Spring 数据存储库 我读到它可以通过命名本机查询来实现 如果指
  • 寻找局部最小值

    下面的代码正确地找到了数组的局部最大值 但未能找到局部最小值 我已经进行了网络搜索 以找到找到最小值的最佳方法 并且根据这些搜索 我认为我正在使用下面的正确方法 但是 在几天的时间里多次检查每一行之后 下面的代码中有一些我仍然没有看到的错误
  • 在 Clojure 中解压缩 zlib 流

    我有一个二进制文件 其内容由zlib compress在Python上 有没有一种简单的方法可以在Clojure中打开和解压缩它 import zlib import json with open data json zlib wb as
  • 我可以限制分布式应用程序发出的请求吗?

    我的应用程序发出 Web 服务请求 提供商处理的请求有最大速率 因此我需要限制它们 当应用程序在单个服务器上运行时 我曾经在应用程序级别执行此操作 一个对象跟踪到目前为止已发出的请求数量 并在当前请求超出允许的最大负载时等待 现在 我们正在
  • Java 的 PriorityQueue 与最小堆有何不同?

    他们为什么命名PriorityQueue如果你不能插入优先级 它看起来与堆非常相似 有什么区别吗 如果没有区别那为什么叫它PriorityQueue而不是堆 默认的PriorityQueue是用Min Heap实现的 即栈顶元素是堆中最小的
  • 记录类名、方法名和行号的性能影响

    我正在我的 java 应用程序中实现日志记录 以便我可以调试应用程序投入生产后可能出现的潜在问题 考虑到在这种情况下 人们不会奢侈地使用 IDE 开发工具 以调试模式运行事物或单步执行完整代码 因此在每条消息中记录类名 方法名和行号将非常有
  • 使用 JFreeChart 为两个系列设置不同的 y 轴

    我正在使用 JFreeChart 使用折线图绘制两个数据系列 XYSeries 复杂的因素是 其中一个数据系列的 y 值通常远高于第二个数据系列的 y 值 假设第一个系列的 y 值约为数百万数量级 而第二个数据系列的 y 值约为数百万数量级
  • 检查应用程序是否在 Android Market 上可用

    给定 Android 应用程序 ID 包名称 如何以编程方式检查该应用程序是否在 Android Market 上可用 例如 com rovio angrybirds 可用 而 com random app ibuilt 不可用 我计划从
  • 将对象从手机共享到 Android Wear

    我创建了一个应用程序 在此应用程序中 您拥有包含 2 个字符串 姓名和年龄 和一个位图 头像 的对象 所有内容都保存到 sqlite 数据库中 现在我希望可以在我的智能手表上访问这些对象 所以我想实现的是你可以去启动 启动应用程序并向左和向

随机推荐

  • python 调用C DLL库 传参(传递数字、字符串、数组(一维、二维)、结构体)

    usr bin env python conding utf 8 from itertools import filterfalse import PySimpleGUI as sg from ctypes import cdll from
  • 如何将tomcat的http访问,改为https方式访问

    在执行以下操作的时候 需要先了解一个证书生成工具 keytool 他是jdk自带的证书管理工具 在jdk bin目录下 可以用来生成自签名证书 导入导出证书 打印证书信息等 整个过程分为两部分 第一部分是证书的生成 第二部分是修改tomca
  • C语言 五子棋游戏(也可n子棋)

    目录 1 创造棋盘 2 玩家下棋 3 电脑下棋 4 判断输赢 赢的判断 也是最难的 行的判断 列的判断 主副对角线的判断 花费时间最长 平局或者继续 最后判断的全部代码 三个文件的代码 头文件的引用及函数的声明 测试部分 函数的实现 一个文
  • 系统架构师—软件架构设计(一)概述、构建、视图、基本架构风格

    1 软件架构概述 从需求分析到软件设计之间的过渡过程称为软件架构 只要软件架构设计好了 整个软件就不会出现坍塌性的错误 即不会崩溃 架构设计就是需求分配 将满足需求的职责分配到组件上 软件架构为软件系统提供了一个结构 行为和属性的高级抽象
  • 【机器学习】python使用支持向量机SVM

    机器学习 python使用支持向量机SVM 准备 数据集 导入SVM模块 步骤 1 读取数据集 2 划分训练样本与测试样本 3 训练SVM分类器 4 计算分类准确率 5 绘制图像 关于SVM的原理知识 在 机器学习 支持向量机中讲过 欲知详
  • 最近火出圈的GPT-4 技术Report出来了,快进来看看逐文对照翻译!

    近期OpenAI发布的GPT 4的效果好得让人惊艳 碾压了之前火到出圈的ChatGPT 通过同步发布的GPT 4 Technical Report一同看看到底发生了什么 No 0 摘要 We report the development o
  • 微服务网关 —— SpringCloud Gateway

    Gateway 简介 Spring Cloud Gateway 基于 Spring 5 Spring Boot 2 和 Project Reactor 等技术 是在 Spring 生态系统之上构建的 API 网关服务 Gateway 旨在提
  • 剑指offer 06 从尾到头打印链表

    题目 从尾到头打印链表 输入一个链表的头节点 从尾到头反过来返回每个节点的值 用数组返回 示例 输入 head 1 3 2 输出 2 3 1 题解一 栈 解法 遍历链表 将所有遍历到的值压入栈中 再利用栈 后进先出 的特性 从尾到头打印链表
  • STM32 进阶教程 1 - micropython 移植

    前言 Python是一种解释型 面向对象 动态数据类型的高级程序设计语言 Python 是一个高层次的结合了解释性 编译性 互动性和面向对象的脚本语言 具有如下特点 1 易于学习 Python有相对较少的关键字 结构简单 和一个明确定义的语
  • ChatGPT Prompting开发实战(五)

    一 如何编写有效的prompt 对于大语言模型来说 编写出有效的prompt能够帮助模型更好地理解用户的意图 intents 生成针对用户提问来说是有效的答案 避免用户与模型之间来来回回对话多次但是用户不能从LLM那里得到有意义的反馈 本文
  • outbound和inbound关系

    Inbound PCI域訪问存储器域 Outbound 存储器域訪问PCI域 RC訪问EP RC存储器域 gt outbound gt RC PCI域 gt EP PCI域 gt inbound gt EP存储器域 EP訪问RC EP存储器
  • python实现主成分估计

    什么是PCA 主成分分析的主要目的是希望用较少的变量去解释原来资料中的大部分变异 将我们手中许多相关性很高的变量转化成彼此相互独立或不相关的变量 通常是选出比原始变量个数少 能解释大部分资料中的变异的几个新变量 即所谓主成分 并用以解释资料
  • python编程基础知识

    python 切片 可以对list对象 如 1 2 3 4 字符串对象 1234 进行切片 使用 str l r str截取索引范围为 l r 索引值可以为负 表示从倒数方向 如 1表示倒数第一项 例 str 123456 str 0 2
  • 微信小程序开发之数据存储 参数传递 数据缓存

    微信小程序开发内测一个月 数据传递的方式很少 经常遇到页面销毁后回传参数的问题 小程序中并没有类似Android的startActivityForResult的方法 也没有类似广播这样的通讯方式 更没有类似eventbus的轮子可用 现在已
  • sql_labs18

    刚拿到题目时一点头绪没有 虽然提示是user agent注入 但没登录之前是看不到有关信息的 之后经过查看知道了两个admin就可以登录上 并且可以查看到user agent信息 判断闭合符 在User Agent字段结束添加单引号 触发报
  • esp32开发板学习

    1 esp32简介 esp32说到底就是一个小型的linux 可以执行我们的代码 尺寸只有一个苹果watch se的大小 可以集成各个物理组件 好像是通过开发板上的引脚来操作的 2 开发板部署python环境 2 1 在pdd花10块钱买了
  • 关闭文件指针不对

    浏览代码时看到下面几行代码 大家看看有啥问题 其中隐含的问题是关闭空的文件指针 所以写了一个测试代码 运行一下 挂了
  • C++模板类重载"<<"未定义错误

    在使用C 的模板类进行编程的时候 重载 lt lt 运算符时 如果定义不当 会出现未定义的情况 错误为LNK2019 这个问题的原因是由于C 的模板编译机制造成的 解决问题的方式是在类中声明 lt lt 运算符时 需要在运算符和参数之间的位
  • 【Python-利用动态二维码传输文件(五)】动态二维码文件发送端开发,使用Tkinter filedialog实现任意格式文件选中,并显示发送状态

    之前四篇文章论证了利用二维码传输文件的可行性 本章使用tkinter开发 动态二维码文件发送端 发送端具备文件选择 开始发送文件 停止发送文件以及显示发送状态的功能 程序界面下 这里下载源码运行 使用tkinter开发动态二维码文件发送端
  • 【JVM】JVM 垃圾收集器与内存分配策略

    JVM 垃圾收集器与内存分配策略 由JVM内存区域可知Java运行时内存的各个区域 其中程序计数器 虚拟机栈 本地方法栈3个区域随线程而生 随线程而灭 当方法结束或者线程结束时 内存就会跟着被回收了 而只有处于运行期间 我们才能知道程序究竟