类的加载及初始化

一、类加载内存分析

类的加载过程:

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

·验证:确保加载的类信息符合JVM规范,没有安全方面的问题

·准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。

·解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化:
·执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态

·代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

·虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

//类的加载
public class Test {

    /**
     * 1、加载到内存,会产生一个类对应class对象
     * 2、链接,结束后给m附初始值
     * 3、初始化
     *      <clinit>(){
     *          System.out.println("A类静态代码块初始化");
     *          m = 300;
     *          m =100;
     *      }
     *      m = 100
     *      a = 100
     */

    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);
    }
}

class A {
    static {
        System.out.println("A类静态代码块初始化");
        m = 300;
    }

    static int m = 100;

    public A(){
        System.out.println("A类的无参构造初始化");
    }
}

内存图分析

二、类的初始化

类的主动引用(一定会发生类的初始化)

·当虚拟机启动,先初始化main方法所在的类

·new一个类的对象

·调用类的静态成员(除了final常量)和静态方法

·使用java.lang.reflect包的方法对类进行反射调用

·当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

类的被动引用(不会发生类的初始化)

·当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化

·通过数组定义类引用,不会触发此类的初始化

·引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

//测试类什么时候初始化
public class Test {
    static {
        System.out.println("main方法类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        /**
         * 1、主动引用
         *  main方法类被加载
         *  父类被加载了
         *  子类被加载了
         *
         *  Son son = new Son();
         */

        //反射也会产生主动调用
        //Class.forName("com.qf.test.Son");

        //不会产生类的引用方法
        /**
         * 1、子类调用父类方法
         * 结果:main方法类被加载
         *      父类被加载了
         *      2
         * 子类并没有被初始化
         *
         * System.out.println(Son.b);
         */

        /**
         * 2、创建数组
         * 结果:main方法类被加载
         * 只有main类在虚拟机启动被加载,创建数组空间并不会加载类
         *
         * Son[] sons = new Son[5];
         */

        /**
         * 3、调用类里的常量
         * 结果:main方法类被加载
         *      1
         * 常量在链接阶段就被初始化在常量池中
         */
        System.out.println(Son.M);
    }
}

class Father{
    static int b = 2;
    static {
        System.out.println("父类被加载了");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载了");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}

三、类加载器

类加载的作用:

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:

标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。

BootstrapClassLoader(启动类加载器):

用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取

ExtClassLoader (标准扩展类加载器):

负责jre/lib/ext目录下的jar包或-Djava.ext.dirs指定目录下的jar包装入工作库

AppClassLoader(系统类加载器):

负责java -classpath或-Djava.class.path所指的目录下的类与jar包装入工作,是最常用的加载器

CustomClassLoader(用户自定义类加载器)

java编写,用户自定义的类加载器,可加载指定路径的class文件

//类加载器
public class Test {
 public static void main(String[] args) throws ClassNotFoundException {
     //获取系统类的加载器
     ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
     System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

     //获取系统类加载器的父类加载器-->扩展类加载器
     ClassLoader parent = systemClassLoader.getParent();
     System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@7eda2dbb

     //获取扩展类加载器的父类加载器-->根加载器(C/C++)
     ClassLoader parent1 = parent.getParent();
     System.out.println(parent1);//null

     //测试当前类是哪个加载器
     ClassLoader classLoader = Class.forName("com.qf.test.Test").getClassLoader();
     System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

     //测试JDK内置的类是哪个加载器
     ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
     System.out.println(classLoader1);//null

     //如何获取系统类加载器可以加载的路径
     System.out.println(System.getProperty("java.class.path"));

     /*
         F:\JAVA\jdk1.8.0_144\jre\lib\charsets.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\deploy.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\dnsns.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\jaccess.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\localedata.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\nashorn.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunec.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\ext\zipfs.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\javaws.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\jce.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\jfr.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\jfxswt.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\jsse.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\management-agent.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\plugin.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\resources.jar;
         F:\JAVA\jdk1.8.0_144\jre\lib\rt.jar;
         ............
      */
 }
}

双亲委派机制

简介:

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

作用:

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。

2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

原码分析:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // 首先检查这个classsh是否已经加载过了
      Class<?> c = findLoadedClass(name);
      if (c == null) {
          long t0 = System.nanoTime();
          try {
              // c==null表示没有加载,如果有父类的加载器则让父类加载器加载
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  //如果父类的加载器为空 则说明递归到bootStrapClassloader了
                  //bootStrapClassloader比较特殊无法通过get获取
                  c = findBootstrapClassOrNull(name);
              }
          } catch (ClassNotFoundException e) {}
          if (c == null) {
              //如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
              long t1 = System.nanoTime();
              c = findClass(name);
              sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
              sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
              sun.misc.PerfCounter.getFindClasses().increment();
          }
      }
      if (resolve) {
          resolveClass(c);
      }
      return c;
  }
}

注:

​ 双亲委派机制转自:https://www.jianshu.com/p/1e4011617650

posted @ 2021-03-13 09:35  憨憨守护者  阅读(1197)  评论(0)    收藏  举报