本文主要介绍Java垃圾回收(Garbage Collection),90%干货,文字颇多,需要耐心一点看。

【对象判断状态算法】

------引用计数法

在创建对象时,为对象创建一个伴生的引用计数器,当有其他对象引用该对象时,将引用计数器的值加1,如果其他对象不再引用该对象就将引用计数器的值减1,所以当引用计数器的值为0时,就代表不再有任何对象引用该对象,就说明该对象已经"死亡",就可以被判定为待回收。

但是这样做是有问题的,在某些情况下,比如:两个对象相互引用时,这两个对象就永远不会被回收。

python采用的是引用计数为主,分代回收和 标记清除 两种机制为辅的策略。

------可达性分析算法

通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

在java语言中,可作为GC Roots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中JNI(即一般所说的Native方法)引用的对象。

【垃圾回收算法】

------标记-清除算法

最早的垃圾回收算法,它是由标记阶段和清除阶段构成的。

标记阶段会给所有的存活对象做上标记,而清除阶段会把没有被标记的死亡对象进行回收。

标记-清除算法执行完成之后会产生大量的不连续内存,这样当程序需要分配一个大对象时,因为没有足够的连续内存而导致需要提前触发一次垃圾回收动作。

------标记-复制算法

将内存分为大小相同的两块区域,每次只使用其中的一块区域,这样在进行垃圾回收时就可以直接将存活的东西复制到新的内存上,然后再把另一块内存全部清理掉。

可以解决内存碎片的问题,内存的可用率大幅降低。

------标记-整理算法

标记-整理算法的后一个阶段不是直接对内存进行清除,而是把所有存活的对象移动到内存的一端,然后把另一端的所有死亡对象全部清除。

【jvm堆的三个区域】

GC(Garbage Collection):即垃圾回收器。

为了高效的回收,jvm将堆分为三个区域

1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小

2.老年代(Old Generation)

3.永久代(Permanent Generation)(保存方法区,已经移除)

在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。

同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)

堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:

新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小

------新生代

新生代内存按照8:1:1的比例分为一个Eden区和S0,S1区。(Survivor)

正常情况下,大部分对象是在Eden区中生成的,回收的时候,将Eden区内所有的存活对象保存到S0区内,然后清空Eden区。

当S0区也存满的时候,则将Eden区和S0区的存活对象保存到S1区,然后清空Eden区和S0区;然后将S0区和S1区互换。(核心思想就是一直保持S1区域为空)

新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。

新生代垃圾回收的主要目的,就是尽快回收掉那些生命周期比较短的对象。

------老年代

当S1区的空间不足以放下Eden区和S0区的存活对象的时候,就会把对象存放到老年代。

所以,我们可以认为老年代存放的是一些生命周期很长的对象。

如果老年代也满了,就会触发一次Full GC(Major GC + Minor GC),也就是新生代、老年代都进行回收。

Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。

出现Full GC经常会伴随至少一次的Minor GC(不是绝对,Parallel Sacvenge收集器就可以选择设置Major GC策略);

Major GC速度一般比Minor GC慢10倍以上。

------Metaspace

用于存放静态文件,如Java类、方法等。

Metaspace包含JVM用于描述应用程序中类和方法的元数据,是由JVM在运行时根据应用程序使用的类来填充的。

【并发垃圾收集/并行垃圾收集】

------并行(Parallel)

指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态;

如ParNew、Parallel Scavenge、Parallel Old;

------并发(Concurrent)

指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行);

用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;

如CMS、G1(也有并行);

【垃圾收集器】

图中,基本上涵盖了主流的垃圾收集器,图中紫色的线,代表他们可以配合使用。

【Serial收集器】

Serial(串行)垃圾收集器,JDK1.3.1前是HotSpot新生代收集的唯一选择。

------特点

针对新生代;

采用复制算法;

单线程收集。(会"Stop The World")

------应用场景

HotSpot在Client模式下默认的新生代收集器。

------优势

简单高效(与其他收集器的单线程相比);

对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率;

在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的

------设置参数

-XX:+UseSerialGC

------Stop TheWorld说明

JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉,即GC停顿;

【ParNew收集器】

ParNew垃圾收集器是Serial收集器的多线程版本。

------特点

除了多线程外,其余的行为、特点和Serial收集器一样

------应用场景

在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;

但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。

------设置参数

-XX:+UseConcMarkSweepGC

指定使用CMS后,会默认使用ParNew作为新生代收集器;

-XX:+UseParNewGC

强制指定使用ParNew;

-XX:ParallelGCThreads

指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;

【Parallel Scavenge收集器】

Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。

------特点

新生代收集器;

采用复制算法;

多线程收集;

CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间;而Parallel Scavenge收集器的目标则是达一个可控制的吞吐量(Throughput)。

------应用场景

高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间;

应用程序运行在具有多个CPU,对暂停时间没有特别高的要求的机器上,即程序主要在后台进行计算,而不需要与用户进行太多交互;

例如:批量处理、订单处理、工资支付、科学计算。

------设置参数

-XX:MaxGCPauseMillis

控制最大垃圾收集停顿时间,大于0的毫秒数;

MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;

因为可能导致垃圾收集发生得更频繁;

-XX:GCTimeRatio

设置垃圾收集时间占总时间的比率,0<n<100的整数;

GCTimeRatio相当于设置吞吐量大小;

-XX:+UseAdptiveSizePolicy

JVM会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略

------吞吐量(Throughput)

CPU用于运行用户代码的时间与CPU总消耗时间的比值;

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

高吞吐量即减少垃圾收集时间,让用户代码获得更长的运行时间;

------垃圾收集器期望的目标(关注点)

停顿时间

停顿时间越短就适合需要与用户交互的程序;

良好的响应速度能提升用户体验;

吞吐量

高吞吐量则可以高效率地利用CPU时间,尽快完成运算的任务;

主要适合在后台计算而不需要太多交互的任务;

覆盖区(Footprint)

在达到前面两个目标的情况下,尽量减少堆的内存空间;

可以获得更好的空间局部性;

【Serial Old收集器】

Serial Old是 Serial收集器的老年代版本

------特点

针对老年代;

采用"标记-整理"算法(还有压缩,Mark-Sweep-Compact);

单线程收集;

------应用场景

主要用于Client模式;

而在Server模式有两大用途:

在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);

作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

【Parallel Old收集器】

Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本

------特点

针对老年代;

采用"标记-整理"算法;

多线程收集

------应用场景

JDK1.6及之后用来代替老年代的Serial Old收集器;

特别是在Server模式,多CPU的情况下;

这样在注重吞吐量以及CPU资源敏感的场景,就有了Parallel Scavenge加Parallel Old收集器的"给力"应用组合

------设置参数

-XX:+UseParallelOldGC

【CMS收集器】

并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器

------特点

针对老年代;

基于"标记-清除"算法(不进行压缩操作,产生内存碎片);

以获取最短回收停顿时间为目标;

并发收集、低停顿;

需要更多的内存

是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

------应用场景

与用户交互较多的场景;

希望系统停顿时间最短,注重服务的响应速度;

以给用户带来较好的体验;

如常见WEB、B/S系统的服务器上的应用

------设置参数

-XX:+UseConcMarkSweepGC

------运作过程

初始标记(CMS initial mark)

仅标记一下GC Roots能直接关联到的对象;

速度很快;

但需要"Stop The World";

并发标记(CMS concurrent mark)

进行GC Roots Tracing的过程;

刚才产生的集合中标记出存活对象;

应用程序也在运行;

并不能保证可以标记出所有的存活对象;

重新标记(CMS remark)

为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;

需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;

采用多线程并行执行来提升效率;

并发清除(CMS concurrent sweep)

回收所有的垃圾对象;

整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作,所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行。

------缺点

对CPU资源非常敏感

并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。

无法处理浮动垃圾

在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;

这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;

也要可以认为CMS所需要的空间比其他垃圾收集器大;

"-XX:CMSInitiatingOccupancyFraction":设置CMS预留内存空间;

可能出现"Concurrent Mode Failure"失败

如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败;

这时JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生;

这样的代价是很大的,所以CMSInitiatingOccupancyFraction不能设置得太大。

产生大量内存碎片

由于CMS基于"标记-清除"算法,清除后不进行压缩操作;
产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。

总体来看,与Parallel Old垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间;

但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间;

【G1】

G1(Garbage-First)是JDK7-u4才推出商用的收集器

------特点

并行与并发

能充分利用多CPU、多核环境下的硬件优势;

可以并行来缩短"Stop The World"停顿时间;

也可以并发让垃圾收集与用户程序同时进行;

分代收集,收集范围包括新生代和老年代

能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;

能够采用不同方式处理不同时期的对象;

虽然保留分代概念,但Java堆的内存布局有很大差别;

将整个堆划分为多个大小相等的独立区域(Region);

新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合;

结合多种垃圾收集算法,空间整合,不产生碎片

从整体看,是基于标记-整理算法;

从局部(两个Region间)看,是基于复制算法;

这是一种类似火车算法的实现;
都不会产生内存碎片,有利于长时间运行;

可预测的停顿:低停顿的同时实现高吞吐量

G1除了追求低停顿处,还能建立可预测的停顿时间模型;

可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;

------应用场景

面向服务端应用,针对具有大内存、多处理器的机器;

最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;

在下面的情况时,使用G1可能比CMS好:

超过50%的Java堆被活动数据占用;

对象分配频率或年代提升频率变化很大;

GC停顿时间过长(长于0.5至1秒)。

------设置参数

-XX:+UseG1GC

指定使用G1收集器;

-XX:InitiatingHeapOccupancyPercent

当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;

-XX:MaxGCPauseMillis

为G1设置暂停时间目标,默认值为200毫秒;

-XX:G1HeapRegionSize

设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region;

------为什么G1收集器可以实现可预测的停顿

可以有计划地避免在Java堆的进行全区域的垃圾收集;

G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;

每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);

这就保证了在有限的时间内可以获取尽可能高的收集效率;

------一个对象被不同区域引用

一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?

无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:

每个Region都有一个对应的Remembered Set;

每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作;

然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象);

如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;

当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set;

就可以保证不进行全局扫描,也不会有遗漏。

------运作过程

初始标记(Initial Marking)

仅标记一下GC Roots能直接关联到的对象;

且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;

需要"Stop The World",但速度很快;

并发标记(Concurrent Marking)

进行GC Roots Tracing的过程;

刚才产生的集合中标记出存活对象;

耗时较长,但应用程序也在运行;

并不能保证可以标记出所有的存活对象;

最终标记(Final Marking)

为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;

上一阶段对象的变化记录在线程的Remembered Set Log;

这里把Remembered Set Log合并到Remembered Set中;

需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;

采用多线程并行执行来提升效率;

筛选回收(Live Data Counting and Evacuation)

首先排序各个Region的回收价值和成本;

然后根据用户期望的GC停顿时间来制定回收计划;

最后按计划回收一些价值高的Region中垃圾对象;

回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;

可以并发进行,降低停顿时间,并增加吞吐量;

【ZGC】

在 ZGC 收集器中没有新生代和老生代的概念,它只有一代。

ZGC 收集器采用的着色指针技术,利用指针中多余的信息位来实现着色标记,并且 ZGC 使用了读屏障来解决 GC 线程和应用线程可能存在的并发(修改对象状态的)问题,从而避免了Stop The World(全局停顿),因此使得 GC 的性能大幅提升。

ZGC 的执行流程和 CMS 比较相似,首先是进行 GC Roots 标记,然后再通过指针进行并发着色标记,之后便是对标记为死亡的对象进行回收(被标记为橘色的对象),最后是重定位,将 GC 之后存活的对象进行移动,以解决内存碎片的问题。

【参考】

https://www.cnblogs.com/cxxjohnson/p/8625713.html

《深入理解Java虚拟机:JVM高级特性与最佳实践》

《Thinking in Java》

Java垃圾回收略略观的更多相关文章

  1. 【转载】Java垃圾回收机制

    原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...

  2. 【转】深入理解 Java 垃圾回收机制

    深入理解 Java 垃圾回收机制   一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  3. 深入理解java垃圾回收机制

    深入理解java垃圾回收机制---- 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...

  4. Java GC系列(2):Java垃圾回收是如何工作的?

    本文由 ImportNew - 伍翀 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 本教程是为了理解基本的Java垃圾回收以及它是如何 ...

  5. Java GC系列(1):Java垃圾回收简介

    本文由 ImportNew - 好好先生 翻译自 javapapers. Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Ja ...

  6. Java垃圾回收介绍(译)

    在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的.与C语言不同的是,在Java中开发者不需要专门为垃圾回收写代码.这是使Java流行的众多特征之一,也帮助了程序员写出了更好的 ...

  7. [牛感悟系列]JAVA(1)理解JAVA垃圾回收

    理解JAVA垃圾回收的好处是什么?满足求知欲是一方面,编写更好的JAVA应用是另外一方面. 如果一个人对垃圾回收过程感兴趣,那表明他在应用程序开发领域有相当程度的经验.如果一个人在思考如何选择正确的垃 ...

  8. [译]GC专家系列2:Java 垃圾回收的监控

    原文链接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 这是"成为GC专家系 ...

  9. java 垃圾回收(堆内存)、以及栈内存的释放

    一.Java的垃圾回收机制———解疑 Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间. 需要注意的是:垃圾回收回收的是无任何引用的 ...

随机推荐

  1. 新老版本vue-cli的安装及创建项目等方式的比较

    vue-cli 3.0 正式版于2018年8月发布,截至到2020年08月05日版本已经更新到4.4.6.Vue CLI 的包名称由 vue-cli 改成了 @vue/cli,目前网上很多的Vue项目 ...

  2. 学会这些Python美图技巧,就等着女朋友夸你吧

    一.前言 Python中有许多用于图像处理的库,像是Pillow,或者是OpenCV.而很多时候感觉学完了这些图像处理模块没有什么用,其实只是你不知道怎么用罢了.今天就给大家带了一些美图技巧,让你的图 ...

  3. 极简 Node.js 入门 - 1.3 调试

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  4. 前端面试 vue 部分 (3)——v-show和v-if的区别

    v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景: v-show 则适用于需要非常频繁切换条件的场景. v-if 是条件渲染,如果在初始渲染时条件为假,则什么也不做--直到条件第一次变为 ...

  5. Java indexof

    java中字符串方法 indexof() indexof()可以返回输入的字符串在目标字符串中第一次出现的位置,如果没有出现返回int 0:

  6. troubleshoot之:使用JFR解决内存泄露

    目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的 ...

  7. go语言gRPC系列(二) - 为gRPC添加证书

    1. 前言 2. 生成自签证书 2.1 MAC生成自签证书的教程链接: 2.2 Windows生成自签证书的教程 3. 改造服务端使用自签证书 3.1 复制证书至代码下 3.2 改造代码添加证书认证 ...

  8. IDEA使用教程(上)

     一.介绍 IDEA全称IntelliJ IDEA,是java语言开发的集成环境.idea提倡的是智能编码,目的是减少程序员的工作,其特色功能有智能的选取.丰富的导航模式.历史记录功能等,最突出的功能 ...

  9. 详解Python Graphql

    前言 很高兴现在接手的项目让我接触到了Python Graphql,百度上对其介绍相对较少也不够全面,几乎没有完整的中文文档,所以这边也借此机会学习一下Graphql. 什么是Graphql呢? Gr ...

  10. IDEA创建聚合项目

    1.新建一个空工程 2.新建Modul New-->Modul 3.选择maven(不选模板,放在工程下) 4.选择依赖 <project xmlns="http://maven ...