文章目录
- 1、类加载器
-
- [1.1 什么是类加载器](#1.1 什么是类加载器)
- [1.2 什么是双亲委派机制](#1.2 什么是双亲委派机制)
- 2、类装载的执行过程(类的生命周期)
- 3、对象什么时候可以被垃圾回收器处理
- 4、JVM垃圾回收算法
-
- [4.1 标记清除算法](#4.1 标记清除算法)
- [4.2 标记整理算法](#4.2 标记整理算法)
- [4.3 复制算法](#4.3 复制算法)
- 5、分代收集算法
-
- [5.1 MinorGC、Mixed GC、Full GC的区别是什么](#5.1 MinorGC、Mixed GC、Full GC的区别是什么)
- 6、JVM有哪些垃圾回收器
-
- [6.1 G1垃圾回收器](#6.1 G1垃圾回收器)
- 7、强软弱虚引用的区别
1、类加载器
1.1 什么是类加载器
JVM处理class字节码文件成为二进制文件,类加载的作用则是将class字节码文件加载到JVM中。有四种类加载器:
- 启动类加载器:加载JDK下的jre/lib下的类,这个加载器由C++实现
- 扩展类加载器:加载JDK下的jre/lib/ext下的类,如果把开发者自己写的class放这个目录,也会被扩展类加载器加载
- 应用类加载器:加载开发者自己写的class
- 自定义类加载器:自己去继承ClassLoader,自己实现
1.2 什么是双亲委派机制
加载某一个类时,先委托上一级的加载器(父加载器)去进行加载,如果上一级加载器也有上级,则继续向上委托,若没被上级加载,则向下开始逐个找,直到加载成功,到最下面的加载器也没找到这个类,则抛异常ClassNotFountException。总之:
- 自底向上查找是否已经被父加载器加载过,有则直接返回
- 若没被加载,再自顶向下进行加载
该机制的作用:
- 保证类加载的安全性:避免用户自定义一个java.lang.String恶意替换JDK的核心类库里的String类
- 避免重复加载:避免同一个类被多次加载,提高效率
2、类装载的执行过程(类的生命周期)
- 加载:查找和导入class 文件
- 验证:保证加载类的准确性
- 准备:为类变量分配内存并设置类变量初始值
- 解析:把类中的符号引用转换为直接引用
- 初始化:对类的静态变量,静态代码块执行初始化操作
- 使用:new对象
- 卸载:删掉方法区的InstanceKlass和堆的Class对象
bash
加载:
- 加载磁盘上的class字节码文件、动态代理生成的类
- 通过全类名,获取类的二进制数据流
- 解析类的二进制数据流,在方法区中,生成一个InstanceKlass对象(c++),保存了类的所有信息
- 堆区生成一个和上面InstanceKlass对象类似的java.lang.Class对象,给Java代码操作
bash
连接--验证:
验证文件格式(魔数)、class文件里的主版本号是否适配当前JVM、符号引用里是否有其他类的private变量
bash
连接--准备:
为静态变量分配内存并设置初始值:
- static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成,如下面的b变量
- static + final 修饰基本类型,以及字符串常量,值已确定,赋值在准备阶段完成,如下面的c、d变量
- static + final 修饰引用类型,赋值也在初始化阶段完成,如下面的obj变量
bash
连接--解析:
将符号引用转为直接引用,比如方法中调用了其他方法,直接引用即使用内存地址直接指向方法。如#25转为内存地址。
bash
初始化:
初始化阶段对类的静态变量初始化、静态代码块执行。
- 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类
- 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行
java
使用:
- new关键字创建对象
- 调用静态类成员信息,比如:静态字段、静态方法
3、对象什么时候可以被垃圾回收器处理
如果一个对象没有任何引用指向它了,那这个对象就被定义为垃圾,可能被垃圾回收器回收。确定是否有引用指向它,有两种方式:
- 引用计数法:维护个计数,被引用一次就+1,循环依赖时会有内存泄漏,一般不用
- 可达性分析:普通对象A,经一个引用链可以到达GC Root对象,则A不可被回收,JVM持有GCRoot的List列表
A、B、C、D不可回收,X、Y可回收
4、JVM垃圾回收算法
标记清除算法、复制算法、标记整理算法、分代算法
4.1 标记清除算法
先根据可达性分析算法,标记垃圾对象,再对标记了可回收的对象进行GC
优点是标记和清除速度较快,缺点是回收后出现内存碎片化,不连贯,存个大对象或者数组(内存地址连续),就不行
4.2 标记整理算法
和标记清除算法相比,多了一步对象内存位置移动的步骤,解决了碎片化,但效率也低了一点
4.3 复制算法
内存一分为二,Form和To两块,先在From存对象,进行回收时,将存活的对象copy到To空间,再清掉From剩下的可回收对象,然后From和To角色互换,To成了新的From,清理后的From成了To
优点是无碎片化问题,效率较高,但空间利用率低,一半一半的存
5、分代收集算法
JDK8时,堆被分成两份,默认新生代 : 老年代 = 1:2
对占堆三分之一的新生代,又分为三块:
- 伊甸园区Eden,新生的对象都分配到这里
- 两块幸存者区survivor(分成from和to)
- Eden区:from区:to区= 8:1:1
分代回收的步骤:
- 新创建的对象,都会先分配到 eden 区
- 当伊甸园内存不足,标记伊甸园与 from(现阶段没有对象)的存活对象
- 将存活对象采用复制算法复制到 to 中,复制完毕后,伊甸园和from 内存都得到释放
- 经过一段时间后伊甸园的内存又出现不足,标记 eden 区域 to 区存活的对象,将存活的对象复制到 from 区
- 继续伊甸园区 + S0 + S1之间玩复制算法,直到有对象经过了15次GC(GC年龄默认15),晋升到老年代,不再from和to之间频繁的复制
- 对象过大或者幸存者区空间不足,可能出现对象提前晋升到老年代
5.1 MinorGC、Mixed GC、Full GC的区别是什么
STW,stop the world,暂停所有用户线程,等待垃圾回收完成再处理用户请求,STW要尽量短
- MinorGC(Young GC):发生在新生代的垃圾回收,暂停时间短(STW),这也是分代的最明显的一个好处了
- Mixed GC: 新生代 +老年代部分区域的垃圾回收,G1 收集器特有
- Full GC:新生代 + 老年代完整垃圾回收,暂停时间长(STW),应尽力避免
6、JVM有哪些垃圾回收器
- 串行垃圾收集器:Serial GC、Serial Old ,只有一个线程处理垃圾回收,期间用户线程阻塞(STW)
- GC并行垃圾收集器:Parallel Old GC、ParNewGC,多个线程处理垃圾回收,期间用户线程阻塞(STW)
- CMS(并发)垃圾收集器:CMS GC,作用在老年代
- G1垃圾收集器:作用在新生代和老年代
6.1 G1垃圾回收器
-
应用于新生代和老年代,在JDK9之后默认使用G1
-
堆被划分成多个区域Region,每个区域都可以充当eden,survivor,old,humongous,其中 humongous 专为大对象准备
-
采用复制算法
-
响应时间与吞吐量兼顾
-
分成三个阶段:新生代回收、并发标记、混合回收MixedGC。多次Young GC回收后,当堆的使用率达到阈值,触发混合回收MixedGC,用复制算法回收一轮所有年轻代、部分老年代、大对象区
-
因为清理是复制算法,如果清理时发现没有空Region去存放转移的对象(没地儿复制了),则转为单线程执行标记-整理算法进行Full GC,此时会导致用户线程的暂停
7、强软弱虚引用的区别
- 强引用:默认,只要有 GCRoots能找到它,就不会被回收,哪怕OOM
- 软引用:需要配合 SoftReference 使用,当垃圾多次回收,内存依然不够的时候会回收软引用对象
弱引用:需要配合 WeakReference 使用,只要进行了垃圾回收,就会把弱引用对象回收
虚引用:和其他几种引用不一样,它不影响对象的回收规则,形同虚设。必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法。虚引用的一个应用场景是直接内存的释放问题。
强引用:
软引用:
弱引用:
虚引用: