【学习笔记】反射(二)之类加载

反射(二)之类加载

 

类加载内存分析

image-20220804143534184

 

当程序主动使用某个类时,如果该类还未被加载到内存中,则该系统会通过如下三个步骤;来对该类进行初始化。

image-20220804143757208

 

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

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

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

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

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

  • 初始化:

    • 执行类构造器<clint>()方法的过程。类构造器 <clint>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)

    • 当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化

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

 

代码测试:

package com.reflection;
​
public class Demo04 {
    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 类无参构造初始化");
    }
}

image-20220804145359518

 

结果是m的值为100,我们从内存来分析

 

  1. 类写好了编译成class,加载到内存中,包含类的一些信息,都是在方法区中

image-20220804150455856

 

  1. 加载完成立马生成Class对象,这个对象就已经包含这个类的所有东西了

image-20220804150913954

  1. 形成之后,就执行main()方法,首先m = 0,因为在链接阶段的准备过程时,

为类变量即static 分配内存并设置默认的值

image-20220804151400056

 

  1. 准备过程完成之后,我们 new A() 生成一个A类的对象,这个A类的对象指向A类的Class,就会得到A类的所有数据

image-20220804151813972

 

  1. 得到这些数据,就可以给A类显示赋值,就进入初始化阶段,当进入初始化阶段,A类就执行了<clinit>() 方法,这个方法会把静态代码块和类变量都合并,合并之后代码就成了这个样子:

    m = 300;

    m = 100;

    所以上面静态代码块中的 m = 300; 被 m = 100;覆盖,所以m的值为100

    image-20220804152414733

 

 

什么时候会发生类初始化?

我们先写一段测试代码

package com.reflection;
​
public class Demo05 {
    static {
        System.out.println("Main被初始化");
    }
​
    public static void main(String[] args) {
        
    }
}
​
class Father{
    static{
        System.out.println("父类被初始化");
    }
    static int b = 1;
}
class Son extends Father{
    static {
        System.out.println("子类被初始化");
        m = 300;
    }
    static int m = 100;
    static final int A = 0;
}

 

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

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

  2. new 一个类的对象

public static void main(String[] args) {
    Son son = new Son();
}

image-20220804154208605

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

System.out.println(Son.m);

image-20220804154349268

 

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

Class.forName("com.reflection.Son");

image-20220804154602230

 

  1. 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类,从上面的例子可以看出

 

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

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

System.out.println(Son.b);

image-20220804154937340

 

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

Son[] sons = new Son[5];

image-20220804155057658

 

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

System.out.println(Son.A);

image-20220804155249913

 

 

类加载器的作用

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

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

  • 类加载器的作用时用来把类装载进内存的,JVM定义了如下类型的类的加载器:

    • 引导类加载器(根加载器):用C++编写的,是JVM自带的类加载器,负责Java平台核心库(rt.jar),用来装载核心类库,该类加载器无法直接获取

    • 扩展类加载器:负责jre/lib/ext目录下的jar包或-D java.ext.dirs 指定目录下的jar包装入工作库

    • 系统类加载器(用户类加载器):负责 java -classpath 或 -D java.class.path所指目录下的类与jar包装入工作,是最常用的加载器。

  • 引导类加载器是扩展类加载器的父类,扩展类加载器是系统类加载器的父类

 

下面我们通过代码来看一下这些加载器:

package com.reflection;
​
public class Demo06 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
​
        //获取系统类加载器的父类加载器--->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
​
        //获取扩展类加载器的父类加载器 ---> 根加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
    }
}

image-20220804165054709

系统类/用户类加载器:AppClassLoader

扩展类加载器:ExtClassLoader

根加载器:null

我们无法直接获取根加载器,所以打印的结果是null

 

测试类是哪个加载器加载的

//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.reflection.Demo06").getClassLoader();
System.out.println(classLoader);
​
//测试JDK内置的类是哪个加载器加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);

image-20220804165803115

 

我们自己写的类是由系统类加载器加载的,JDK内置的类是由根加载器加载的

 

//如何获得系统类加载器可以加载的路径

//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
/*
D:\Program Files\Java\jdk1.8.0_201\jre\lib\charsets.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\deploy.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\dnsns.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jaccess.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\localedata.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\nashorn.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunec.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\ext\zipfs.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\javaws.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\jce.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\jfr.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\jfxswt.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\jsse.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\management-agent.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\plugin.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\resources.jar;
D:\Program Files\Java\jdk1.8.0_201\jre\lib\rt.jar;
E:\Growth\code\JavaSE\out\production\基础语法;
E:\Growth\code\JavaSE\基础语法\src\lib\commons-io-2.11.0.jar;
E:\JAVA\IDEA\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar
​
 */

 

双亲委派机制:

如果你写了一个包和根加载器及其他扩展中的包重复了,它会用根加载器里的包,而不会去用你自己写的包,为了保证安全性

posted @ 2022-08-04 17:06  GrowthRoad  阅读(23)  评论(0编辑  收藏  举报