IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> JVM 2.JVM垃圾回收 -> 正文阅读

[Java知识库]JVM 2.JVM垃圾回收

JVM 2.JVM垃圾回收

0 思考

如果我们抛开JVM对垃圾回收的实现,如果让我们自己来进行垃圾回收,我们会怎么做呢?

  1. 首先我们要知道在哪里进行垃圾回收,也就是哪里会产生垃圾
  2. 我们要判别哪些是垃圾 没被引用的对象是垃圾
  3. 我们如何把“垃圾对象”所占用空间释放出来 类似操作系统,把这块内存标记为空闲内存
  4. 如何又好有快的完成回收 既不产生碎片,又不损耗性能 各种算法

以上都是我自己想的答案,接下来我们去看下JVM是怎么解决这些问题的

1. 哪些对象是垃圾?

我们有许多方法识别哪些对象是垃圾

1.1 引用计数法

给对象添加一个引用计数器,每当有一个地方引用它,计数器加1,引用失效时,计数器间1,计数器为0,就代表它暂时是个垃圾。但JVM没用使用这种方法 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiBgnX5Z-1640184180408)(C:\Users\55488\AppData\Roaming\Typora\typora-user-images\image-20211221223309857.png)]

如果采用引用计数法,就不能正常回收。

1.2 可达性分析算法

基本思想:通过一些列被称为==“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称为应用链==,当一个对象无法被GC Roots 到达时,就是垃圾。

image-20211221223647229

在Java中,可做为GC Roots的对象主要在全局性应用和执行上下文中,有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即Native方法)引用的对象
1.2.0 HotSpot JVM中的可达性分析算法

上面的可达性分析算法有个一个大问题,方法区太大了,找到所有的对象和GCRoots对象需要的时间太久了。更致命的是,我们在进行GC是系统还能正常运行吗? 显然不能,这些对象的状态都不能发生改变了,也就是所谓的一致性快照

如果让我们自己解决这个问题,我们会怎么解决呢?

让我来的话,我会在一个Map<引用,地址>把每个引用和他对于的地址存下来,每有新的对象,就更新这个Map。

这正确吗? 只正确了一半

JVM中确实是用了一个 叫做OopMap的Map结构来存储哪个对象引用指向了哪个位置的对象。但他是每有引用变动,就更新吗? 不是的

因为这样需要进行大量的操作,消耗大量资源。

JVM 中定义了==安全点(Safepoint)==来解决这个问题,程序会在安全点进行对OopMap的操作更新。然后只有在安全点才能进行GC。

注意安全点是指令级别的操作了,是JVM 直接写入 字节码文件

有了安全点我们就高枕无忧了吗? 不是的

安全点机制要求如果需要进行GC,那么所有的线程都会在最近的安全点停下,等待GC,这是一个动态的过程。那么如果有的线程没办法到安全点呢?比如阻塞的线程。其实这些阻塞的线程已经满足垃圾回收的条件了,那就是他的状态确实不会变了。所以我们称他是在安全区的。安全区可以看作扩展的安全点。

1.3 什么是引用

如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,则称这块内存代表这一个引用,也就是这个reference类型的数据代表一个引用。

JDK1.2后对引用进行了扩充

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0QThVtk-1640184180410)(C:\Users\55488\AppData\Roaming\Typora\typora-user-images\image-20211221224518836.png)]

虚引用相当于一个报警器,当这个对象被回收时,会发出报警

1.4 finalize

现在我们知道了哪些对象是可以进行回收的,那么 JVM 一定会在垃圾回收时回收这些对象吗?

不一定!

如果一个对象实现了且没有执行过finalize()方法,那么他会被放入F-Queue中去执行他的finalize方法,如果执行完,他还是GC Roots不可达,那么就会被回收掉。

怎么判断有没有执行过呢?

这个对象被放入F-Queue前,会给他一个标记。下次再回收到他,看到这个标记,就直接回收了。

F-Queue队列将交给一个 低优先级的Finalizer线程去执行。也就是如何里面出问题 了,也要保证不影响别的地方

这个功能建议不用

1.5 方法区中的垃圾

除了堆里面,方法区中也有垃圾。

这里以1.8为例,之前的大家看下图也就知道了。

image-20211221220843916

1.8中除去堆的部分,方法区就剩下元空间了。

image-20211221225941433image-20211221225946976

2.垃圾收集

JVM为了实现我们上面提到的问题中的 3 和 4 。

在进行垃圾回收前又进行了垃圾的收集,非常合理

那么如何进行垃圾收集呢?

2.1 标记-清除算法

属于收集算法的基本款,在前面讲到的应用技术法的基础上进行。但显然无法满足我们 问题4 中要用高效还要没有内存碎片的要求。

2.2 复制算法

把内存分为大小相等的两块,每次只用一块,当不够的时候,把还存活的对象复制到另外一块上,放整齐。然后对这一块进行全部回收。代价:牺牲一半的内存,这会导致垃圾回收的频率加大,再加上要复制,效率会有所降低,不过好在每次活下来的对象很少很少,所以总体来讲,效率不错,效果也不错。

因为我们现在主要使用的HotSpot JVM 就用的这种方法来回收新生代,并且更加优化,我们在对HotSpot 中的复制算法做详细解释。

image-20211221231822428

2.3 HotSpot JVM 中的 复制算法

我们知道新时代的垃圾回收后,存活下来的对象会很少,明显不需要一半的内存时存放。所以我们做了点优化,把存放幸存对象的区域缩小了,而且分成了两个。至于为什么分成两个,请看。

image-20211221232557187

  • Eden 区放新进入新生代的对象
  • survivor 1 放 从之前的垃圾回收中存活下来的对象
  • survivor 2 为空 相当于 原始的复制算法的内一半空的区域,用于存放存活下来的对象。

那么对新生代进行垃圾回收时,会将Eden区和survivor 1 区存活下来的对象会进入 survivor 2 区放整齐。那么为了保证survivor2 保存空,是不是垃圾回收后我们还要将survivor2 区的对象放入 survivor 1区呢? 不是这样的 这样太慢了,JVM 直接把survivor 2 改名为survivor 1 ,把原来的surviror 1 改为survivor 2 。这样更高效

然后在一次垃圾回收后,在交换survivor区前,如果survivor 2 也满了,那么就把里面的对象放入老年代。

2.4 标记-整理算法

在新生代我们之所以可以使用复制算法,是因为新生代存活率很低,即使要复制,复制的也不多。在HotSpot 中 甚至我们还可以只浪费10%的空间。

但如果我们对象存活率很高呢?那么需要留给复制的空间就要很多,极端情况下 100%存活率,那么我们就需要浪费50%的空间,好要花大量时间复制。亏爆了。

那么哪里对象存活率高呢?老年代,他来了。

标记整理算法就是在标记清除算法的基础上,加了整理这个过程,先把存活的对象都整齐摆好,然后在回收,这样就没有碎片了。

2.5 分代收集算法

不算一个单独的算法。就是把堆分为新生代,老年代,新生代用复制,老年代用标记整理。

3.垃圾收集器

垃圾收集器是对上面垃圾回收算法的具体实现和应用。

每种垃圾回收器都有自己的优缺点,适合不同的场景,通常可以配合使用。真正的垃圾回收流程也会因为垃圾收集器的选择而产生不同。这里不做详细解释。

image-20211222221138032

4.内存分配与回收策略

我们上面讨论了对象内存的回收,下面我们开始聊聊对象内存的分配。

image-20211222221829211

4.1 对象内存分配在哪?

这个问题好像很简单,新对象的内存分配肯定在新生代的Eden区呀,但真实情况下一定是这样吗?

**首先,对象确实优先在Eden分配。**当Eden区空间不够的时候,就触发一次Minor GC,然后再试图放入Eden。这里,从Eden区被转入Survivor 1 区的对象,如果能放入,则放入,否则会直接进老年代。

大对象直接进入老年代。这么做是为了避免Eden区及两个Survivor区之间发生大量的内存复制

长期存活的对象将进入老年代。这个很好理解,虚拟机给每个对象了一个Age计数器,在survivor中每活过一次垃圾回收,就加一,到达一定数量(默认15,通过-XX:MaxTenuringThreshold调整)会被放入老年代。

Survivor中相同年龄的对象所占用内存大小和超过survivor区的一半超过该年龄的对象会之间进入老年代

4.2 空间分配担保

大白话,就是一次minor GC 后存活下来的对象的内存大于survivor 2 区中的内存,那么就会把超出的部分之间放入老年代。那么理所当然的,在放入老年代前,要看下够不够放,如果够就可以放,如果不够还要看是否允许失败,不允许的化,就进行一次Full GC,允许的话,就去根据以往的经验推测下能否成功,推测可以就尝试Minor GC,不可以就Full GC。

这部分很简单,就不长篇大论了,大家去看下书就明白了。

这种机制的存在,是为了防止一次突发的Minor GC后大量对象存活,导致一次Full GC。 也就是减少Full GC频率。
以放,如果不够还要看是否允许失败,不允许的化,就进行一次Full GC,允许的话,就去根据以往的经验推测下能否成功,推测可以就尝试Minor GC,不可以就Full GC。

这部分很简单,就不长篇大论了,大家去看下书就明白了。

这种机制的存在,是为了防止一次突发的Minor GC后大量对象存活,导致一次Full GC。 也就是减少Full GC频率。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-23 15:37:37  更:2021-12-23 15:39:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:26:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码