本文知识点全是对那本书的回顾-记录:
众所周知深入理解JVM的各种博客已烂大街,但其主要来源还是来自那本书,本文不准备另辟蹊径,只是将脑子里的知识点做一汇总,立贴准备后面打脸
本文学习思路:
- 各个模块简介,触发异常
- 各个模块对应的GC算法、垃圾收集触发场景
- 各种垃圾回收器的简介
- Metaspace
一、运行时数据区5大模块简介
1、方法区
- 属于线程共享的数据区
- 主要存放:运行时常量池、类的静态结构、静态变量、常量
- 不断动态加载类(反射、自定义类加载器加载外部类等),申请不到足够内存时:报错OutOfMemoryError
2、堆
- 属于线程共享的数据区
- 主要存放:数组、对象(是垃圾回收的主要区域)
- 不断创建对象,申请不到足够内存时:报错OutOfMemoryError
3、程序计数器
- 线程私有的数据区
- cpu时间片用完时记录当前线程的运行位置等信息
- 唯一一块没有定义OutOfMemoryError的数据区
4、本地方法栈
- 线程私有的数据区
- 为jvm本地方法服务
- 申请不到足够内存时:报错OutOfMemoryError
5、虚拟机栈
- 线程私有的数据区
- 主要存储:局部变量、动态链接、方法出口等信息
- 栈内存可以动态扩展,当扩展到不能申请到足够内存时:报错OutOfMemoryError
- 当线程请求的栈深度大于栈允许的深度时(如:递归):报错StackOverfolwError
二、GC
1、5大运行时数据区域中哪些需要去做垃圾回收呢?
其中线程计数器、本地方法区、虚拟机栈内存,属于线程私有,随着线程的创建、销毁而穿件销毁,所以不需要垃圾回收,那么垃圾回收就集中到了方法区和堆内存
2、什么样的数据将会被回收呢?
- 方法区-必须同时满足以下三个条件才能被回收
- 加载该类的类加载器被回收
- java.lang.Class对象没有任何地方引用
- 堆中没有该类的任何对象实例
- 堆-两大算法
- 引用计数器算法:给每个对象加一个引用计数器,每引用一次就+1,取消引用-1,当等于0时代表可以被回收
- 可达性分析算法:从"GCROOT"为起点搜索所经过的引用链,如果一个对象没有在引用链上说明可以被回收
- 其中引用计数器算法缺点:不能有效解决循环引用的问题,所有现在用的是可达性分析算法
- GC ROOT
- 栈内存中引用的对象
- 方法区中的常量引用的对象
- 方法区中静态属性引用的对象
- 内部方法引用的对象
3、垃圾收集过程中的算法
按照每个对象的生命周期来看,我们可以大概分为3类,朝生夕死类(新生代)、活的时间相对较长(老年代)、一般不死的(方法区)
- 分代收集算法(站在整个堆的角度来看):分为:新生代(大多数对象朝生夕死);老年代(活的相对较长的对象)
- 复制算法(站在新生代的角度):每个新生代分为一个Eden取,两个Survivor;内存比例是8:1:1;运行过程:对象进入后存在Eden区,当Eden区内存不足时发送一次Young GC,此时会将Eden和其中一块的Survivor中的存活数据放入另一块Survivor中,Eden和另一块Survivor清空(一直有一块Survivor是空的)
- 标记清除:每次先标记出来哪些对象需要被回收,然后进行清除
- 标记整理:每次先标记出来哪些对象需要被回收,然后进行清理并整理,保证空闲内存处于连续
总结:堆内存整体分为:新生代和老年代;新生代主要使用复制算法;老年代主要使用标记清除、标记整理算法
4、整个垃圾回收过程是什么样的呢?
- 对象创建后进入新生代的Eden,当Eden满后触发Young GC,将Eden和有数据的一个Survivor运用复制算法将还存活的数据复制到另一块Survivor,此时岁数+1(存活的对象都维护一个岁数)当岁数到达配置岁数时将Survivor的数据移到老年代
- 当新创建的对象属于大对象时直接将对象放入老年代,避免大对象多次复制
- 对象不断在老年代申请内存,当到达配置比例时触发Full GC进行一次标记清除或标记整理算法
- 代码显示调用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
- serial(适用于新生代复制算法):单线程去做收集,此过程中其它线程都处于等待中,直到收集线程结束
- parNew(适用于新生代复制算法):多线程运行收集
- Parallel Scavenge(适用于新生代复制算法):多线程运行收集,主要在不需要太多用户交互的场景(后台服务),主要关注吞吐量,对于等待时间没有特别高的要求
- CMS(适用于老年代标记清除算法):并发收集,底停顿,适应于和用户交互多,有限考虑等待时间短,会产生大量内存碎片,比较耗cpu默认启动的回收线程数是(cpu 数量+3)/4
- Senal Old(适用于老年代标记整理算法):单线程收集处理
- Parallel Old(适用于老年代标记整理算法):多线程收集,适用于跟用户交互少的后台服务,首要考虑吞吐量,对等待时间没有特别高的需求,如果有用户交互可考虑cms
- G1:不再区分新生代老年代,将整个堆划分为大小相等的多个区域(Region);有复制算法,有标记整理,并发与并发执行,停顿时间可控;
四、Metaspace
jdk1.8以前是方法区(永久代),重1.8开始改为Metaspace,主要区别
- Metaspace在本地内存分配,只要本地内存足够大就不会发送OutOfMemoryError异常,当然也可以设置最大值,防止占用本地内存过多,影响其它
- 字符串常量池移入堆内存,类的静态结构、静态属性、常量等在Metaspace
- 每个类加载器都有它自己的元空间,如果类加载器被回收,那么这个类的元空间都会被回收
- 、、、、、、
公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢