java垃圾回收机制

2023-11-16

今天算是对java的gc有了一定的了解。三篇文章做个标记,配合上篇文章来看。

http://www.daniel-journey.com/archives/139

另外推荐三篇很棒的文章

JVM调优总结

Java 6 JVM参数选项大全

一次Java垃圾收集调优实战



GC的概念

      GC是一种自动内存管理程序,与之相对应的是C++采用的内存管理方式。GC主要的职责就是分配内存;保证被引用的对象始终在内存中;把不被应用的对象从内存中释放。被引用的对象称之为Live 对象;不被引用的对象就是Dead对象,是需要回收的。任何事物都有光面和黑暗两面,原因很简单GC是一个很复杂的东西:-)。

      GC会自动计算对象被引用的情况,只要对象不在被引用,相应的内存就会被回收,而C++中需要开发人员通过代码来“显示”地回收内存,如果程序员没有回收就会导致内存的泄露(内存泄露的原因有很多种这只是其中一个)。C++中还有经常出现的一个问题是一个对象在还有其他引用存在的情况下,就被程序给回收了,导致其他引用访问该对象时出现严重错误。另外,GC非常重要的一点就避免内存碎片,道理跟windows的磁盘

整理一样,把使用中各个内存块整合起来,这样才能保证有足够的空间来存储大对象。

理想的GC

一个理想的GC要能够满足以下几点:

·        该回收的回收,不改回收的绝不回收

·        GC要快而且GC运行的时候不能导致应用程序的停顿。

·        限制内存碎片,对象被回收以后,所使用的内存会被回收,如果不加处理内存中就会出现大量的内存碎片,这样就有可能导致因为没有足够的连续空间分配给某些大对象而导致OutofMemory。消除内存碎片的的手段之一就是“内存压缩”。

·        可扩展性(Scalability),内存的分配和回收都不能成为应用程序的瓶颈。

GC的设计选择

·        串行回收(Serial)VS并行回收(Parallel)

串行就是不管有多少个CPU,始终只有一个CPU用来执行回收操作,而并行就是把整个回收工作拆分成多个,由多个CPU同时执行。并行回收执行会快,但复杂度增加,另外也有其他一些副作用,比如内存碎片会增加。

·        并发执行(Concurrent)VS应用程序停止(Stop-the-world)

Stop-the-world的GC方式在执行GC的同时会导致应用程序的暂停。并发执行的GC虽然不会导致应用程序的暂停(这点描述不准确,后面的CMS收集器仍然会造成程序的暂停,虽然时间很短。后面的ITEYE的那个讨论也看得出来,没法阻止应用程序暂停,只是时间而已),但由于并发执行GC要解决和应用程序的执行冲突(应用程序可能会在GC的过称中修改对象),并发执行GC执行的消耗会高于Stop-the-world,而且执行也需要更多的内存堆。

·        压缩(Compacting)VS不压缩(Non-compacting)VS拷贝(Copying)

为了减少内存碎片,支持压缩的GC会把所有的活对象搬迁到一起,然后将之前占用的内存全部回收。不压缩式的GC顾名思义就是在GC的过程中不压缩内存,较之压缩式的GC,不压缩式的GC回收内存快了,而分配内存慢了,而且无法解决内存碎片的问题。拷贝式的GC会将活对象拷贝到不同的内存区域中,这种方式的优点是源数据可以被认为已经清空并可以用来分配,缺点也很明显,需要拷贝数据和额外的内存。

GC的性能评判标准

·        生产力(Throughput

全部时间中不用于GC的比例

·        GC的开销

全部时间中用于GC的比例

·        暂停时间

GC过程中应用程序执行暂停的时间。

·        GC的频率

通过跟应用程序的执行比较来得到GC的执行频率。 

·        支持GC运行所需使用的内存大小

例如heap的大小。

·        GC的及时性(Promptness)

一个对象从被废弃到内存被回收之间的时间差。

Hostspot虚拟机采用了“分代回收”(我注:关于分代的概念,可以去看看上篇问章)的策略,而“分”的非常重要的一个依据就是根据对象存在的时间的长短分成若干个“代(Gerneration)”,每个代上可以采取不同的GC策略。而采用这种“分代回收”策略是利用了2条潜规则,而且这两条潜规则不只限于Java

·        绝大对数的对象不会被长时间引用,这些对象在他的“青年期”就会被回收。

·        几乎不存在很老和很新对象之间的引用

      依据这两条潜规则,Hotspot分离出新生代(Yound Generation)和旧生代(Old Generation)。由于新生代的空间通常都比较小而且可能存在大量不再被引用的对象,所以针对新生代的GC执行频率高、速度快。

在新生代中存在了一定时间还没被GC掉的对象最终会被提升到旧生代。旧生代空间比新生代的要大,但它的占用率增长会比较缓慢,因此,旧生代的GC执行频率低,但需要更长的时间来完成。

对新生代的GC侧重的是速度而且执行频繁,与此相反旧生代的GC侧重的是空间的利用率,即便在旧生代GC频率低的情况下依然要能够正常地工作。

另外还有一个永生代,永生代中的保存的对象都是JVM用来方便管理GC的,例如类和方法对象以及它们的描述对象。

新生代由一个Eden区域和2个survivor空间构成。绝大多数对象先分配到Eden中(有一些大的对象会可能会直接分配到旧生代中),survivor空间中的对象至少经历过一次新生代的GC,所以这些对象在被转移到旧生代之前都先暂且保留在survivor空间中。同一时间两个survivor空间中有一个用来保存对象,而另一个是空的,用来在下次的新生代GC中保存对象。

垃圾回收的种类

当新生代被占用满了,就会运行新生代的垃圾回收(也叫做次要回收)。当永生代或老生代满了以后,就会进行全回收(也叫做主要回收),也就是说所有的代都会被收集。通常对新生代会先被收集,而且使用的收集算法也是特别针对新生代的。然会都对老生代和永生代也进行收集。如果需要进行内存的压缩,每个代都独立地进行压缩。

当出现老生代内容太多,不再能容纳由新生代提升到老生代的内容的情况,所有的收集器(除了CMS收集器)都不再运行新生代的回收算法,相反的,老生代的收集算法会用在整个heap中。CMS收集器由于不回收新生代,所用是个特列。

快速分配

在很多情况下系统中有大量的连续的内存块可以用来分配对象,这种情况下使用bump-the-pointer算法来给对象分配内存是高效的。这种算法会记录前次分配对象的末尾,当有新的对象要分配的时候就会先检查剩余的空间是否内容满足对象的分配,如果可以的话,就会更新指针并且初始化对象。

在一个对线程的应用中,分配操作需要时线程安全的,如果通过全局锁的方式来保证线程安排的话,内存的分配就会成为瓶颈。所以HotSpot采用的是Thread-Local Allocation Buffers(TLABs)。每个thread都会有它自己的buffer,例如代中的一小块。每个TLAB都只有一个thread可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的lock。只有少数不平凡的情况,thread使用的TLAB已经满了需要新的TLAB时才会需要同步操作。

串行收集器

串行收集器对新生代和旧生代的回收都是串行的(只使用一个CPU),而且垃圾回收执行期间应用程序的执行会暂停。

新生代的串行回收

 

上面这张图说明了新生代串行回收的过程。Eden中的活动对象会拷贝到初始为空的Survior空间,也就是下图中的To Survior空间,有些特别巨大的对象无法放入到To Survior空间中会被直接拷贝到老生代中,而Form Survior空间中的活动对象如果想对比较新的话就也会被拷贝到To Survior空间中,而想对比较老的话就会拷贝到老生代中。如果To Survior空间已经满了,Eden和Form Survior空间中的活动对象都会被保留下来。Eden和Form Survior空间中的活动对象在被拷贝以后就都是非活动的状态(下图中红色X标识的内容)。新生代回收以后Eden和FormSurvior空间都会被清空,只有原本空的To空间保存了活动对象。执行以后的效果应该就是下面右边那张图。

老生代的串行回收

对老生代和永生代的回收算法是mark-sweep-compact。这个算法有3个阶段,mark(标识回收对象),sweep(清除),compact(压缩)。在mark阶段收集器会识别出哪些对象仍然是活动的,在sweep阶段就会对垃圾对象进行回收,在compact阶段收集器执行sliding compaction,把活动对象往老生代(永生代也一样)的前端启动,而在尾部保留一块连续的空间,方便在给新对象分配空间的时候执行bump-the-pointer。下面这张图说明的就是老生代在执行垃圾回收前后的状况。

串行回收器的启用

在命令行中加入参数-XX:+UseSerialGC

并行回收器

并行回收器充分利用计算机的CPU提高吞吐量。

对新生代的并行回收

新生代的并行回收算法和串行回收器是一样的,只是增加了并行的能力,新生代的并行回收器仍然是stop-the-world和coping收集器,但通过在多个cpu中并发运行,降低了GC的开销并提升了应用程序的吞吐量。

老生代的并行回收

老生代的并行回收使用的也是串行的mark-sweepcompact回收算法,特别注意的是并行回收器对老生代的回收并没有并行处理的能力,也就是说并行回收器只对新生代并行回收。

并行回收器的启用

在命令行中加入参数-XX:+UseParallelGC

并行压缩回收器(Parallel Compacting Collector)

Parallel Compacting Collector是在J2SE 5.0update 6中引入的,跟并行收集器最大的不同是对老生代的回收使用了不同的算法,并行压缩回收器最终会取代并行收集器。

新生代的并行压缩回收

并行压缩回收器对新生代的回收算法跟并行回收器是一样的。

老生代的并行压缩回收

      并行压缩回收器同样还是会引起stop-the-world效应,并行主要是体现在sliding compaction上。收集器使用了3个阶段,首先每个代在逻辑上都分配成几个固定大小的region(区域)。在Marking阶段,在应用程序中可以直接引用到的活动对象被多个GC线程所划分掉,然后所有的活动对象就可以以并行地方式来实现对它们的标注。当某个Object被鉴定为活动对象的时候,就会更新这个对象所在区块的大小以及该对象位置的信息。

      接下来是summary阶段,summary阶段操作的region而不是对象了。由于每次的GC的压缩都会每一个代的左边的部分区域活动对象密度特别高,保存了多数活动对象。对这样的高密度活动对象的区域进行压缩往往不划算。所以在summary阶段会从最左边的区域开始检验每个区域的密度,当进行到某个区域中能回收的空间达到了某个数值的时候,那么收集器会判定该区域以及该区域右边的那些区域都是值得进行回收的。该区域左边的区域都会被标识成密集,不会有对象移动到这些密集区域去,而该区域和右边的区域之后都会被进行压缩,回收空间的操作。在summary阶段会计算和保存每个活动对象在每个压缩区域的第一个字节的新位置。summary阶段目前还是串行操作,虽然并行是可以实现的,但重要性不如对marking和压缩阶段的并行来的重要。

      最后的压缩阶段,回收器利用summary阶段生成的数据识别出有哪些区域是需要装填的,每个GC线程就可以独立的将数据拷贝到这些区域中。这个过程就会heap在一端很密集而另一端存在大块的空闲块。

并行压缩回收器适合运行在多个CPU的机器上,而且较之并行回收器增强了对老生代的并行回收,减少了系统停顿的时间。

并行压缩回收器的启用

在命令行中加入参数-XX:+UseParallelOldGC

还可以限制并行回收线程的数量–XX:ParallelGCThreads=n

并发Mark-Sweep(标识-清理)收集器(CMS)

      很多应用中更注重快速的相应时间而不是end-to-end的处理能力。对新生代的回收通常不会造成长时间的应用程序中断,而老生代特别当Heap比较大的时候会导致长时间的中断。Hotspot引入CMS的目的就是为了解决这个问题。

CMS的新生代回收

收集的方式和并行回收一致。

CMS的老生代回收

      CMS对老生代的回收多数是并发操作。垃圾收集循环开始的时候需要一个短暂的暂停,称之为初始标识(initial mark ),这个阶段会识别出那些直接被引用的活动对象。然后进入了并发标识阶段(concurrent marking phase),收集器会依据在初始标识中发现的活动对象来寻找活动对象。这个时候应用程序也同时在运行,那就无法保证所有的活动对象都会被识别、标注出来。于是应用程序会再次被暂停,在这个再标识(remark)阶段,收集器会访问在并发标识阶段中被修改过的对象并完成标志。由于再标识阶段较之初始标识更重要,所以会并发运行对个线程来提升效率。

在完成了再标识以后,所有的活动对象都已经被标注出来了。接下来就可以运行并发清理阶段。

    

另外就是CMS是不进行内存压缩的。对象回收以后就会,收集器不会移动活动对象,执行完回收的内存情况就会使下面这张图的效果。

而且由于空闲空间是不连续的,收集器就必须要保存一份可用空间的列表。当需要分配对象的时候,收集器就要通过这份列表找到足够容纳新对象的空间,这就内存分配算法的效果肯定要比之前介绍的几种收集器使用的bump-the-pointer算法性能差。由于老生代的分配效率差了也就影响了新生代回收过程中需要将新生代对象移到老生代的效率。

另外,CMS较之前的几种回收器需要更大的Heap,原因是在标志过程中,应用程序同时在运行,同时在分配对象,因此老生代也同时在增长。此外,虽然活动对象在标示阶段都会被识别出来,但有些在标示阶段成为垃圾的对象并不能同时被回收,只有等到下次收集的时候才能被回收。

最后,由于没有压缩,所以就容易出现内存碎片。为了解决这个问题,CMS会分析通常对象的大小来预估下一步可能的需求,然后可能会对空闲的内存块分割或合并。CMS不会等到老生代满的时候才运行内存回收,而是提早到在老生代满之前就完成内存回收工作。所以CMS会利用之前内存回收的统计数据(收集所需要的时间、老生代被沾满的时间等等)然后选择一个开始回收的时间。CMS在老生代的占用率到达某个阀值的时候也会进行回收。这个阀值可以通过

–XX:CMSInitiatingOccupancyFraction=n来定义,缺省值是68.

总之,CMS较之并发收集器以某些时候稍微增加新生代的回收时间、增加Heap的使用量、减少一些吞吐量为代价减少了老生代回收过程中的停顿时间,而且CMS会要求在应用程序运行过和收集器分享处理器资源。对那些会产生比较大老生代的应用程序而言,如果运行在多处理器上,CMS是一个不错的选择。

CMS回收器的启用

在命令行行中增加-XX:+UseConcMarkSweepGC启用CMS。

增加–XX:+CMSIncrementalMode会然CMS运行在增量模式。所谓的增量模式指的是把收集器的工作分成多个时间块,然后在两次新生代的回收期间加以运行,这种方式可以更进一步减少暂停的时间。

注:http://www.iteye.com/topic/262541

这个讨论很精彩。java的gc在实时大数据处理的时候可能会遇到严重的问题。主要是由于gc中会有stop-whole-world神一样的存在。jvm会暂停程序来进行标记。









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

java垃圾回收机制 的相关文章

随机推荐

  • SQL数据分析概念与基础命令

    Parch Posey 数据库 实体关系图 实体关系图 ERD 是查看数据库中数据的常用方式 下面是我们将用于 Parch Posey 数据库的 ERD 这些图可帮助你可视化正在分析的数据 包括 表的名称 每个表中的列 表配合工作的方式 你
  • 蓝色巨人——IBM公司

    蓝色巨人 IBM之所以被称为蓝色巨人是因为他蓝色的徽标 还有IBM的深蓝超级计算机在第二次人机大战中胜出 IBM是为数不多的在成功逃过数次经济危机 并在历次技术革命中成功转型的公司之一 虽然他是大型计算机制作商 但是他已经过气了 不过他确是
  • 搞懂Vision Transformer 原理和代码,看这篇技术综述就够了(三)

    点击蓝字 关注极市平台 作者丨科技猛兽 来源丨极市平台 审核丨邓富城 极市导读 本文为详细解读Vision Transformer的第三篇 主要解读了两篇关于Transformer在识别任务上的演进的文章 DeiT与VT 它们的共同特点是避
  • 代码查看工具_F12 - 开发者工具详解

    学习使用浏览器自带的 F12 网页开发者工具 可以帮助前端以及测试人员来快速定位调试分析问题 解决问题 一 如何调出开发者工具 在浏览器页面上 F12 键 笔记本电脑 Fn F12 右键选择 检查 N 快捷键 Ctrl Shift i 二
  • Qt 信号和槽的机制(逻辑清晰的来说清楚信号和槽,呕心沥血之作)

    Qt 信号和槽的机制 首先说声对不起 上次在PyQt5中写信号与槽 由于时间原因没有写完 有小伙伴留言说 希望把这章补全 所以 这是一篇迟来的文章 再次向大家说声抱歉 一 桌面程序的结构 Qt的使用场景 主要是应用于桌面程序来使用 不管你使
  • SpringCloud+mybatis+WeMagic Mapper注入失败 NPE空指针异常

    项目背景 Springcloud mybatis webMagic 获取百度热搜榜 搜狗热搜榜等热搜数据并存储到数据库中 使用Mybatis Generator自动生成Mapper后放置在Mapper文件夹 并添加了对应的注解支持 serv
  • 深度学习系列50:苹果m1芯片加速pytorch

    1 介绍 Apple的Metal Performance Shaders MPS 作为PyTorch的后端来加速GPU训练 MPS后端扩展了PyTorch框架 提供了在Mac上设置和运行操作的脚本和功能 MPS通过针对每个Metal GPU
  • 树莓派gpio接ttl转usb串口调试

    树莓派设置修改 以下教程只在树莓派3B 验证测试通过 其它版本未经测试仅供参考 1 gt 修改config txt enable uart 1 找到这行 将值改为1 dtoverlay pi3 miniuart bt 在config txt
  • uni-apph5 端获取当前位置坐标及地理位置逆解析

    1 uni app getLocation在浏览器端获取的地理位置坐标是你电脑里面ip地址位置的坐标 2 调用百度地图api逆解析地址对坐标解析详细地址 代码如下 经纬度 记得改成活的 测试用写死了 uni getLocation type
  • 详解 MySQL InnoDB 实现原理

    MySQL InnoDB 引擎现在广为使用 它提供了事务 行锁 日志等一系列特性 本文分析下 InnoDB 的内部实现机制 MySQL 版本为 5 7 24 操作系统为 Debian 9 1 InnoDB 架构 Innodb 架构图 Inn
  • Java基于Selenium动态抓取页面

    Java基于Selenium动态抓取页面 前情介绍 解决 思路 编码 解决过程中遇到的问题一 解决过程中遇到的问题二 总结 前情介绍 前段时间开发了一个功能 通过HttpClient访问某个页面 获取页面的全部html内容 之后通过抓取过来
  • 【Spring Boot 集成应用】 OAUTH2统一认证单点登录中的各种模式说明

    1 OAUTH2统一认证介绍 OAuth 2 0 是一个行业的标准授权协议 OAuth 2 0 专注于简化客户端开发人员 同时为 Web 应用程序 桌面应用程序 手机等各种设备接入提供特定的授权流程 2 传统登陆认证 传统登陆方式是在每个服
  • python基础笔记(三)_Matplotlib基础语法

    图表绘制工具 Matplotlib 概念 一个python版的matlab绘图接口 以2D为主 支持python numpy pandas基本数据结构 有较丰富的图表库 图表窗口 plt show 直接生成图表 matplotlib inl
  • seate底层原理_Seate

    Seata是阿里开源的一个分布式事务框架 Seata主要有两种分布式事务实现方案 AT及TCC AT模式主要关注多 DB 访问的数据一致性 当然也包括多服务下的多 DB 数据访问一致性问题 TCC 模式主要关注业务拆分 在按照业务横向扩展资
  • Jmeter(三十七) - 从入门到精通进阶篇 - 输出HTML格式的性能测试报告(详解教程)

    1 简介 相对于Loadrunner Jmeter其实也是可以有测试报告产出的 虽然一般都不用 没有Loadrunner的报告那么强大是一方面 但是有小伙伴们私下问 那宏哥还是顺手写一下吧 今天我们就来学习下 如何输入HTML格式的JMet
  • nginx下面完美配置解决404 file not found(让nginx支持PATHINFO路由模式)

    老朱亲自写的 最完美Nginx 配置文件 server listen 80 server name xxxx com if host d d d d return 404 禁IP访问 root var www index index htm
  • C++ 中 operator< 运算符重载来实现 sort 排序的简单理解

    先上代码 Struct Student string id int grade bool operator lt const Student t const if grade t grade return grade gt t grade
  • 面试常见问题

    最失败的案例 复盘经验和反思 有分量的事情 最成功 印象最深的案例 网络架构较为复杂 用户端为移动手持 即无线网络 服务端为固网私有网络 中间为办公网 即服务端网络 办公网 用户端 故障排查 分别在服务端 服务器 网络中转端 防火墙 用户端
  • PLSQL的使用

    目录 1 PLSQLl的安装 配置文件配置教程地址 2 PLSQL建表出现乱问题 Oracle PLSQL 表中字段 注释时为乱码 解决方式 1 PLSQLl的安装 配置文件配置教程地址 https blog csdn net master
  • java垃圾回收机制

    今天算是对java的gc有了一定的了解 三篇文章做个标记 配合上篇文章来看 http www daniel journey com archives 139 另外推荐三篇很棒的文章 JVM调优总结 Java 6 JVM参数选项大全 一次Ja