@@ -143,10 +143,11 @@ java -Xss=512M HackTheJava
143143该区域可能抛出以下异常:
144144
145145- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
146-
147146- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
148147
149148
149+
150+
150151### 3. 本地方法栈(线程私有)
151152
152153本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
@@ -717,7 +718,7 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
717718
718719其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
719720
720- 这7个阶段中的 :加载、验证、准备、初始化、卸载的顺序是固定的。但它们并不一定是严格同步串行执行,它们之间可能会有交叉,但总是以 “开始” 的顺序总是按部就班的。至于解析则有可能在初始化之后才开始,这是为了支持 Java 语言的运行时绑定(也称为动态绑定或晚期绑定)。
721+ 这 7 个阶段中的 :加载、验证、准备、初始化、卸载的顺序是固定的。但它们并不一定是严格同步串行执行,它们之间可能会有交叉,但总是以 “开始” 的顺序总是按部就班的。至于解析则有可能在初始化之后才开始,这是为了支持 Java 语言的运行时绑定(也称为动态绑定或晚期绑定)。
721722
722723### 类初始化时机
723724
@@ -763,13 +764,11 @@ System.out.println(ConstClass.HELLOWORLD);
763764
764765加载是类加载的一个阶段,注意不要混淆。
765766
766-
767-
768767加载过程完成以下三件事:
769768
770- - 通过一个类的全限定名来获取定义此类的二进制字节流。
771- - 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。
772- - 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
769+ - 通过一个类的全限定名来获取定义此类的二进制字节流
770+ - 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构
771+ - 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口
773772
774773
775774
@@ -779,7 +778,7 @@ System.out.println(ConstClass.HELLOWORLD);
779778- ** 网络** :从网络中获取,这种场景最典型的应用是 Applet。
780779- ** 计算生成一个二进制流** :运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
781780- ** 由其他文件生成** :由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。
782- - ** 数据库** :从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 ...
781+ - ** 数据库** :从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
783782
784783
785784
@@ -788,11 +787,12 @@ System.out.println(ConstClass.HELLOWORLD);
788787目的:确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
789788
790789- 文件格式验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
791- - 是否以0xCAFEBABE开头
792- - 版本号是否合理
790+ - 是否以0xCAFEBABE开头,前四个字节为魔数
791+ - 版本号是否合理,如:JDK1.8(52.0)、JDK1.7(51.0)
792+
793793- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
794794 - 是否有父类-继
795- - 承了final类 ?
795+ - 继承了 final 类 ?
796796 - 非抽象类实现了所有的抽象方法
797797- 字节码验证(很复杂):通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
798798 - 运行检查
@@ -806,21 +806,19 @@ System.out.println(ConstClass.HELLOWORLD);
806806
807807#### 3. 准备
808808
809- 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
810-
811- 实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在堆中。
809+ 准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配。类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
812810
813- 注意,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
811+ 实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在堆中。 注意,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
814812
815- 初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123,在初始化的<clinit >中才会被设置为1。
813+ 初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123,在初始化的 \ < clinit> 中才会被设置为1。
816814
817815- 默认值:int 0, boolean false, float 0.0, char '0', 抽象数据类型 null
818816
819817``` java
820818public static int value = 123 ;
821819```
822820
823- 对于static final类型 ,在准备阶段就会被赋上正确的值
821+ - 对于 static final 类型 ,在准备阶段就会被赋上正确的值
824822
825823``` java
826824public static final int value = 123 ;
@@ -832,14 +830,7 @@ public static final int value = 123;
832830
833831#### 4. 解析
834832
835- ** 什么是符号引用和直接引用?**
836-
837- - ** 符号引用** :符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
838- - ** 直接引用** :直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
839-
840-
841-
842- 将常量池的符号引用替换为直接引用的过程
833+ 解析阶段是虚拟机将常量池的符号引用替换为直接引用的过程
843834
844835- 类或接口的解析
845836- 字段解析
@@ -848,17 +839,26 @@ public static final int value = 123;
848839
849840
850841
851- 更多详情转向:[ 【必读】JVM 类加载机制 - 掘金] ( https://juejin.im/post/5a1d644551882534af25b381 )
842+ ** 什么是符号引用和直接引用?**
843+
844+ - ** 符号引用** :符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
845+ - ** 直接引用** :直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
846+
847+
848+
849+ 参考资料:
850+
851+ - [ 【必读】JVM 类加载机制 - 掘金] ( https://juejin.im/post/5a1d644551882534af25b381 )
852852
853853
854854
855855#### 5. 初始化
856856
857- 初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit >() 方法的过程。
857+ 初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 \ < clinit>() 方法的过程。
858858
859859在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
860860
861- <clinit >() 方法具有以下特点:
861+ \ < clinit>() 方法具有以下特点:
862862
863863- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
864864
@@ -872,8 +872,8 @@ public class Test {
872872}
873873```
874874
875- - 与类的构造函数(或者说实例构造器 <init >())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit >() 方法运行之前,父类的 <clinit >() 方法已经执行结束。因此虚拟机中第一个执行 <clinit >() 方法的类肯定为 java.lang.Object。
876- - 由于父类的 <clinit >() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码:
875+ - 与类的构造函数(或者说实例构造器 \ < init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 \ < clinit>() 方法运行之前,父类的 \ < clinit>() 方法已经执行结束。因此虚拟机中第一个执行 \ < clinit>() 方法的类肯定为 java.lang.Object。
876+ - 由于父类的 \ < clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码:
877877
878878``` java
879879static class Parent {
@@ -892,17 +892,17 @@ public static void main(String[] args) {
892892}
893893```
894894
895- - <clinit >() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit >() 方法。
896- - 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit >() 方法。但接口与类不同的是,执行接口的 <clinit >() 方法不需要先执行父接口的 <clinit >() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit >() 方法。
897- - 虚拟机会保证一个类的 <clinit >() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit >() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit >() 方法完毕。如果在一个类的 <clinit >() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
895+ - \ < clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 \ < clinit>() 方法。
896+ - 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 \ < clinit>() 方法。但接口与类不同的是,执行接口的 \ < clinit>() 方法不需要先执行父接口的 \ < clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 \ < clinit>() 方法。
897+ - 虚拟机会保证一个类的 \ < clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 \ < clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 \ < clinit>() 方法完毕。如果在一个类的 \ < clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
898898
899899
900900
901901
902902
903903### 类加载器
904904
905- 虚拟机设计团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流( 即字节码) ” 这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类(通过一个类的全限之名获取描述此类的二进制字节流)。实现这个动作的代码模块称为 ** “类加载器”** 。
905+ 虚拟机设计团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流( 即字节码) ” 这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类(通过一个类的全限之名获取描述此类的二进制字节流)。实现这个动作的代码模块称为 ** “类加载器”** 。
906906
907907
908908
@@ -912,6 +912,8 @@ public static void main(String[] args) {
912912
913913这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
914914
915+
916+
915917#### 2. 类加载器分类
916918
917919从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
@@ -923,16 +925,19 @@ public static void main(String[] args) {
923925
924926从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
925927
926- - 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
927- - 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
928- - 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
929- - 自定义类加载器
928+ - ** 启动类加载器** (Bootstrap ClassLoader)
929+ - 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
930+ - ** 扩展类加载器** (Extension ClassLoader)
931+ - 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
932+ - ** 应用程序类加载器** (Application ClassLoader)
933+ - 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
934+ - ** 自定义类加载器**
930935 - 载器步骤:
931- - 定义一个类,继承ClassLoader
936+ - 定义一个类,继承 ClassLoader
932937 - 重写 loadClass 方法
933938 - 实例化 Class 对象
934939 - 自定义类加载器的优势
935- - 类加载器是java语言的一项创新,也是java语言流行的重要原因之一,它最初的设计是为了满足java applet 的需求而开发出来的
940+ - 类加载器是 Java 语言的一项创新,也是 Java 语言流行的重要原因之一,它最初的设计是为了满足 java applet 的需求而开发出来的
936941 - 高度的灵活性
937942 - 通过自定义类加载器可以实现热部署
938943 - 代码加密
@@ -949,21 +954,19 @@ public static void main(String[] args) {
949954
950955JVM 如何加载一个类的过程,双亲委派模型中有哪些方法有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类?
951956
952- - 双亲委派机制图
957+ - ** 双亲委派机制图**
953958
954959<div align =" center " > <img src =" assets/d330251551f6de988239494ce2773095.png " width =" 450 " /> </div ><br >
955960
956- - 双亲委派概念
957-
961+ - ** 双亲委派概念**
958962 - 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
959- - 加载器
960-
963+ - ** 加载器**
961964 - 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
962965 - 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
963- - 系统(System)类加载器:由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
966+ - 系统(System)类加载器:由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
964967- 如果加载同一个类,该使用哪一个类?
965968
966- - 父类的
969+ - 父类的
967970
968971
969972
0 commit comments