diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md index 81c5dff0..2cf8718b 100644 --- a/notes/Java 虚拟机.md +++ b/notes/Java 虚拟机.md @@ -1,9 +1,9 @@ * [一、运行时数据区域](#一运行时数据区域) * [程序计数器](#程序计数器) - * [Java 虚拟机栈](#java-虚拟机栈) + * [虚拟机栈](#虚拟机栈) * [本地方法栈](#本地方法栈) - * [Java 堆](#java-堆) + * [堆](#堆) * [方法区](#方法区) * [运行时常量池](#运行时常量池) * [直接内存](#直接内存) @@ -27,17 +27,17 @@ # 一、运行时数据区域 -

+

## 程序计数器 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。 -## Java 虚拟机栈 +## 虚拟机栈 -每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 +每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 -

+

可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小: @@ -56,13 +56,13 @@ java -Xss=512M HackTheJava 与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 -

+

-## Java 堆 +## 堆 所有对象实例都在这里分配内存。 -是垃圾收集的主要区域("GC 堆 "),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块: +是垃圾收集的主要区域("GC 堆"),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块: - 新生代(Young Generation) - 老年代(Old Generation) @@ -102,7 +102,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用 ## 直接内存 -在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 +在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 # 二、垃圾收集 @@ -221,11 +221,9 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。 不足: -1. 标记和清除过程效率都不高 +1. 标记和清除过程效率都不高; 2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存。 -之后的算法都是基于该算法进行改进。 - ### 2. 复制

@@ -394,7 +392,7 @@ JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 ### 4. 动态对象年龄判定 -JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。 +JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等待 MaxTenuringThreshold 中要求的年龄。 ### 5. 空间分配担保 @@ -418,7 +416,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才 ### 4. JDK 1.7 及以前的永久代空间不足 -在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。 +在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。 ### 5. Concurrent Mode Failure @@ -448,7 +446,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生): -1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 +1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。 @@ -507,19 +505,19 @@ System.out.println(ConstClass.HELLOWORLD); 主要有以下 4 个阶段: -**(一)文件格式验证** +(一)文件格式验证 验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。 -**(二)元数据验证** +(二)元数据验证 对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。 -**(三)字节码验证** +(三)字节码验证 通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。 -**(四)符号引用验证** +(四)符号引用验证 发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。 @@ -610,11 +608,11 @@ public static void main(String[] args) { 从 Java 开发人员的角度看,类加载器可以划分得更细致一些: -- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。 +- 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。 -- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。 +- 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。 -- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 +- 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 ### 3. 双亲委派模型 @@ -634,7 +632,7 @@ public static void main(String[] args) { ```java protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ - //check the class has been loaded or not + // 先检查请求的类是否已经被加载过了 Class c = findLoadedClass(name); if(c == null) { try{ @@ -644,9 +642,10 @@ protected synchronized Class loadClass(String name, boolean resolve) throws C c = findBootstrapClassOrNull(name); } } catch(ClassNotFoundException e) { - //if throws the exception , the father can not complete the load + // 如果父类加载器抛出 ClassNotFoundException,说明父类加载器无法完成加载请求 } if(c == null) { + // 如果父类加载器无法完成加载请求,再调用自身的 findClass() 来进行加载 c = findClass(name); } } diff --git a/pics/540631a4-6018-40a5-aed7-081e2eeeaeea.png b/pics/540631a4-6018-40a5-aed7-081e2eeeaeea.png new file mode 100644 index 00000000..e22b2c83 Binary files /dev/null and b/pics/540631a4-6018-40a5-aed7-081e2eeeaeea.png differ diff --git a/pics/f5757d09-88e7-4bbd-8cfb-cecf55604854.png b/pics/f5757d09-88e7-4bbd-8cfb-cecf55604854.png new file mode 100644 index 00000000..1664247b Binary files /dev/null and b/pics/f5757d09-88e7-4bbd-8cfb-cecf55604854.png differ