|
|
@ -94,7 +94,7 @@ Java 中的引用可以分为四类,强引用、软引用、弱引用和虚引 |
|
|
|
|
|
|
|
|
|
|
|
##### G1 及 ZGC |
|
|
|
##### G1 及 ZGC |
|
|
|
|
|
|
|
|
|
|
|
Garbage First(G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。它和 CMS 同样是一款主要面向服务端应用的垃圾收集器,不过在 JDK9 之后,CMS 就被标记为废弃了。在 G1 收集器出现之前的所有其他收集器,包括 CMS 在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),在要么就是整个 Java 堆(Full GC)。而 G1 是基于 Region 堆内存布局,虽然 G1 也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间或者老年代。收集器根据 Region 的不同角色采用不同的策略去处理。G1 会根据用户设定允许的收集停顿时间去优先处理回收价值收益最大的那些 Region 区,也就是垃圾最大的 Region 区,这就是 Garbage First 名字的由来。 |
|
|
|
Garbage First(G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。它和 CMS 同样是一款主要面向服务端应用的垃圾收集器,不过在 JDK9 之后,CMS 就被标记为废弃了,G1 作为默认的垃圾收集器,在 JDK 14 已经正式移除 CMS 了。在 G1 收集器出现之前的所有其他收集器,包括 CMS 在内,垃圾收集的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),在要么就是整个 Java 堆(Full GC)。而 G1 是基于 Region 堆内存布局,虽然 G1 也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间或者老年代。收集器根据 Region 的不同角色采用不同的策略去处理。G1 会根据用户设定允许的收集停顿时间去优先处理回收价值收益最大的那些 Region 区,也就是垃圾最大的 Region 区,这就是 Garbage First 名字的由来。 |
|
|
|
|
|
|
|
|
|
|
|
G1 收集器的运作过程大致可划分为以下四个步骤: |
|
|
|
G1 收集器的运作过程大致可划分为以下四个步骤: |
|
|
|
|
|
|
|
|
|
|
@ -116,7 +116,7 @@ G1 收集器的运作过程大致可划分为以下四个步骤: |
|
|
|
|
|
|
|
|
|
|
|
G1 的目标是在可控的停顿时间内完成垃圾回收,所以进行了分区设计,但是 G1 也存在一些问题,比如停顿时间过长,通常 G1 的停顿时间在几十到几百毫秒之间,虽然这个数字其实已经非常小了,但是在用户体验有较高要求的情况下还是不能满足实际需求,而且 G1 支持的内存空间有限,不适用于超大内存的系统,特别是在内存容量高于 100GB 的系统上,会因内存过大而导致停顿时间增长。 |
|
|
|
G1 的目标是在可控的停顿时间内完成垃圾回收,所以进行了分区设计,但是 G1 也存在一些问题,比如停顿时间过长,通常 G1 的停顿时间在几十到几百毫秒之间,虽然这个数字其实已经非常小了,但是在用户体验有较高要求的情况下还是不能满足实际需求,而且 G1 支持的内存空间有限,不适用于超大内存的系统,特别是在内存容量高于 100GB 的系统上,会因内存过大而导致停顿时间增长。 |
|
|
|
|
|
|
|
|
|
|
|
ZGC 作为新一代的垃圾回收器,在设计之初就定义了三大目标:支持 TB 级内存,停顿时间控制在 10ms 之内,对程序吞吐量影响小于 15%。 |
|
|
|
ZGC 在 JDK11 被引入,作为新一代的垃圾回收器,在设计之初就定义了三大目标:支持 TB 级内存,停顿时间控制在 10ms 之内,对程序吞吐量影响小于 15%。 |
|
|
|
|
|
|
|
|
|
|
|
#### 类加载机制 |
|
|
|
#### 类加载机制 |
|
|
|
|
|
|
|
|
|
|
@ -172,4 +172,17 @@ HotSpot 内置了多个 JIT 即时编译器,C1 和 C2,之所以引入多个 |
|
|
|
|
|
|
|
|
|
|
|
其实呢就是了解 Java 编译器和 JVM 是如何区分方法的。方法重载在编译阶段就能确定下来,而方法重写则需要运行时才能确定。 |
|
|
|
其实呢就是了解 Java 编译器和 JVM 是如何区分方法的。方法重载在编译阶段就能确定下来,而方法重写则需要运行时才能确定。 |
|
|
|
|
|
|
|
|
|
|
|
Java 编译器会根据所传入的参数的声明类型来选取重载方法,而 JVM 识别方法 |
|
|
|
Java 编译器会根据所传入的参数的声明类型来选取重载方法,而 JVM 识别方法依赖于方法描述符,它是由方法的参数类型以及返回类型所构成。JVM 内置了五个与方法调用相关的指令,分别是 invokestatic 调用静态方法、invokespecial 调用私有实例方法、invokevirtual 调用非私有实例方法、invokeinterface 调用接口方法以及 invokedynamic 调用动态方法。对于 invokestatic 以及 invokespecial 而言,JVM 能够直接识别具体的目标方法,而对于 invokevirtual 和 invokeinterface 而言,在绝大多数情况下,JVM 需要在执行过程中,根据调用者的动态类型来确定具体的目标方法。唯一的例外在于,如果虚拟机能够确定目标方法有且只有一个,比如方法被 final 修饰,那么它就可以不通过动态类型,直接确定目标方法。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
上面所说的 invokespecial、invokeinterface 也被称为虚方法调用或者说动态绑定,相比于直接能定位方法的静态绑定而言,虚方法调用更加耗时。JVM 采用了一种空间换时间的策略来实现动态绑定。它为每个类生成一张方法表,用于快速定位目标方法,这个发生在类加载的准备阶段。方法表本质上是一个数组,它有两个特性,首先是子类方法表中包含父类方法表中所有的方法,其次是子类方法在方法表中的索引,与它所重写的父类方法的索引值相同。我们知道,方法调用指令中的符号引用会在执行之前解析为实际引用。对于静态绑定的方法调用而言,实际引用将指向具体的方法,对于动态绑定而言,实际引用则是方法表的索引值。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JVM 也提供了内联缓存来加快动态绑定,它能够缓存虚方法调用中调用者的动态类型,以及该类型所对应的目标方法。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### JVM 是如何实现反射的? |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
反射呢是 Java 语言中一个相当重要的特性,它允许正在运行的 Java 程序观测,甚至是修改程序的动态行为。表现为两点,一是对于任意一个类,都能知道这个类的所有属性和方法,二是对于任意一个对象,都能调用它的任意属性和方法。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
反射的使用还是比较简单的,涉及的 API 分为三类,Class、Member(Filed、Method、Constructor)、Array and Enumerated。我当时是直接扒 Oracle 官方文档看的,讲的很详细。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
我对反射的好奇是来源于,经常会听说反射影响性能,那么性能开销在哪以及如何优化? |
|
|
|
|
|
|
|
|
|
|
|