JAVA类的生命周期

参考:

1.https://mp.weixin.qq.com/s?src=11&timestamp=1596419476&ver=2499&signature=kT5XnCOM5otjYKMHnxTWZKAd2vMEWUu5lGm9IyyvuLTUwYfexnSLDoBbKovrWodxdShLdcQ1q4oGaZIucCIxdmfmp8DrD8OhhxN2tKZk05huaX8Q6c*Aa5KNzFI4WILo&new=1

2.https://zhuanlan.zhihu.com/p/51029831

3.https://blog.csdn.net/qq_26440803/article/details/93675122

4.https://www.cnblogs.com/zhangpan1244/p/6197832.html

 

介绍下虚拟机:

  概念:JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

  介绍:Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,

            Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

            Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

 

这里就是简单了解下jvm执行过程和内部结构,想理解更多看参考,现在jvm可以解析的编程语言也不仅仅是java了,Groovy,JRuby等都是可以的

 

了解了虚拟机大致的工作过程,那么就开始了解下生命周期:

1.类的生命周期

   类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括:

   

 

 注:

   1.加载和连接是交叉进行的,不需要等加载完成,再连接

   2.解析可以在初始化之后再做。比如:Jvm仅仅规定了:如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。

   3.类的生命周期在初始化的时候有交叉,但整体的结构是按照这个固定顺序的

   4.这里的“加载阶段”和我们常说的“类加载”是两回事,“类加载”指的是虚线框中三部分加起来。

 

介绍下类的生命周期各阶段:

   1加载

     概念:查找字节流,以此来创建类的过程。是类加载过程的一个阶段,jvm需要完成以下事情:

         1)根据类的全限定名,获取类的二进制字节流

         2)  将这个字节流的静态存储结构转化成方法区的运行时数据结构

         3)  在内存中生成一个代表这个类的java.lang.Class对象,作为这个方法区这个类的各种数据的访问入口。

      类加载示意图

        

 

    总结:  从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。

                 另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。

   

    四种加载器:

          第一种:启动(Bootstrap)类加载器

                   加载对象:启动类加载器负责加载最为基础、最为重要的类。负责将 JAVA_HOME/lib 下面的类库加载到内存中(比如rt.jar)。

                   注意:1. 由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

                              2. 启动类加载器是由 C++ 实现的,没有对应的 Java 对象,因此在 Java 中只能用 null 来指代。

                              3. 除了启动类加载器之外,其他的类加载器都是 java.lang.ClassLoader 的子类,因此有对应的 Java 对象。

                              4. 这些类加载器需要先由另一个类加载器,比如说启动类加载器,加载至 Java 虚拟机中,方能执行类加载。 

 

          第二种:标准扩展(Extension)类加载器

                 加载对象:它负责加载相对次要、但又通用的类,负责将 JAVA_HOME/jre/lib/ext 或者由系统变量 java.ext.dirs指定位置中的类库加载到内存中。

 

          第三种:应用程序(Application)类加载器

                   加载对象:它负责将系统类路径(CLASSPATH) 中指定的类库加载到内存中。由于这个类加载器是ClassLoader中的 getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器

 

            第四种:自定义类加载器

                 加载对象:除了由 Java 核心类库提供的类加载器外,我们还可以加入自定义的类加载器,来实现特殊的加载方式。举例来说,我们可以对 class 文件进行加密,加载时再利用自定义的类加载器对其解密。

                   

         双亲委任

            工作流程:

                 (1)当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

                          注意:每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

                 (2)当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

                 (3)当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。

            

           好处:1.类的命名空间:类需要类的全限定名(类的全路径)以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。

                        比如:比如上面说的,我们 JDK 本生提供的类库,比如 string,hashmap,linkedlist 等等,这些类由bootstrp 类加载器加载了以后,

                                  无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。

                      2.避免病毒植入。

                          比如:自定义一个 java.lang.String 类,该 String 类具有系统的 String 类一样的功能,只是在某个函数稍作修改。

                                     这个函数经常使用,假如在这这个函数中植入一些“病毒代 码”。并且通过自定义类加载器加入到 JVM 中。完了,程序凉凉,这是比较直观的理解。

 

           双亲委任加载规则总结:检查自底而上,加载自上而下

 

    2.连接

      1)验证阶段

            工作:类被加载后必须要验证下类是否合法,比如这个类是不是符合字节码的格式,变量以及方法是不是重复,数据类型是否有效,继承和实现是否合乎标准等等。

            注意:我们平常写代码很多时候第一步都是写校验,jvm也是这个思路,Java 编译器生成的类文件必然满足 Java 虚拟机的约束条件,但是为了防止“解字节码注入”。

            有哪些需要检验:

                  (1)文件格式验证(主要验证是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。)

                           总结:基于二进制字节流的检验,只有通过了文件格式验证,节点流才能进入内存的方法区存储,所以后面的检验都是基于方法区的存储结构进行的,不再操作字节流

                   (2)元数据验证(对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范要求)

                            例如:这个类是否有父类(除了java.lang.Object是所有类的父类),如果这个类不是抽象类是否实现了父类或者接口中要求实现的所有方法等。

                  (3)字节码验证

                  (4)符号引用验证(发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段解析阶段中发生)

                           比如:验证符号引用中通过字符串描述的全限定名是否能找到对应的类。

             2)准备阶段

                  工作:就是为类的静态变量分配内存并设为 jvm 默认的初值,而不是我们设置的,我们设置的会在后面一个阶段“初始化”期间来做,对于非静态的变量,则不会为它们分配内存。

                  注意:1. 静态变量参考:https://www.cnblogs.com/zijeak/p/11237003.html,https://blog.csdn.net/u012613144/article/details/88352779

                             2.所谓的静态变量在这里就可以很好的解释了,它是被类的所有的实例对象共享的,也就是说一但静态变量发生改变,那么所有的实例对象都会相应的发生改变

                  jvm给静态成员变量的默认值:

                            1.基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。其中boolean只有true,false两种类型,对应到jvm值分别是数据1,0。

                            2.引用类型(对象,数组)的默认值为null。

                  注意: 在 class 文件被加载至 Java虚拟机之前,这个类无法知道其他类及其方法、字段所对应的具体地址,甚至不知道自己方法、字段的地址。

                              因此,每当需要引用这些成员时,Java 编译器会生成一个符号引用。在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上(因为验证阶段进行符号引用验证了)。

                               比如:public static final int value=123,常量直接赋值为设置的123.

 

               3) 解析阶段

                     上面说到的“在运行阶段,这个符号引用一般都能够无歧义地定位到具体目标上”,就是在解析阶段进行的符号解析。

                      这个阶段目的正是将常量池中的符号引用转换解析成为实际引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址,从而让用到了别的类或者接口的类能找到和加载其他的类/接口。

                      如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化)

 

       3.初始化

 

         目的:在 Java 代码中,如果要初始化一个静态字段,我们可以在声明时直接赋值,也可以在静态代码块中对其赋值。除了 final static 修饰的常量,

                   直接赋值操作以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命名为 < clinit >。 类加载的最后一步是初始化,目的便是为标记为常量值的字段赋值,

                   以及执行< clinit > 方法的过程。Java 虚拟机会通过加锁来确保类的 < clinit > 方法仅被执行一次。

         类初始化触发情况:

                 1. 当虚拟机启动时,初始化用户指定的主类(main函数);

                 2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;

                 3.当遇到调用静态方法的指令时,初始化该静态方法所在的类;

                4.子类的初始化会触发父类的初始化;

                5.如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;

                6.使用反射 API 对某个类进行反射调用时,初始化这个类;

                7.当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(设计模式中单例延迟加载,便是充分利用了这个特点。)

 

       4.卸载

           卸载类需要满足的条件

             1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;

             2.加载该类的ClassLoader已经被回收;

             3.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

             4.上条件都满足了,垃圾回收时候回在方法区清空类信息进行卸载

posted @ 2020-08-03 11:30  小窝蜗  阅读(289)  评论(0编辑  收藏  举报