zoukankan      html  css  js  c++  java
  • JVM类加载

    JVM类加载机制

    JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,如下图:
    Paste_Image.png

    由于本文主要讲解的是类的 加载 部分,所以加载,验证,准备,解析,初始化仅仅作下简单的回顾,详细内容参阅《深入理解Java虚拟机》

    加载

    类的加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类时,系统都会为之创建一个java.lang.Class对象。

    类也是一种对象,就像概念主要用于定义和描述其他的事物(反射中class含有各种信息),但概念本身也是一种事物。

    通常有下面几种来源 加载 类的二进制数据

    1. 从本地文件系统加载class文件
    2. 从jar包中加载class文件,如从F盘动态加载jdbc的mysql驱动。
    3. 通过网络加载(典型应用Applet)
    4. 把一个java源文件动态编译并加载
    5. 从zip包读取,如jar,war,ear。
    6. 运算时计算生成(动态代理技术)
    7. 数据库中读取(可以加密处理)
    8. 其他文件生成(jsp文件生成对应的class文件)
    验证

    这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

    准备

    准备阶段开始为 类变量(即static变量) 分配内存并设置 类变量的初始值(注意初始值一般为0,null也是零值)的 阶段,这些变量所使用的内存都将在方法区进行分配。

    注意
    这里进行内存分配的仅包含 《类变量》即static变量,而不包括实例变量不包括实例变量不包括实例变量,重要的事说3遍,实例变量将会在 对象实例化时随着对象一起分配在Java堆中。且初始值 通常情况 下 是数据类型 的零值

    Paste_Image.png

    public class Animal{
        private static int age = 20;
    }

    那变量age在 准备阶段 过后的初始值是0而不是20,因为这时候还没有执行任何Java方法。所以把age赋值为20的动作将在 初始化 阶段执行。

    再次注意
    存在一种特殊情况,如果上面的类变量声明为final的,则此时(准备阶段)就会被初始化为20。
    public static final int age = 20;
    编译时候,JavaC将会为age生成ConstantValue属性,在准备阶段 虚拟机就会根据ConstantValue的设置将age赋值为20。

    解析

    解析阶段是指虚拟机将常量池中的符号引用替换为直接引用(内存地址)的过程。

    这里简单说下常量池

    常量池 
    1. 字面量 比较接近Java语言层面,如String字符串,声明final的常量等
    2. 符号引用 属于编译原理方面的概念:1.包括类和接口的全限定名 2.字段的名称和描述符3.方法的名称和描述符

    符号引用大概是下面几种 类型

    1. CONSTANT_Class_info
    2. CONSTANT_Field_info
    3. CONSTANT_Method_info

    的常量。

    初始化

    类加载的最后一个阶段,除了加载阶段我们可以通过自定义类加载器参与之外,其余完全又JVM主导。到了初始化阶段,才真正开始执行类中定义的Java程序代码(字节码)

    这里需要区分下<init> 和<client>

    1. <init>指的是实例构造器,也就是构造函数
    2. <client>指的是类构造器,这个构造器是jvm自动合并生成的。
      它合并static变量的赋值操作(1. 注意是赋值操作,仅声明的不会触发<client>,毕竟前面准备阶段已经默认赋过值为0了,2. final static的也是这样哦)和static{ }语句块生成,且虚拟机保证<client>执行前,父类的<client>已经执行完毕,所以说父类如果定义static块的话,一定比子类先执行,当然了,如果一个类或接口中没有static变量的赋值操作和static{ }语句块,那么<client>也不会被JVM生成。最后还要注意一点,static变量的赋值操作和static{}语句块合并的顺序是由语句在源文件中出现的顺序所决定的。

    静态语句块只能访问定义在静态语句块之前的变量,定义在它之后的变量,前面的静态语句块只能赋值,不能访问。

    Paste_Image.png

    类初始化的时机

    当Java程序 首次主动通过下面6种方式使用某个类或接口时候,系统就会初始化该类或接口,假如这个类还没有被 加载和连接,则程序先加载并连接该类。类的初始化只会发生一次,再次使用new,callMethod等等都不会重复初始化。

    1. 生成类的实例,如(1)new (2)反射newInstance (3)序列化生成obj
    2. 调用static的方法,如LogUtil.i(TAG,"fucking");
    3. 访问类或接口的 static变量,或者为static变量赋值。注意有特例(一会说明)。
    4. Class.forName(name);
    5. 初始化某个类的子类,子类的所有父类都被初始化
    6. java.exe 运行Main类(public static void main),jvm会先初始化该主类。

    刚才说 3 有一个特例,需要特别指出,仍然是static的变量,前面说过,如果是 static final类型的则会在准备阶段 就给赋值并加入常量池。所以仅仅访问某个类的常量并不会导致该类初始化。

    class Person{ 
        public static int age = 20;
        static { 
            System.out.println("静态初始化!"); 
        }
    } 
    public class Test { 
        public static void main(String args[]){ 
            System.out.println(Person.age); 
        }
     }

    没有final修饰的情况打印

    静态初始化!
    20

    加上final修饰后打印

    20

    以下是不会执行类初始化的几种情况

    1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
    2. 定义对象数组,不会触发该类的初始化。
    3. 就是上面说的那种情况,类A引用类B的static final常量不会导致类B初始化。
    4. 通过类名获取Class对象,不会触发类的初始化。如
      System.out.println(Person.class);
    5. 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
    6. 通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

    JVM类加载机制算是结尾了,不过在参考其他文章时候发现一个非常棒的例子,可以很好的验证上面的结论。

    出处是 简书:小腊月

    public class Singleton{
        private static Singleton singleton = new Singleton();
        public static int counter1;
        public static int counter2 =0;
        private Singleton(){
          counter1++;
          counter2++;
      }
        public static Singleton getSingleton(){
            return singleton;
        }
    }
    public class Main{
        public static void main(String args[]){
            Singleton singleton = Singleton.getSingleton();
            System.out.println("counter1="+singleton.counter1);
            System.out.println("counter2="+singleton.counter2);
        }
    }

    根据 类初始化的时机 所作的结论

    1. 执行Main方法,根据结论6,会首先初始化Main类,Main类从(加载开始 ----> 初始化结束)
    2. 执行到Singleton.getSingleton();时候,根据结论2,直接 【先】 触发【类的初始化】初始化Singleton类,Singleton类首次初始化,所以从 加载部分开始执行,执行到 准备阶段 所有static变量都被设置为初始值。此时
      public static int counter1 = 0; 
      public static int counter2 = 0; 
      private static Singleton singleton = null;

      Singleton执行到初始化阶段,生成类构造器<client>,类构造器会合并 static变量的赋值操作和 static语句块。合并后执行

      public static int counter1 ;// 由于 counter1没被赋值,所以不会被合并进去
      public void client() {// 伪代码:<client>方法体内容
          Test singleton = new Test();//(1)
          int counter2 = 0;// (2)
      }

      4.**初始化阶段**执行client内代码,执行到(1)处,此时counter1counter2都变为15.**初始化阶段**执行client代码,执行到(2)处,counter2又被设置为06.**初始化结束**,回到Main方法的Singleton.getSingleton();继续执行main方法,最后输出结束。最后打印结果为:

    counter1= 1
    counter2= 0
    ## 详解类加载(第一阶段) 类加载部分是我们能够操作的部分,其他部分不需要我们管理。 
    Jvm启动时候默认至少开启了3个类加载器,分别是Bootstrap ClassLoader,Extension ClassLoader,Application ClassLoader各自加载各自管辖的区域。
    
    ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1281543-3f7c2473524f8307.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 
    
    1. 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。 
    2. 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOMElibext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
    3. 应用程序类加载器(Application ClassLoader)或者叫**System ClassLoader**:负责加载用户路径(classpath)上的类库。
    
    ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1281543-c86d07145d0bfadf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 
    
    >此程序说明:Java中getClassLoader用的一般和getSystemClassLoader是一个实例。源码中ClassLoader默认的构造器也说明这点,initSystemClassLoader里面会获取sun.misc.Launcher.getLauncher().getClassLoader()作为默认的parent。 
    **这里重点强调**:Android的ClassLoader类也有一个getSystemClassLoader()方法,但是又被改写了,后面再说明这个问题。 
            

    【出处】:https://www.imooc.com/article/21512

  • 相关阅读:
    Linux下制作和使用静态库和动态库
    C语言的内存管理
    C语言柔性数组
    大小端模式
    C位域操作
    C/C++字节对齐
    C/C++指针
    Linux之Socket编程
    VSCode配置FTP
    GCC的编译过程和链接
  • 原文地址:https://www.cnblogs.com/yelao/p/9627251.html
Copyright © 2011-2022 走看看