jvm内存模型-GC算法-垃圾回收器

本文详细解析了JVM的运行时数据区,包括方法区、堆、程序计数器、本地方法栈和虚拟机栈,重点阐述了垃圾回收机制。介绍了垃圾回收的触发条件、对象回收标准、GC算法及过程,以及各种垃圾回收器的特点,如Serial、ParNew、ParallelScavenge、CMS、SerialOld、ParallelOld和G1。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文知识点全是对那本书的回顾-记录:

众所周知深入理解JVM的各种博客已烂大街,但其主要来源还是来自那本书,本文不准备另辟蹊径,只是将脑子里的知识点做一汇总,立贴准备后面打脸

本文学习思路:

  1. 各个模块简介,触发异常
  2. 各个模块对应的GC算法、垃圾收集触发场景
  3. 各种垃圾回收器的简介
  4. Metaspace

一、运行时数据区5大模块简介

JVMåå­æ¨¡å-1

1、方法区

  1. 属于线程共享的数据区
  2. 主要存放:运行时常量池、类的静态结构、静态变量、常量
  3. 不断动态加载类(反射、自定义类加载器加载外部类等),申请不到足够内存时:报错OutOfMemoryError

2、堆

  1. 属于线程共享的数据区
  2. 主要存放:数组、对象(是垃圾回收的主要区域)
  3. 不断创建对象,申请不到足够内存时:报错OutOfMemoryError

3、程序计数器

  1. 线程私有的数据区
  2. cpu时间片用完时记录当前线程的运行位置等信息
  3. 唯一一块没有定义OutOfMemoryError的数据区

4、本地方法栈

  1. 线程私有的数据区
  2. 为jvm本地方法服务
  3. 申请不到足够内存时:报错OutOfMemoryError

5、虚拟机栈

  1. 线程私有的数据区
  2. 主要存储:局部变量、动态链接、方法出口等信息
  3. 栈内存可以动态扩展,当扩展到不能申请到足够内存时:报错OutOfMemoryError
  4. 当线程请求的栈深度大于栈允许的深度时(如:递归):报错StackOverfolwError

二、GC

1、5大运行时数据区域中哪些需要去做垃圾回收呢?

其中线程计数器、本地方法区、虚拟机栈内存,属于线程私有,随着线程的创建、销毁而穿件销毁,所以不需要垃圾回收,那么垃圾回收就集中到了方法区和堆内存

2、什么样的数据将会被回收呢?

  1. 方法区-必须同时满足以下三个条件才能被回收
    1. 加载该类的类加载器被回收
    2. java.lang.Class对象没有任何地方引用
    3. 堆中没有该类的任何对象实例
  2. 堆-两大算法
    1. 引用计数器算法:给每个对象加一个引用计数器,每引用一次就+1,取消引用-1,当等于0时代表可以被回收
    2. 可达性分析算法:从"GCROOT"为起点搜索所经过的引用链,如果一个对象没有在引用链上说明可以被回收
    3. 其中引用计数器算法缺点:不能有效解决循环引用的问题,所有现在用的是可达性分析算法
  3. GC ROOT
    1. 栈内存中引用的对象
    2. 方法区中的常量引用的对象
    3. 方法区中静态属性引用的对象
    4. 内部方法引用的对象

3、垃圾收集过程中的算法

按照每个对象的生命周期来看,我们可以大概分为3类,朝生夕死类(新生代)、活的时间相对较长(老年代)、一般不死的(方法区)

  1. 分代收集算法(站在整个堆的角度来看):分为:新生代(大多数对象朝生夕死);老年代(活的相对较长的对象)
  2. 复制算法(站在新生代的角度):每个新生代分为一个Eden取,两个Survivor;内存比例是8:1:1;运行过程:对象进入后存在Eden区,当Eden区内存不足时发送一次Young GC,此时会将Eden和其中一块的Survivor中的存活数据放入另一块Survivor中,Eden和另一块Survivor清空(一直有一块Survivor是空的)
  3. 标记清除:每次先标记出来哪些对象需要被回收,然后进行清除
  4. 标记整理:每次先标记出来哪些对象需要被回收,然后进行清理并整理,保证空闲内存处于连续

总结:堆内存整体分为:新生代和老年代;新生代主要使用复制算法;老年代主要使用标记清除、标记整理算法

4、整个垃圾回收过程是什么样的呢?

  1. 对象创建后进入新生代的Eden,当Eden满后触发Young GC,将Eden和有数据的一个Survivor运用复制算法将还存活的数据复制到另一块Survivor,此时岁数+1(存活的对象都维护一个岁数)当岁数到达配置岁数时将Survivor的数据移到老年代
  2. 当新创建的对象属于大对象时直接将对象放入老年代,避免大对象多次复制
  3. 对象不断在老年代申请内存,当到达配置比例时触发Full GC进行一次标记清除或标记整理算法
  4. 代码显示调用System.gc()方法也会触发Full GC;建议忘掉此方法,不要在代码中用

5、finalize()方法

执行Full GC是会经过标记和清除两个节点,标记阶段时首先要看对象有没有必要执行finalize()方法,如果已经执行过finalize方法或者没有重写finalize方法,那么久认为没有必要执行了,直接进入清除阶段;如果重写了finalize方法,先将对象放入一个F-Queue队列,稍后会对队列中的对象在进行一次标记:看finalize方法内有没有将自己赋值给GC ROOT引用链内的变量,如果没有赋值,进入清理阶段,如果有赋值则移出标记,每个finalize方法只能被执行一次(下次再次被标记到不会再执行finalize方法,直接清除)

三、垃圾回收器

先来张图

å¨è¿éæå¥å¾çæè¿°

可以看到上三种适用于新生代的复制算法,下三种适用于老年代的标记清除、标记整理算法,整个垃圾收集过程需要上下配合并且有链接的收集器才能配合使用,而G1都适用

jdk1.6 默认垃圾收集器Parallel Scavenge(新生代)+ Serial Old(老年代)

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

  1. serial(适用于新生代复制算法):单线程去做收集,此过程中其它线程都处于等待中,直到收集线程结束
  2. parNew(适用于新生代复制算法):多线程运行收集
  3. Parallel Scavenge(适用于新生代复制算法):多线程运行收集,主要在不需要太多用户交互的场景(后台服务),主要关注吞吐量,对于等待时间没有特别高的要求
  4. CMS(适用于老年代标记清除算法):并发收集,底停顿,适应于和用户交互多,有限考虑等待时间短,会产生大量内存碎片,比较耗cpu默认启动的回收线程数是(cpu 数量+3)/4
  5. Senal Old(适用于老年代标记整理算法):单线程收集处理
  6. Parallel Old(适用于老年代标记整理算法):多线程收集,适用于跟用户交互少的后台服务,首要考虑吞吐量,对等待时间没有特别高的需求,如果有用户交互可考虑cms
  7. G1:不再区分新生代老年代,将整个堆划分为大小相等的多个区域(Region);有复制算法,有标记整理,并发与并发执行,停顿时间可控;

四、Metaspace

jdk1.8以前是方法区(永久代),重1.8开始改为Metaspace,主要区别

  1. Metaspace在本地内存分配,只要本地内存足够大就不会发送OutOfMemoryError异常,当然也可以设置最大值,防止占用本地内存过多,影响其它
  2. 字符串常量池移入堆内存,类的静态结构、静态属性、常量等在Metaspace
  3. 每个类加载器都有它自己的元空间,如果类加载器被回收,那么这个类的元空间都会被回收
  4. 、、、、、、

公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值