JVM垃圾收集算法和垃圾收集器

0、mind

在这里插入图片描述

在这里插入图片描述

本节主要介绍jvm中自动内存管理的内容, 当然在此之前要简单讲一下jvm中的内存区域是怎么样的。随后会介绍垃圾回收的过程,垃圾回收的方法以及垃圾收集器

一、基础

1.1 JDK框架

JDK全名是Java Development ToolKit,java开发工具包,这整个工具包包括JRE(java运行环境)以及一些java工具相关api。jre又包括jvm,Java SE api。

1.2 Java内存区域

在这里插入图片描述

这里我们介绍一下JVM在运行java代码的时候,它的内存区域是怎么样的。方法区和堆都是线程共享的,右边三个是线程隔离的。

1)程序计数器

在多线程切换的时候,每个程序计数器来记录当前线程的执行状态和位置,便于上下文切换的时候能恢复场景

2)虚拟机栈

虚拟机栈也是线程隔离的,用来存放线程运行的时候产生的局部变量表等信数据,局部变量表里存放的就是编译器已知的各种基本类型和对象引用

常说的堆内存和栈内存其实是继承自C/C++的内存分配风格,放在java中,栈内存指的就是这块虚拟机栈的内存空间

3)本地方法栈

与虚拟机栈相比,本地方法栈是为本地方法服务

4)堆

堆是jvm中内存区域最大的一块地方,这个区域唯一存放的就是对象实例,几乎所有的对象实例都在这里分配内存。堆也是垃圾收集器管理的内存区域。

5)方法区

方法区用来存放一些常量,静态变量等信息

6)运行时常量池

方法区的一部分

7)直接内存

直接内存并不是jvm虚拟机运行内存的一部分,但是也是java运行时经常使用的内存区域

二、垃圾收集

2.1 垃圾收集的流程

对虚拟机对象中的垃圾进行收集,首先要考虑三件事

  • 哪些需要回收?哪些是垃圾哪些不是
  • 什么时候回收?
  • 怎么回收?用什么收集算法

2.2 判断对象是否存活

首先我们回收的肯定是垃圾对象,死亡对象,那么我们就需要判断一个对象是否存活

1)引用计数算法

比较简单易用的方法。在对象中添加一个引用计数器,当有其他对象引用的时候,该计数器加一;当引用失效的时候,计数器减一。

但是这个方法也有明显的缺点,就是无法解决循环引用的问题。

这个方法原理简单,并且高效,但是需要考虑很多例外情况,所以主流的jvm虚拟机并不使用该方法。

2)可达性算法

目前主流程序语言使用的是可达性算法。

通过GC Rtoots的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,如果某个对象和root之间没有引用链,就是说它不可达,即死亡

在这里插入图片描述

3)什么是引用

上节我们提到了java中的引用,在这里我们更深入的了解一下。

在jdk1.2之前,引用就是很传统的其内存为另一个对象的地址。在这之后,分成四种

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

强引用就是传统的引用定义,只要有强引用,对象就存活

软引用则是描述一些还有用,但不是必须的对象。第一次内存溢出时,软引用对象会拉入第二轮回收范围,如果第一次垃圾回收后内存仍然不够就会清除

弱引用和软引用一样,也是非必需对象,但是被弱引用关联的对象能活过第一轮垃圾收集,但是下一轮会被回收

4)死亡救赎

因为程序在不断运行的过程中,一个对象可能在清除的某一个过程中“存活”被引用,所以即便在可达性算法判定为不可达对象时,也并非“非死不可”的。

这个时候处于缓冲状态,要真正宣告对象死亡需要两个过程。

第一次是可达性分析,没被引用的进入筛选。

进入筛选阶段的对象,再判断有无必要进行回收。

这个过程中,只要对象被拯救了,和GC Roots中有对象关联,即存活了。

2.3 收集算法

如果用判断对象是否存活方法来分类,垃圾收集算法分为引用计数式和追踪式,我们这里讨论追踪式,因为并没有使用引用计数方法。

1)分代收集理论

当前大多的收集器都遵循分代收集的原则。它建立在两个假说之上

  • 弱分代假说:绝大多数对象都是朝升夕灭的
  • 强分代假说:熬过越多次垃圾收集的对象就越越难消亡

根据这个假说,大部分的垃圾收集器都分成新生代和老年代。

分出不同区域之后,可以在不同区域分别进行垃圾回收,但是这又有一个问题,倘若新生代和老年代之间有引用怎么办?这样子每次还是需要扫描全部空间。

如何解决跨代问题?增加了第三条经验法则

  • 跨代引用假说:跨代引用对于同代引用来说仅占少数
2)标记——清除算法

把标记的死亡的对象直接清除。这是最基础最简单最高效的办法。缺点就是会造成内存碎片,并且当有大量对象需要回收时,会造成效率较低的情况。

3)标记——复制算法

为了解决标记清除算法回收大量对象效率低的情况,提出一种半区复制的方法。把一块内存区域分成两块,一次只用一边。进行垃圾回收时,标记存活的对象,复制到另一边去。这样的缺点就是浪费较多的空间,并且当对象存活率较高的情况下效率较低。

现在大部分收集器的新生代是采用这个方法的

4)标记——整理算法

针对标记复制方法的缺陷,提出了一种适合老年代的算法。标记存活对象后,让存活对象向一边移动。

移动势必会导致“stop the word”,但是不移动则会造成内存碎片

对于延迟控制来说,不移动的方法更为优秀;但是对于程序的整体吞吐量来说,移动则更为优秀

2.4 垃圾收集器

在这里插入图片描述

在开始之前,要简单介绍一下收集器中,并行并发的概念。

  • 并行指的是多条垃圾收集器线程之间的关系,多个线程在协同工作,此时用户线程是在等待状态。
  • 并发指的是垃圾收集器和用户线程之间的关系,用户线程并未被冻结。
1)Serial

最基础,历史最悠久的收集器,因为简单快速,现在也常在使用。

是单线程收集器,在收集过程中需要“stop the world"。新生代采用复制算法,老年代配合Serial Old采用整理算法

即便stop the world带来较高延迟的问题,但是这个收集器需要的内存极其小,而且延迟在绝对意义上,只要内存小也影响不大,所以对于客户端模式下的虚拟机还是非常不错的。

2)parNew

是serial的多线程并行版本,其他的和serial完全一致。

3)Parallel Scavenge

基于复制算法实现的收集器,也是并行的多线程收集器。这个收集器的关注点是吞吐量,希望垃圾收集的时间尽量短。

在这里插入图片描述

4)Serial Old

serial收集器的老年代版本

5)Parallel Old

老年代版本,支持多线程并发收集,使用整理算法。

6)CMS收集器

以最短回收停顿时间为目标。服务器响应速度优先,基于标记清除算法。

收集的过程分为四个步骤

  • 初始标记:仅标记GC Roots能直接关联的对象,速度很快
  • 并发标记:从GC Roots关联的对象遍历整个对象图,耗时长但是不需要停止用户线程
  • 重新标记:修正并发标记期间,由于用户线程所导致的部分对象记录
  • 并发清除:清除阶段

这之间初始标记和重新标记都需要“stop the world”。由于使用清除算法,会导致较多的内存碎片

7)Garbage First

G1收集器是垃圾收集器发展史上里程碑成果。

它摒弃了之前收集器分为老年代新生代的做法,采用局部收集的设计思路和基于Region的内存布局,收集“最具价值”的区域。G1是面向服务端的垃圾收集器。

  • 初始标记:标记GC Roots能直接关联的对象,stop the world
  • 并发标记:获取对象图
  • 最终标记:stop the world 修正标记
  • 筛选回收:对各个region的回收价值和成本来排序。这里的操作设计对存活对象的移动,需要stop the world

t

G1收集器是垃圾收集器发展史上里程碑成果。

它摒弃了之前收集器分为老年代新生代的做法,采用局部收集的设计思路和基于Region的内存布局,收集“最具价值”的区域。G1是面向服务端的垃圾收集器。

  • 初始标记:标记GC Roots能直接关联的对象,stop the world
  • 并发标记:获取对象图
  • 最终标记:stop the world 修正标记
  • 筛选回收:对各个region的回收价值和成本来排序。这里的操作设计对存活对象的移动,需要stop the world
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值