| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 游戏开发 -> JVM GC -> 正文阅读 |
|
[游戏开发]JVM GC |
文章目录GC 核心概述
Java 自动化内存管理Java 相比 C/C++ 等语言,最大的不同就在于对内存的开辟和释放都是自动化的,这样做的好处是能降低由程序编码疏漏导致的内存泄漏和内存溢出的风险。但自动化同样也带来了缺点,它在一定上弱化了开发人员在程序出现内存泄漏和内存溢出时定位问题和解决问题的能力。 虽然内存的开辟和释放都交由了堆和 GC,但同样的我们也得了解自动化背后的原理,学会如何去监控和调节。 什么是垃圾?看到标题也许你会很奇怪:垃圾还需要定义吗?不就是不使用的对象吗?不被 GC Root 引用的对象吗? 那如果我再具体问:什么是不使用的对象?对象什么时候不被 GC Root 引用的?也许你可能就回答不出来了。 对于垃圾的定义如下:
翻译过来就是,垃圾指的是在程序中没有被任何指针指向的对象,这个对象就是需要被回收的垃圾。 所以我们说 GC 回收垃圾,那回收的其实就是这些没有被任何指针指向的对象。 内存碎片的概念
展现的效果就如上图,一个或多个白色方块就是被回收的对象,蓝色是可回收的对象内存,红色方块是仍然存活的对象内存。 为什么需要 GC?对于系统而言,内存迟早都会被消耗完,因为不断的分配内存空间而不进行回收,就好像不停的产生生活垃圾。 所以 GC 的作用 除了释放垃圾对象,还需要对内存空间进行碎片管理,例如堆中分配给对象的内存,在经过 GC 回收内存后,根据不同的垃圾回收算法,被回收的内存地址需要通过空闲列表管理记录,或者回收后需要整理内存碎片。没有 GC 就不能保证应用程序的正常进行。 GC 相关算法对比垃圾回收相关算法垃圾回收算法分为两个阶段,每个阶段也对应有相应的算法:
上面两个阶段简单理解就是:我要丢垃圾,那肯定得先确定它是垃圾。 特别要注意的是,清除阶段算法中,标记-清除算法、标记-压缩算法在理解时要把 “标记” 两个字去掉,因为标记就是用的可达性分析算法完成的(后面会讲到),所以在它们的实际名称你只要记住是清除算法、压缩算法即可。 标记阶段:引用计数算法引用计算算法的原理是,对每一个对象保存一个整形的引用计数器属性,用于记录对象被引用的情况。 例如,对象 A 只要有任何一个对象引用了,则 A 的引用计数器 +1;当引用失效时,引用计数器 -1;只要对象 A 的引用计数器的值为 0,即标识对象 A 不可能再被使用,可进行回收。 引用计数算法的优点是,实现简单,垃圾对象便于识别且判断效率高。 但同时它又有缺点:
引用计数算法的循环引用问题用下图解释: 标记阶段:可达性分析算法可达性分析算法它以 GC Roots 为起点,Gc Roots 就是一个集合,按照从上至下的方式搜索被 GC Roots 所连接的对象目标是否可达;使用可达性分析算法后,内存中的存活对象会被 GC Roots 直接或者间接连接着,搜索所走过的路径称之为引用链;如果目标对象没有任何引用链,则是不可达的,意味着该对象已经死亡,可以标记为垃圾对象。 在 Java 中可作为 GC Roots 的对象包括:
总的来说,可以作为 GC Roots 的对象就是:这个对象不在堆中,又引用着堆里面的对象,那么它就是 GC Roots。在堆内部的对象引用另一个堆内部的对象,这不算 GC Roots。 清除阶段:标记-清除(Mark-Sweep)算法
标记-清除算法执行过程是,当堆空间中有效内存空间被耗尽时,会进行 STW(Stop The World)行为,然后进行标记清除。 标记即通过可达性分析算法遍历所有被引用的对象确认可回收的垃圾,清除则是对堆内存从头到尾进行线性遍历,如果发现某个对象在 GC Roots 没有被标记为可达,则将其回收。 它的缺点也很明显:
需要注意的是,这里的清除不是抹去内存中的数据,而是本身分配的是一组连续的内存地址给对象使用,清除就是在回收这些内存地址,将它们保存在空闲地址表中,下次有对象需要分配内存时可以从这里提供内存地址。 清除阶段:复制(Copying)算法
复制算法执行过程是,将内存空间分为两块,每次只使用其中一块,在垃圾回收的时候,将正在使用的内存中存活的对象复制到未被使用的内存块中,之后清除正在使用的内存块,交换两个内存角色。 复制算法简单理解就是,将一块内存对半分(假设分为左内存和右内存),真正在使用的只有一半内存,左内存要清除前,将存活的对象按右内存的地址顺序排放后再清除;等右内存满了,就反过来处理交换角色。 它的缺点是:
复制算法的方案适用于垃圾对象较少,量级不大的情况。 针对复制算法的问题同样的也提供了优化方案: 在 Eden 区产生的对象,经过 GC 后存活的对象就会被推到 Survivor 区,图中有两个 Survivor,其实就是 From(S0) 和 To(S1) 区,这两部分内存和复制算法一样交替角色,直到对象年龄到达了老年代的阈值,就会被推到 Old 区。 这种方案也是目前虚拟机对年青代的内存分配方案。 清除阶段:标记-压缩/整理(Mark-Compact)算法
标记-压缩/整理算法的执行过程是,标记阶段它和标记-清除算法一致,通过可达性分析算法标记要回收的内存;然后将所有存活的对象压缩/整理到内存的一段,按照顺序排放,然后清理边界外所有的空间。 简单理解就是,标记-压缩/整理算法相比标记-清除算法标记完后 GC 清除多做了一步,将存活对象的内存整理有序排放。 标记-压缩/整理算法的优劣:
算法性能指标对比
从效率上来说,复制算法最快,但是内存浪费最多。 而为了尽量兼顾上面三个指标,标记-压缩/整理算法相对平滑一些,但是效率上差一些,它比复制算法多了一个标记阶段,比标记-清除算法多了一个整理阶段。 分代收集算法无论是标记-清除算法、标记-压缩/整理算法还是复制算法都有各自的优劣,那是否有最优的回收算法呢?为了满足垃圾回收的效率最优性,分代收集算法应运而生。 分代收集算法基于一个事实:不同的对象生命周期是不一样的,因此,不同生命周期的对象可以采用不同的收集方式,以便于提高回收效率。一般是把堆分为年青代和老年代,这样就可以根据各个年代的特点使用不同回收算法,相对提高效率。
增量收集算法上述所有的垃圾回收算法在垃圾回收过程中,软件都会处于 STW(Stop The World)状态,在 STW 状态下,应用程序所有线程都会挂起暂停一切正常工作等待垃圾回收完成,这种情况将严重影响用户体验或系统稳定,为了解决这个问题,催生出了增量收集算法。 增量收集算法的概念是,如果一次性将所有垃圾进行处理会造成系统长时间停顿,增量收集就是让垃圾收集线程和应用程序线程交替执行,每次垃圾收集线程只手机一小片区域的内存空间,接着切换到应用程序线程,直到垃圾收集完成。 增量收集算法实际上就是对线程间冲突的妥善处理,允许垃圾收集线程分阶段的方式完成标记、清理、复制等工作。 使用这种算法由于在垃圾回收过程中间断性的执行了应用程序代码,虽然能减少停顿时间,但线程切换和上下文切换的消耗会让垃圾回收的总体成本上升,系统吞吐量下降。 分区算法
常用的垃圾回收器垃圾回收的串行与并行&并发
并行(Parallel)指的是多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。 并行&并发:
常用的几种垃圾回收器
根据年青代和老年代两个区域的划分,又可以将上面各个垃圾回收器根据不同的 GC 算法做区分: CMS 回收器CMS 是采用标记-清除算法实现以获取最短回收停顿时间为目标的回收器。它的特点是能够实现在某些阶段与用户线程同时运行一边标记回收。
关于 CMS 回收器的具体算法实现思路是采用的三色标记法,具体实现可以参考下图: 垃圾回收器对比根据上面的常用垃圾回收器,下面整理了它们具体的适用场景: 评估 GC 的性能指标上面介绍了多种 GC 回收器以及简单了解了它们的运行机制。那怎么判定 GC 的性能?有具体的哪些指标? GC 的性能指标如下:
GC 性能调优是空间换时间或时间换空间,不存在完美,一般情况下是抓住吞吐量和暂停时间来设计。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/16 21:14:38- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |