开发者学堂课程【JVM 整体架构及内存调优 :JVM 整体架构及内存调优(二)】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1207/detail/18174
JVM 整体架构及内存调优
三、JVM 加载机制详解
是 JVM 架构的一个组成部分。类加载机制大致可以分为以下几个步骤加载、验证、准备、初始化、卸载等。类加载的过程必须是按照顺序去执行的。例如加载可以理解为 class 编译时,把 class 文件加载过来;验证是验证 class 里是否是合法的,是否有一些编译错误等;准备是做一些准备工作;解析是把 class 文件解析成系统内识别的一些指引,然后是初始化使用,继而是卸载。卸载是谁去进行这个动作。
1.类加载机制的加载器
分为四类:启动类加载器、扩展类加载器、系统类加载器、用户自定义类加载器。
加载器的作用是正常一个程序运行之后不同的类选择自己的加载器。类可以是自己编写的类以及 JDK 自带的类,也可以是一些别的jar包里的类。
(1)启动类加载器是操作一些核心的类库,即 JRE lab RT jar 包里面的类。
(2)系统类载器是开始时加载程序中默认的一个加载器,它一开始便是系统类载器,指定 Class pass 下的属性,然后去指定路件下的类库,继而去执行。
(3)扩展类加载器是可以拓展的加载器,因为它有自己的目录,例如核心类库:JRE lab RT。在 JRE lab 对一个 EXT 目录下,如果想让程序默认扩展类加载器,可以把程序编译成一个 jar 包,之后放到 EXT 目录下,这时会自动成为扩展类加载器。
(4)自定义加载器,顾名思义加载器是自定义的。通过配合这三种加载器去执行的,必要的时候进行定义。应用部署就是通过自定义实现的。
2. 加载类的顺序
存在顺序则会引发一些问题。例如同样的一个类在 JDK 里存在,但是在程序里加载时是存在顺序的,如果没有按照顺序,就会混乱,而且会导致一些重复加载的问题。
此时引入新的概念:双亲委派机制
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException ),子加载器才会尝试自己去加载。
如果没有这样的流程,需要自己去加载,这样的话需要复列再去加载,这样就会存在重复加载的情况。再者,例如像 String 类型,在 jar包里以及 JDK 里的类都存在一个 String,则加载的过程中会存在加载混乱的情况,所以需要双亲委派模型。双亲委派主要防止类的重复加载以及不正常的加载,这是引入双亲委派的一个主要的原因。
四、垃圾回收机制及算法
垃圾回收机制是 java 语言的伴生产物,实际上就是垃圾回收。事实上 ,垃圾收集的历史远远比 java 久远,在1960年诞生于麻省理工学院的 Lisp 是第一门开始使用内存动态分配和垃圾收集技术的语言问题的能力。
是在 java 当中应用比较广泛,实践上比较丰富的一个地方。
1.优点
(1)具有垃圾回收机制及算法之后,不需要去考虑内存管理。例如C++,因为它需要主动申请释放内存。但如果身为 java 的开发工作人员,则完全不用考虑它的内存、管理情况。
(2)它可以防止有效的内存泄露,有效的利用可以使用内存。因为不需要操作内存,则出错的概率就变低,就可以更有效的防止内存泄漏。
(3)由于有垃圾回收机制, java 中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。
缺点:因为不去操作内存管理,java 开发人员不了解自动内存管理,
因为内存管理都是 JVM 在管理,实际上是不知道底层的一些操作原理的。出现问题时,因为不了解底层,就会不知道如何具体解决这个问题
2.判断对象是否死亡
只有死亡的时候才会进行回收。
(1)引用技术算法
一个对象被另一个对象、另一个例子引用,这时它的计数值加一;当另外一个类先结束的时候进行释放,这时计数值减一,当变成零的时候,就可以被回收。
但会存在问题,例如存在一个循环,有 A、B、C 三个类,三个类都互相引用对方的对象,这时则不能等到对象被完全释放掉,即计数值为零的情况,这样不能一直回收,所以引入了可达性分析算法。
(2)可达性分析算法
GC Root 作为试点,以搜索对象的路径为引用链。如果说当一个对象到任何的 GC Root 都没有引用链的时候则表明对象不可达,则这个对象是不可用的,这时可以解决循环依赖的问题。
如下图:
存在一个 GC Root,下有 Object A,如果后面的对象没有在引用链里,例如 Object E 没有被 GC Root 关联到,即没有在引用链里,这时可以回收。
可达性分析算法是通过 GC Root 从上到下去寻找引用链,所以 GC Root 是非常关键的一个角色。
可能成为 GC Root 对象:
①方法区中 static 静态引用的对象
②方法区中 final 常量引用的对象
③本地方法栈中 JNI(Native 方法)引用的对象
④java虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
⑤所有被同步锁( synchronized关键字)持有的对象。
3.引用
在 JDK 中有四种引用类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)四种。
(1)强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足, java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止。
(2)弱引用:内存空间足够,垃圾回收机制就不会回收;如果空间不足,此时会回收。
用来描述那些非必须对象,但是它的强度比软引用更弱-些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK 1.2版之后提供了 WeakReference 类来实现弱引用。弱引用可以和一个引用队列( ReferenceQueue )联合使用,如果弱引用所引用的对象被垃圾回收。java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
(3)软引用可以和一个引用队列( ReferenceQueue )联合使用,如果软引用所引用的对象被垃圾回收器回收, java 虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用与软引用的区别:
①更短暂的生命周期;
②一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;
弱引用不会判断内存是否充足,但是软引用会进行判断。如果内存空
间不充足足够,垃圾回收机制就会回收;如果空间充足,则不会回收。
(4)虚引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会“虚引用”顾名思义,它是最弱的一种引用关系。如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
是最弱的一种引用,四者是以此递减的关系,强引用是最强的引用。
如果一个对象仅有虚引用,则任何时候都有可能被垃圾回收机制回收。
虚引用与软引用和弱引用的一个区别在于:
①虚引用必须和引用队列( ReferenceQueue )联合使用。
②当垃圾回收器准备回收一个对象时 ,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
可以用来判断对象之间引用关系的一个概念。
4.垃圾回收算法
垃圾回收算法分为:标记—清除算法,标记—复制算法,标记—整理算法。
(1)标记—清除算法
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,再释放内存。
如图:
深色的是可以回收的,回收后内存变成白色的,即可以使用的。实际上内存被隔断,不可回收的存活的对象的内存是不连续的。
(2)标记—复制算法
存在碎片问题,标记-复制算法主要是要解决标记-清除算法面对大量客户的对象执行效率低的问题以及内存碎片的问题。
标记-复制算法与标记-清除算法的区别:先复制出来同样的一个内存。
把可回收的对象先回收到复制出来的内存空间里,这时不会存在内存碎片。但是也存在缺点:在复制的时候会有一个相同的内存,这样形成了内存的浪费。首先要预留一半的内存区域,存放存活对象,这样就会导致一些对象可用对象的区域减少一半,总体的 GC 可能会变得更加频繁。如果出现存活率比较多时,需要复制的对象变多,成本会上升,效率会降低。如果99%的对象都在老年代,长时间没有被回收的对象,那么老年代的对象是无法使用这种算法的。
(3)标记—整理算法
为了解决标记—复制算法问题诞生的一种算法。
具有标记过程,但是最后会有一个整理过程,不会有内存碎片,标记
完成之后会把空前的内存连接到一起;没有被释放的内存则归类放到一起。这样可以避免标记—复制算法浪费50%空间的缺点。
本身的缺点:例如有些极端对象在一些老年代是不能执行这种算法的,
因为老年代的对象一直存活。
5.垃圾回收器
(1)分为四类:串行回收器、并行回收器、CMS 回收器、G1 回收器。
垃圾回收器会根据新生代、老年代去回收。串行回收器有新生代串行回收器和老年代串行回收器;并行回收器也会有新生代 ParNew 回收器和老年代 ParNew 回收器。
并发回收器是同时回收很多个对象,并行回收器是一起进行回收。
CMS 是老年代的回收器,如果使用 CMS,则需要寻找一个年轻代的回收器与之配合使用;G1可以回收年轻代也可以回收老年代。
(2)组合关系
垃圾回收器是具有组合的关系,并不是可以任意去组合,会有建议的组合关系,例如 CMS 只能与 ParNew GC 进行组合。并行回收器只能与并行回收器进行组合,例如 Parallel Scavenge GC 和 Parallel Old GC 进行组合。
串行垃圾回收( Serial ):为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,不适合交互性强的服务器环境。
串行可以理解为不是并发的过程,是一个一个进行回收的,是单个GC区;
并行垃圾回收 ( Parallel ):多个垃圾收集器线程并行工作,同样会暂停用户线程,适用于科学计算、大数据后台处理等多交互场景。
并行是当多个应用线程过来之后,有多个 GC。
并发垃圾回收(CMS):用户线程和垃圾回收线程同时执行,不一定是并行的,可能是交替执行。即可能一边垃圾回收, 一边运行应用线程,不需要停顿用户线程,互联网应用程序中经常使用,适用对响应时间有要求的场景。
特点:
①CMS 收集器对 CPU 资源非常敏感,效率会急剧下降;
②CMS 收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次 Full GC 的产生。
③空间碎片: CMS 是一款基于标记清除算法实现的收集器,所有会有空间碎片的现象。
(3)CMS 垃圾收集过程
初始标记、并发标记、重新标记、并发清除,
其中初始标记和重新标记都需要 stop The World。
(4)G1 收集器
是新出的垃圾回收器,面向服务端应用,主要针对配备多核 CPU 及大容量内存的机器,以极高概率满足 GC 停顿时间的同时,还兼具高吞吐量的性能特征。
特点:
①G1 把内存划分为多个独立的区域 Region;
②G1仍然保留分代思想保留了新生代和老年代但他们不再是物理隔离,而是一部分 Region 的集合;
③G1 能够充分利用多 CPU.多核环境硬件优势,尽量缩短 STW;
④G1 整体整体采用标记整理算法局部是采用复制算法,不会产生内存碎片;
⑤G1 的停顿可预测能够明确指定在一个时间段内消耗在垃圾收集 上的时间不超过设置时间;
⑥G1 跟踪各个 Region 里面垃圾的价值大小会维护一个优先列表每次根据允许的时间来回收价值最大的区域从而保证在有限事件内高效的收集垃圾。
(5)G1 GC 垃圾回收的过程中的四个阶段:
①初始标记和 CMS 一样只标记 GC Roots 直接关联的对象;
②并发标记进行 GC Roots Traceing 过程;
③最终标记修正并发标记期间,因程序运行导致发生变化的那一部分对象;
④筛选回收根据时间来进行价值最大化收集;
与 CMS 的区别:CMS 存在一个 SW 的停止过程,做初始标记和重新标记。