Skip to content

Latest commit

 

History

History
258 lines (126 loc) · 9.9 KB

File metadata and controls

258 lines (126 loc) · 9.9 KB

JVM

个人网站:https://www.chengxuchu.com/

1.JVM类加载

1.0 JVM 类加载机制

JVM的类加载机制,总共分为 5 部分,加载,验证,准备,解析,初始化。

加载

  • 将该类解析成二进制字节流

  • 将表示该类的二进制流,转储为方法区运行时的数据结构。

  • 在堆中生成一个表示该数据结构的对象,然后该对象作为 访问该数据结构的入口

验证

这一段主要是保证加载出的二进制字节流符合JVM虚拟机的要求,保证不会对虚拟机造成伤害。

准备

验证完之后,发现没有问题,我们开始对类分配内存,初始化默认值等。

解析

将常量池内的符号引用替换为直接引用的过程

符号引用:使用一组符号来描述所引用的目标,符号可以是任何形式的字面量。

直接引用:直接引用时可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

初始化

JVM开始真正执行JAVA类中编写的代码,将主导权交给应用程序。

1.1 什么是类加载器

通过一个类的全限定名来获取描述此类的二进制字节流”。

类加载器的主要分为这几种,启动类加载器,拓展类加载器,程序类加载器。用户自定义类加载器。

另外的话,判断两个类是否相同,需要判断是否使用同一个类进行加载,如果来自于一个 Class文件,但是不是一个加载器加载的话,那也不是同一个类。

1.2 JVM类加载模型

类加载过程使用双亲委派机制进行加载

1.3 说说双亲委派机制

加载过程:除了启动类加载器,其余所有的类加载器都有父加载器,他们之间为组合关系。

双亲委派加载过程:某个类加载器在收到加载过程之后,首先不会自己去尝试加载,会优先请求自己的父加载器进行加载。(如果加载过,则返回给子加载器)进行不断向上请求,直到启动类加载器,如果启动类加载不了,再下沉到子加载器进行加载。

1.4 双亲委派机制的意义

防止某些系统级别的类被篡改,因为这些类已经被 启动类加载器加载过了,所以其他类没有机会再去加载了,进而防止了一个恶意代码注入的问题。

1.5 如何打破双亲委派机制

双亲委派模型只是虚拟机的规范,比如我们设置的类则可以不遵守这个规范,重写 loadClass方法即可。

1.6 内存模型

内存模型

2.垃圾收集器

垃圾收集器的目标就是为了减少STW而努力。

2.0 CMS

是一种以获取最短回收停顿时间为目标的收集器。这个收集器也是基于标记-清除算法实现的,他的运作过程相比前面几种收集起来说要更复杂一些,整个过程分为四个步骤。

  • 初始标记

  • 并发标记

  • 重新标记

  • 并发清除

初始标记也需要,所有用户线程停顿,然后标记出GCROOT能直接关联到的对象。速度很快,并发标记就是开始搜索全图,此时不需要用户线程停顿。重新标记是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这时也需要停止用户线程。然后进行并发清除。

缺点:

1.CMS收集器对处理器资源非常敏感

2.无法处理浮动垃圾(就是并发清理阶段生成的垃圾)

后备预案,冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集。

2.1 G1

JDK9之后G1成了默认的垃圾收集器

G1的几种分区

  • 年轻代(Eden区和Survivor区)

  • 自由代

  • 老年代

  • 大对象区

每个Region的大小通过-XX:G1HeapRegionSize来设置,大小为1~32MB,默认最多可以有2048个Region,那么按照默认值计算G1能管理的最大内存就是32MB*2048=64G。

因为我们每个区域的Region的大小可以自己设定,然后我们设定超过一半Region大小的对象为大对象,则放入大对象分区中,另外就是当超过几个Region的对象,我们可以通过大对象分区的几个Region进行存储。

另外因为传统的垃圾回收器的停顿时间是不可预估的,所以不是特别好,所以我们�可以通过G1来对停顿时间进行一个预估,我们可以通过回收之后的空间大小回收需要的时间,根据评估得到的价值,在后台维护一个优先级列表,然后基于我们设置的停顿时间优先回收价值收益最大 的Region。

G1里新生代的大小,占所有空间的 5%,最大可以拓展到 60 %。

新生代的大小,主要通过这几个参数决定。

G1NewSizePercentG1MaxNewSizePercent固定新生代大小。

-XX:NewRatio看看是否设置了比值,新生代和老年代的比值

如果没有则按之前的比例,5% - 60%

回收过程

image-20211014135634858

初始标记:GC ROOT能关联到的对象需要可达性分析

并发标记:不需要停止,标记整个对象图中所有对象,并且会标记上并发过程中产生的新对象。

最终标记:需要STW,做最后一次标记

筛选回收:通过获取的最大收益进行回收。然后把需要回收的Region中,存活的对象复制到新的Region中。

新生代的回收情况:会回收新生代eden区的内容,当里面含有存活的对象时,将其复制到 Survior 中,使其年龄+1,如果年龄超过阈值,则将其复制到老年代中。

eden 和 Survivor的 比例大概是 8:1:1,因为百分之 90 的新生代都是朝生夕死,然后8+1用来回收新生代,另一个 1 用来复制。

2.2 其他垃圾收集器

Serial

最基础,历史最悠久的垃圾收集器,是一个单线程工作的收集器,他在收集垃圾时,必须暂停其他工作,直到它收集结束。新生代标记-复制,老年代标记-整理

Serial old

是Serial的老年代版本,使用标记-整理算法,主要有两种用法,一种是和Parallel Scavenge 搭配使用,另一种是作为CMS的预案。

ParNew收集器

和Serial 收集器相比,没有太大改进,仅仅是增加了多线程回收,然后新生代使用复制算法回收,老年代采用标记整理回收,后面ParNew和CMS垃圾收集器配合使用,作为新生代的收集器。

Parallel Old

Parallel Scavenge 的老年代版本,支持多线程并发收集,基于标记整理算法啊。

Parallel Scavenge

这个收集器也是一款新生代收集器,同样是基于标记-复制算法实现的收集器。它和其他收集器不同的地方是,他专注于可以达到一个可以控制的吞吐量。

吞吐量 = 运行用户代码时间/(运行用户代码时间+运行垃圾收集时间)

高吞吐量,适合在后台运算而不需要太多交互的分析任务。自适应调整策略是这个收集器的一个重要特性。

3.其他常考

3.0 垃圾回收算法

标记-清除算法

原理和名字一样,首先标记出需要回收的对象,标记完成后,统一回收掉所有被标记的对象。

缺点:不稳定,效率低,内存空间碎片化,会产生没有连续内存的情况,无法分配给新的对象。

标记-复制算法

每次分配两块内存,先使用一块,当某一块快用完的时候,就将这一块存活的对象回收到另一块中。

缺点:如果有大量存活的时候,则需要大量的内存开销,每次都是针对半区进行回收,然后就会将可用内存缩小为原来的一半。appel 分配担保机制,以 9 :1的比例进行标记复制,9的用于分配内存,1用来复制存活的对象,另外当1不足以容纳一次的时候,就需要依赖其他内存区域,分配担保。

用于新生代,老年代一般不使用

标记-整理算法

3.1 两种判断对象死亡的方法

可达性分析:

原理:首先从GC Roots的对象作为起点开始,然后向下搜索,搜索走过的路径称为引用链,如果一个对象没有任何引用链相连,则判断为对象不可用,作为可回收对象,通过finalize()自救。

优缺点:解决相互循环引用问题

这个算法的核心思路就是通过一些列的“GC Roots”对象作为起始点,从这些对象开始往下搜索,搜索所经过的路径称之为“引用链”。

当一个对象到GC Roots没有任何引用链相连的时候,证明此对象是可以被回收的。如下图所示:

在Java中,可作为GC Roots对象的列表:

  • Java虚拟机栈中的引用对象。

  • 本地方法栈中JNI(既一般说的Native方法)引用的对象。

  • 方法区中类静态常量的引用对象。

  • 方法区中常量的引用对象。

引用计数法:

原理:有一个地方引用对象,则此对象计数器 +1,引用失效则-1,计数器为0则不能使用

优缺点:判断效率高,实现简单,但是难以解决对象互相循环引用问题

对象内存图