diff --git "a/docs/notes/Java \350\231\232\346\213\237\346\234\272.md" "b/docs/notes/Java \350\231\232\346\213\237\346\234\272.md" index 023b3831ef04f49ce42b8e4524985a33999c85e3..8a903300b841f38cb06159107e127a7521ddcd9f 100644 --- "a/docs/notes/Java \350\231\232\346\213\237\346\234\272.md" +++ "b/docs/notes/Java \350\231\232\346\213\237\346\234\272.md" @@ -103,9 +103,7 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会 # 二、垃圾收集 -垃圾收集主要是针对堆和方法区进行。 - -程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。 +垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。 ## 判断一个对象是否可被回收 @@ -113,7 +111,7 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会 为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。 -在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。 +在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。 ```java public class Test { @@ -131,9 +129,9 @@ public class Test { ### 2. 可达性分析算法 -将 GC Roots 作为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。 +以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。 -Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容: +Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般包含以下内容: - 虚拟机栈中局部变量表中引用的对象 - 本地方法栈中 JNI 中引用的对象 @@ -148,9 +146,9 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC 主要是对常量池的回收和对类的卸载。 -为了避免内存溢出,在大量使用反射、动态代理的场景都需要虚拟机具备类卸载功能。 +为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。 -类的卸载条件很多,需要满足以下三个条件,但是满足了也不一定会被卸载: +类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载: - 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。 - 加载该类的 ClassLoader 已经被回收。 @@ -158,7 +156,7 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC ### 4. finalize() -类似 C++ 的析构函数,用于关闭外部资源。try-finally 等方式可以做的更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 +类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。 @@ -464,9 +462,10 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和 加载过程完成以下三件事: -- 通过一个类的全限定名来获取定义此类的二进制字节流。 -- 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。 -- 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。 +- 通过类的完全限定名称获取定义该类的二进制字节流。 +- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。 +- 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。 + 其中二进制字节流可以从以下方式中获取: @@ -483,9 +482,7 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 -实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在堆中。 - -注意,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。 +实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。 初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。 @@ -493,7 +490,7 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和 public static int value = 123; ``` -如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。 +如果类变量是常量,那么它将初始化为表达式所定义的值而不是 0。例如下面的常量 value 被初始化为 123 而不是 0。 ```java public static final int value = 123; @@ -505,9 +502,15 @@ public static final int value = 123; 其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 +
+ ### 5. 初始化 -初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。 +
+ +初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。 + +在准备阶段,已经为类变量分配了系统所需的初始值,并且在初始化阶段,根据程序员通过程序进行的主观计划来初始化类变量和其他资源。 <clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码: @@ -604,23 +607,25 @@ System.out.println(ConstClass.HELLOWORLD); - 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 +
+ ## 双亲委派模型 -应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。 +应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。 -下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都要有自己的父类加载器。这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。 +下图展示了类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。类加载器之间的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。 -

+

### 1. 工作过程 -一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试自己加载。 +一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。 ### 2. 好处 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 -例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 的类并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。 +例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。 ### 3. 实现 @@ -674,7 +679,7 @@ public abstract class ClassLoader { FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader,用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。 -java.lang.ClassLoader 的 loadClass() 实现了双亲委派模型的逻辑,因此自定义类加载器一般不去重写它,但是需要重写 findClass() 方法。 +java.lang.ClassLoader 的 loadClass() 实现了双亲委派模型的逻辑,自定义类加载器一般不去重写它,但是需要重写 findClass() 方法。 ```java public class FileSystemClassLoader extends ClassLoader { diff --git a/docs/notes/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png b/docs/notes/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png new file mode 100644 index 0000000000000000000000000000000000000000..382cc19b1c4b8ff09c73309876fb63db965c12af Binary files /dev/null and b/docs/notes/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png differ diff --git a/docs/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png b/docs/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png new file mode 100644 index 0000000000000000000000000000000000000000..382cc19b1c4b8ff09c73309876fb63db965c12af Binary files /dev/null and b/docs/pics/805812fa-6ab5-4b8f-a0aa-3bdcadaa829d.png differ