JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理

本文对JVM垃圾收集进行说明,包括三种不同算法(标记复制、标记清除、标记整理),以及七种不同的垃圾收集器(Serial,ParNew,Serial Scavenge, CMS, Serial Old, Parallel Old, G1)

持续更新中… …

1. 垃圾回收相关概念

1.1. 垃圾回收对象

说到垃圾收集,首先得确定哪些是可回收的对象,这里涉及到java的四种引用方式,即强、软、弱、虚四类引用。

  • 强引用:即普遍存在的对对象的引用,如指向通过new创建的对象
  • 软引用:即程序运行非必须的对象,仅在程序运行过程中快要发生内存溢出的时候,会对这部分进行垃圾回收
  • 弱引用:弱引用也是非必需的对象,不过与软引用的区别是一旦垃圾回收器发现弱引用的存在,就会将之回收掉。
  • 虚引用:不能通过虚引用获取一个对象实例,虚引用的存在基本只是为了在这个对象被垃圾回收之前触发一次通知事件。

1.2. 拉圾回收区域

前文中已经对运行时数据区域进行了简单的说明,这里要说的是垃圾收集主要涉及的区域,也就是堆区。堆区之所以还要细分为新生代和老年代,是为了垃圾回收的方便,把一些不经常回收或者体积比较大,转移起来比较麻烦的对象放在老年代,把一些经常会被回收的对象放到新生代,然后对不同的区域采用不同的垃圾回收器,更能提高垃圾回收的效率。

1.3. 垃圾回收对象判定

通常情况下,判断一个对象实例是否应该被回收主要使用的是可达性分析算法,即通过GC Roots的引用关系进行遍历,能否到达该对象实例。其中,能够作为GC Roots的对象主要由以下:

  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 虚拟机栈中局部变量表引用的对象
  • 本地方法栈中JNI(Java Native Interface)引用的对象

2. 垃圾收集算法

垃圾回收算法主要分为三种,即标记清除、标记复制、标记整理。

  • 【标记清除(Mark-Sweep)】:正如它的名字一样,标记清除算法就是先对所有需要回收的对象进行标记,然后把这部分给清除掉。这是一个很简单的算法,不过有两个缺点,一个是两个步骤的效率都不太高,另一个则是很明显的会产生大量的内存碎片。标记清除算法的示意图如下:

  • 【标记复制(Mark-Copying)】:标记复制主要是考虑到标记清除算法中会产生大量内存碎片的算法提出来的,具体实现为将内存区域划分为两块,每次都会有一块不会被使用,算法运行过程就是将另一块在使用的内存区中的还存活的对象依次放入那块没有被使用的内存块中,然后对原来那块进行一次性清除。这样的好处在于不会产生内存碎片,而且实现也简单高效。缺点就是因为有一块内存不能使用,内存的利用率就不能达到全部。标记复制算法的示意图如下:

  • 【标记整理(Mark-Compact)】:标记整理考虑到的是内存碎片还有内存利用率两方面的因素,也就是标记之后,对存活的对象进行移动,使得存活的对象都在内存一端,另一端则是空闲内存。标记整理的示意图如下:

3. 垃圾收集器

垃圾收集器目前来看主要有7种(Serial,ParNew,Serial Scavenge, CMS, Serial Old, Parallel Old, G1),从新老生代的划分来看,如图所示:

位于上方Young Generation部分的为新生代收集器,位于下方Tenured Generation部分的为老年代收集器,其中连线关联的收集器代表这两个可以一起协同使用。

3.1. Serial

串行收集器,需要停止所有其他线程开始收集垃圾,对新生代的对象采用标记复制的收集方式,即将Eden区和From Survivor区的存活对象复制到To Survivor区,如果满了就朝来年代复制。

3.2. Serial Old

串行收集器,和Serial类似,不过是对老年代进行收集,对老年代的收集方式为标记整理。

3.3. ParNew

新生代的并行收集器,采用标记复制算法,实际上是Serial的多线程版本,在垃圾回收阶段启用多线程进行收集。

3.4. Parallel Scavenge

新生代的并行收集器,和ParNew类似,也采用了标记复制算法,不过它的关注点在于使CPU运行用户代码时间和总的消耗时间的比值也就是吞吐量达到一个可控的量,既可以控制吞吐量为百分之几,然后让jvm自行确定各类GC以及VM参数

3.5. CMS

CMS全称为Concurrent Mark Sweep收集器,专注于最短停顿时间,即给用户最好的体验。这个收集器总体而言是采用的标记清除算法,总共分为四个步骤:

  • 初始标记:与用户线程不能并行,不过这一阶段仅仅标记GC Roots能关联的对象
  • 并发标记:这一阶段能够和用户线程并发执行,即顺着GC Roots标记做可达性监测
  • 重新标记:这一阶段主要是标记那些在并发标记阶段,由于用户线程继续运行而导致的部分标记发生变化的那一部分,这一阶段也不能和用户线程并发执行。
  • 并发清除:这一阶段与用户线程一同运行,这里采用的是清除策略

从CMS的步骤可以看出,它优点就是减少用户的停顿时间,能够达到好的用户体验度,缺点就是无法处理浮动垃圾,浮动垃圾指在并发清除阶段因为用户线程还在运行所产生的垃圾。因为垃圾收集与用户线程并发执行,所以需要预留一些空间给用户线程使用,不能像其他收集器一样等到内存快满的时候才开始使用,预留的大小通过配置实现,如果预留的空间太小,会造成concurrent mode failure错误,这个时候就会启用serial old收集器重新进行垃圾收集。

除此之外,CMS还有个缺点就是采用的是垃圾清除算法,所以会产生不少内存碎片,可以通过设置让内存整理过程进行到一定次数后进行一次碎片整合,整合过程是不能与用户线程并发的,所以阈值设定也需要根据情况设置。

3.6. Parallel Old

即采用并行线程对老年代进行垃圾回收,采用的标记整理算法。

3.7. G1(Garbage First)

G1收集器与其他收集器有个显著的不同就是,垃圾回收不再是整个新生代或者老年代,它将Java堆内存布局划分成了多个大小相等的Region。G1会跟踪每个Region的垃圾价值,即对这个Region进行回收所获得的空间大小和时间的大小的比值,然后维护一个优先队列,每次根据允许的收集时间,回收一个价值最大的Region。

为了避免各个Region之间互相引用所造成的垃圾回收方面的麻烦,G1的每个Region会有一个Remembered Set,用于存储其他Region对这个Region的对象的引用。内存回收时,枚举GC Roots时也将这一部分加入即可。

G1收集器的步骤如下:

- 初始标记:标记GC Roots关联对象,修改Next Top Mark Start的值,使得用户线程在后续并发执行时能在正确的Region中创建对象。

- 并发标记:和CMS类似,从GC Roots开始进行可达性分析。

- 最终标记:用户线程并发执行过程中会生成Remembered Set Logs,记录对象的变化记录,在最终标记阶段需要把这个记录合并到Remembered Set中。

- 筛选回收:对各个Region的回收价值与成本进行评估,按照用户所希望停顿的GC时间制定回收计划。

4. 参考资料

[1]《深入理解Java虚拟机》第二版,周志明著

[2]GC其他:引用标记-清除、复制、标记-整理的说明

时间: 2025-01-16 12:01:08

JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理的相关文章

JAVA程序员养成计划之JVM学习笔记(3)-JVM性能监控

本文对JVM的性能监控方法做整理. 持续更新中- - 1. JDK命令行工具 1.1. jps:虚拟机进程状况工具 JVM Process Status Tool, 显示系统内所有的HotSpot虚拟机进程,用于查看当前在jvm中运行的程序,包括虚拟机执行主类的名称以及进程ID. 1.2. jstat:虚拟机统计信息监视工具 JVM Statistics Monitoring Tool, 用于收集Hotspot虚拟机各方面的运行数据,包括类装载.内存.垃圾收集.JIT编译等运行数据. 1.3.

黑马程序员_JAVA UDP网络编程学习笔记

一.UDP网络编程概述 采用TCP协议通信时,客户端的Socket必须先与服务器建立连接,连接建立成功后,服务器端也会持有客户端连接的Socket,客户端的Socket与服务器端的Socket是对应的,它们构成了两个端点之间的虚拟通信链路.与TCP通信不同,UDP是面向无连接的.不可靠的基于数据包的传输协议.即应用进程(或程序)在使用UDP协议之前,不必先建立连接.自然,发送数据结束时也没有连接需要释放.因此,减少了开销和发送数据之前的延时.UDP也采用端口来区分进程. 在java中,java.

2020年薪30W的Java程序员都要求熟悉JVM与性能调优!

前言 作为Java程序员,你有没有被JVM伤害过?面试的时候是否碰到过对JVM的灵魂拷问? 一.JVM 内存区域划分 1.程序计数器(线程私有) 程序计数器(Program Counter Register),也有称作为 PC 寄存器.保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当 CPU 需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加 1 或者根据转移指针得到下一条

Java程序员进阶路线-高级java程序员养成

1. 引言 搞Java的弟兄们肯定都想要达到更高的境界,用更少的代码解决更多的问题,用更清晰的结构为可能的传承和维护做准备.想想当初自己摸着石头过河,也看过不少人介绍的学习路线,十多年走过来多少还是有些收获.现通过自身经历总结一篇文章,供弟兄们参考. 2.  用好正在用的框架 在已经加入的团队中,和大家协作使用团队已选好的框架.不管框架优劣与否,特点如何,选择了它必然有一定的道理.并且能够在业界经久流行的框架也一定有它的优秀之处. 使用框架第一步是熟悉,可能通过复制和修改前人的代码来实现新的功能

【转】游戏程序员养成计划

博客出处:www.cnblogs.com/clayman/archive/2009/05/17/1459001.html 作者:clayman 与玩游戏相比,写游戏要复杂上千万倍,除了需要掌握通用的编程技巧以外,还要有相当的图形学,物理,数学基础,特别是在国内,由于相关资料的缺乏,更是让初学者无从下手.下面总结了一些入门方法和比较容易入手的资料. 首先你要精通一门高级语言,pc上游戏的首选语言就是C++.其次,要有良好的英文阅读能力.对游戏开发者来说英文阅读能力是最重要也是最基本的工具之一,因为

Java程序员,这些开源工具必须要学会

前言 本文主要介绍Java程序员应该在2018年学习的一些基本和高级工具.如果你是一位经验丰富的Java开发人员,拥有5到10年的经验,你可能对这些工具很熟悉,但如果不是,现在就是是开始学习这些工具的好时机. Java世界中存在许多工具,从Eclipse,NetBeans和IntelliJ IDEA等著名的IDE开始到Java开发人员应该知道的JVM分析和监视工具,如JConsole,VisualVM,Eclipse Memory Analyzer等. 尽管如此,在本文中,我将重点介绍适用于各种

聊聊阿里社招面试,谈谈“野生”Java程序员学习的道路

阿里社招面试都问什么? 和之前一样,文章一上来,我们先来谈谈阿里的社招面试都问什么,其实这个话题并不是什么秘密,所有来阿里面试过的同学,都能回答一二. 两年前的时候,笔者在文章里是这么回答的. 这个是让LZ最头疼的一个问题,也是群里的猿友们问的最多的一个问题. 说实话,LZ只能隐约想起并发.JVM.分布式.TCP/IP协议这些个关键字,具体的问题真的是几乎都没记住.而且就算LZ记住了,也告诉你了,你也背会了,但LZ觉得,在面试中,你被问到一模一样问题的可能性依然很小. 甚至,就算你运气好被问到了

《Java程序员面试宝典》学习笔记(基本语法部分)

这是我学习欧立奇<Java程序员面试宝典>第三版的笔记.这篇是基本语法部分. ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象. 在Java中,字符只以一种形式存在,那就是Unicode.在JVM内部一切都由Unicode表示,而输出到外部后(JVM和OS的交界处就是各种输入/输出流)转换为其他形式的编码. Java用了中间缓存变量的机制,所以,j=j++可换成如下写法: te

【Python】Java程序员学习Python(二)— 开发环境搭建

巧妇难为无米之炊,我最爱的还是鸡蛋羹,因为我和鸡蛋羹有段不能说的秘密. 不管学啥,都要有环境,对于程序员来说搭建个开发环境应该不是什么难题.按顺序一步步来就可以,我也只是记录我的安装过程,你也可以滴. 一.准备Java环境 我已经说过了,其实我是一个Java程序员,所以学习过程中会有很多Java相关的内容和对比.先介绍下我的基本情况 jdk1.8 eclipse即可,版本最新的 怎么安装java,配置环境变量什么的,我都不会再说了,我这不是小白教程,我相信具备一定的能力. 二.准备Python环