@@ -11497,7 +11497,7 @@ Java 语言提供了对象终止(finalization)机制来允许开发人员提
1149711497- 主要不足是**只使用了内存的一半**
1149811498- 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销都不小
1149911499
11500- 现在的商业虚拟机都采用这种收集算法**回收新生代**,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间
11500+ 现在的商业虚拟机都采用这种收集算法**回收新生代**,因为新生代 GC 频繁并且对象的存活率不高, 但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间
1150111501
1150211502
1150311503
@@ -11713,7 +11713,7 @@ CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿
1171311713
1171411714- 初始标记:使用 STW 出现短暂停顿,仅标记一下 GC Roots 能直接关联到的对象,速度很快
1171511715- 并发标记:进行 GC Roots 开始遍历整个对象图,在整个回收过程中耗时最长,不需要 STW,可以与用户线程并发运行
11716- - 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录 ,比初始标记时间长但远比并发标记时间短,需要 STW(不停顿就会一直变化,采用写屏障 + 增量更新来避免漏标情况)
11716+ - 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象 ,比初始标记时间长但远比并发标记时间短,需要 STW(不停顿就会一直变化,采用写屏障 + 增量更新来避免漏标情况)
1171711717- 并发清除:清除标记为可以回收对象,**不需要移动存活对象**,所以这个阶段可以与用户线程同时并发的
1171811718
1171911719Mark Sweep 会造成内存碎片,不把算法换成 Mark Compact 的原因:Mark Compact 算法会整理内存,导致用户线程使用的**对象的地址改变**,影响用户线程继续执行
@@ -11785,7 +11785,7 @@ G1 对比其他处理器的优点:
1178511785- 空间整合:
1178611786
1178711787 - CMS:标记-清除算法、内存碎片、若干次 GC 后进行一次碎片整理
11788- - G1:整体来看是基于标记 - 整理算法实现的收集器 ,从局部(Region 之间)上来看是基于复制算法实现的,两种算法都可以避免内存碎片
11788+ - G1:整体来看是**基于标记 - 整理算法实现**的收集器 ,从局部(Region 之间)上来看是基于复制算法实现的,两种算法都可以避免内存碎片
1178911789
1179011790- **可预测的停顿时间模型(软实时 soft real-time)**:可以指定在 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒
1179111791
@@ -12544,7 +12544,7 @@ Java 对象创建时机:
1254412544
1254512545- 通过类的完全限定名称获取定义该类的二进制字节流(二进制字节码)
1254612546- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构(Java 类模型)
12547- - **在内存中生成一个代表该类的 Class 对象,作为该类在方法区中的各种数据的访问入口**
12547+ - **将字节码文件加载至方法区后,在堆中生成一个代表该类的 Class 对象,作为该类在方法区中的各种数据的访问入口**
1254812548
1254912549其中二进制字节流可以从以下方式中获取:
1255012550
@@ -12553,8 +12553,6 @@ Java 对象创建时机:
1255312553- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类
1255412554- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 生成字节码
1255512555
12556- 将字节码文件加载至方法区后,会**在堆中**创建一个 java.lang.Class 对象,用来引用位于方法区内的数据结构,该 Class 对象是在加载类的过程中创建的,每个类都对应有一个 Class 类型的对象
12557-
1255812556方法区内部采用 C++ 的 instanceKlass 描述 Java 类的数据结构:
1255912557
1256012558* `_java_mirror` 即 Java 的类镜像,例如对 String 来说就是 String.class,作用是把 class 暴露给 Java 使用
@@ -12641,7 +12639,7 @@ Java 对象创建时机:
1264112639 public static final int value = 123;
1264212640 ```
1264312641
12644- * Java 并不支持 boolean 类型,对于 boolean 类型,内部实现是 int,由于 int 的默认值是0 ,故 boolean 的默认值就是 false
12642+ * Java 并不支持 boolean 类型,对于 boolean 类型,内部实现是 int,由于 int 的默认值是 0 ,故 boolean 的默认值就是 false
1264512643
1264612644
1264712645
@@ -12794,7 +12792,7 @@ new 关键字会创建对象并复制 dup 一个对象引用,一个调用 <ini
1279412792
1279512793#### 卸载阶段
1279612794
12797- 时机:执行了 System.exit() 方法,程序正常执行结束,程序在执行过程中遇到了异常或错误而异常终止,由于操作系统出现错误而导致Java虚拟机进程终止
12795+ 时机:执行了 System.exit() 方法,程序正常执行结束,程序在执行过程中遇到了异常或错误而异常终止,由于操作系统出现错误而导致Java 虚拟机进程终止
1279812796
1279912797卸载类即该类的 **Class 对象被 GC**,卸载类需要满足3个要求:
1280012798
@@ -12944,7 +12942,7 @@ ClassLoader 类常用方法:
1294412942
1294512943- **全盘加载:**当一个类加载器负责加载某个 Class 时,该 Class 所依赖和引用的其他 Class 也将由该类加载器负责载入,除非显示指定使用另外一个类加载器来载入
1294612944
12947- - **双亲委派:**先让父类加载器加载该 Class,在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。简单来说就是, 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,**依次递归**,如果父加载器可以完成类加载任务,就成功返回;只有当父加载器无法完成此加载任务时,才自己去加载
12945+ - **双亲委派:**某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,**依次递归**,如果父加载器可以完成类加载任务,就成功返回;只有当父加载器无法完成此加载任务时,才自己去加载
1294812946
1294912947- **缓存机制:**会保证所有加载过的 Class 都会被缓存,当程序中需要使用某个 Class 时,类加载器先从缓存区中搜寻该 Class,只有当缓存区中不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转换成 Class 对象存入缓冲区(方法区)中
1295012948 - 这就是修改了 Class 后,必须重新启动 JVM,程序所做的修改才会生效的原因
@@ -12981,7 +12979,7 @@ ClassLoader 类常用方法:
1298112979 }
1298212980 ```
1298312981
12984- 此时执行 main 函数,会出现异常, 在类 java.lang.String 中找不到 main 方法,防止恶意篡改核心 API 库。出现该信息是因为双亲委派的机制 ,java.lang.String 的在启动类加载器(Bootstrap)得到加载,启动类加载器优先级更高,在核心 jre 库中有其相同名字的类文件,但该类中并没有 main 方法
12982+ 此时执行 main 函数会出现异常, 在类 java.lang.String 中找不到 main 方法。因为双亲委派的机制 ,java.lang.String 的在启动类加载器(Bootstrap)得到加载,启动类加载器优先级更高,在核心 jre 库中有其相同名字的类文件,但该类中并没有 main 方法
1298512983
1298612984双亲委派机制的缺点:检查类是否加载的委托过程是单向的,这个方式虽然从结构上看比较清晰,使各个 ClassLoader 的职责非常明确,但**顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类**(可见性)
1298712985
@@ -13056,9 +13054,9 @@ protected Class<?> loadClass(String name, boolean resolve)
1305613054 * 如果不想破坏双亲委派模型,只需要重写 findClass 方法
1305713055 * 如果想要去破坏双亲委派模型,需要去**重写 loadClass **方法
1305813056
13059- * 引入线程**上下文类加载器 **
13057+ * 引入**线程上下文类加载器 **
1306013058
13061- Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:
13059+ Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:
1306213060
1306313061 * SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的
1306413062 * SPI 的实现类是由系统类加载器加载,引导类加载器是无法找到 SPI 的实现类,因为双亲委派模型中 BootstrapClassloader 无法委派 AppClassLoader 来加载类
@@ -14471,14 +14469,9 @@ public static int invoke(Object... args) {
1447114469在 JVM 中,将符号引用转换为直接引用有两种机制:
1447214470
1447314471- 静态链接:当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变,将调用方法的符号引用转换为直接引用的过程称之为静态链接(类加载的解析阶段)
14474- - 动态链接:如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此被称为动态链接(初始化后的解析阶段)
14475-
14476- 对应方法的绑定(分配)机制:静态绑定和动态绑定。绑定是一个字段、方法或者类从符号引用被替换为直接引用的过程,仅发生一次:
14472+ - 动态链接:被调用的方法在编译期无法被确定下来,只能在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此被称为动态链接(初始化后的解析阶段)
1447714473
14478- - 静态绑定:被调用的目标方法在编译期可知,且运行期保持不变,将这个方法与所属的类型进行绑定
14479- - 动态绑定:被调用的目标方法在编译期无法确定,只能在程序运行期根据实际的类型绑定相关的方法
14480-
14481- * Java 编译器已经区分了重载的方法(静态绑定和动态绑定),因此可以认为虚拟机中不存在重载
14474+ * 对应方法的绑定(分配)机制:静态绑定和动态绑定,编译器已经区分了重载的方法(静态绑定和动态绑定),因此可以认为虚拟机中不存在重载
1448214475
1448314476非虚方法:
1448414477
@@ -14512,7 +14505,7 @@ public static int invoke(Object... args) {
1451214505普通调用指令:
1451314506
1451414507- invokestatic:调用静态方法
14515- - invokespecial:调用私有实例方法 、构造器,和父类的实例方法或构造器,以及所实现接口的默认方法
14508+ - invokespecial:调用私有方法 、构造器,和父类的实例方法或构造器,以及所实现接口的默认方法
1451614509- invokevirtual:调用所有虚方法(虚方法分派)
1451714510- invokeinterface:调用接口方法
1451814511
@@ -14544,9 +14537,6 @@ public static int invoke(Object... args) {
1454414537
1454514538在编译过程中,虚拟机并不知道目标方法的具体内存地址,Java 编译器会暂时用符号引用来表示该目标方法,这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符
1454614539
14547- * 对于静态绑定的方法调用而言,实际引用是一个指向方法的指针
14548- * 对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引
14549-
1455014540符号引用存储在方法区常量池中,根据目标方法是否为接口方法,分为接口符号引用和非接口符号引用:
1455114541
1455214542```java
@@ -14648,8 +14638,6 @@ Java 虚拟机中关于方法重写的判定基于方法描述符,如果子类
1464814638
14649146392. 如果在类型 C 中找到与描述符和名称都相符的方法,则进行访问**权限校验**(私有的),如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回 java.lang.IllegalAccessError 异常
1465014640
14651- IllegalAccessError:表示程序试图访问或修改一个属性或调用一个方法,这个属性或方法没有权限访问,一般会引起编译器异常。如果这个错误发生在运行时,就说明一个类发生了不兼容的改变
14652-
14653146413. 找不到,就会按照继承关系从下往上依次对 C 的各个父类进行第二步的搜索和验证过程
1465414642
14655146434. 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError 异常
@@ -14673,15 +14661,15 @@ Java 虚拟机中关于方法重写的判定基于方法描述符,如果子类
1467314661
1467414662虚方法表的执行过程:
1467514663
14676- * 对于静态绑定的方法调用而言,实际引用将指向具体的目标方法
14677- * 对于动态绑定的方法调用而言,实际引用则是方法表的索引值 ,也就是方法的间接地址。Java 虚拟机获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法内存偏移量(指针)
14664+ * 对于静态绑定的方法调用而言,实际引用是一个指向方法的指针
14665+ * 对于动态绑定的方法调用而言,实际引用是方法表的索引值 ,也就是方法的间接地址。Java 虚拟机获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法内存偏移量(指针)
1467814666
1467914667为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表。每个类中都有一个虚方法表,本质上是一个数组,每个数组元素指向一个当前类及其祖先类中非私有的实例方法
1468014668
1468114669方法表满足以下的特质:
1468214670
1468314671* 其一,子类方法表中包含父类方法表中的**所有方法**,并且在方法表中的索引值与父类方法表种的索引值相同
14684- * 其二,**非重写的方法指向父类的方法表项,与父类共享一个方法表项,重写的方法指向本身自己的实现**。所以这就是为什么多态情况下可以访问父类的方法 。
14672+ * 其二,**非重写的方法指向父类的方法表项,与父类共享一个方法表项,重写的方法指向本身自己的实现**,这就是为什么多态情况下可以访问父类的方法 。
1468514673
1468614674<img src="https://seazean.oss-cn-beijing.aliyuncs.com/img/Java/JVM-虚方法表.png" style="zoom: 80%;" />
1468714675
0 commit comments