Java:类加载(内存分析,初始化分析,类加载器的作用)

类的加载

内存分析

Java内存

  • 堆:
    • 存放new的对象和数组
    • 可以被所有线程共享,不会存放别的对象引用
  • 栈:
    • 存在基本变量类型(包含具体数字)
    • 引用对象的变量(存放引用在堆里的具体地址)
  • 方法区
    • 包含所有的class和static变量
    • 可以被所有线程共享

类的加载过程

  1. 类的加载(Load):将class文件字节码内存加载到内存中,并将这些静态数据转换成方法去的运行时数据结构,然后生成一个代表该类的java.lang.Class对象(该对象本就存在,我们使用时是进行获取)
  2. 链接(Link):将Java类的二进制代码合并到JVM运行状态中的过程
    • 验证:确保加载的类信息符合JVM规范,没用安全性问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 初始化(Initialize):JVM对类进行初始化
    • 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造用于构造类信息)
    • 当初始化类的时候,如果发现其父类还没进行初始化,则需先对其父类进行初始化
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确枷锁和同步
public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
    }
}

class A{
    static int m = 100;

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

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

测试结果为:m的结果由A类中静态方法和静态变量的顺序决定。

A类静态代码块初始化
A的无参构造初始化
300

类加载内存

图片参考

类的初始化分析

  • 类的主动引用(发生类的初始化
    • 虚拟机启动,初始化mian方法所在类
    • new新对象
    • 调用类的静态成员(除final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 初始化类,如果父类没被初始化,会先初始化父类
  • 类的被动引用(不进行类的初始化
    • 访问静态域时,只有真正声明该域的类才会被初始化(如子类引用父类的静态变量,子类不初始化)
    • 通过数组定义类引用
    • 引用常量(常量在链接阶段就存入调用类的常量池中)

测试用例

类的定义:

class Parent{
    static {
        System.out.println("父类加载");
    }
    static int b = 0;
}

class Child extends Parent{
    static {
        System.out.println("子类加载");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}
  1. 类的主动引用:
public class Test06 {
    static {
        System.out.println("Main类加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1. 主动引用
        Child child = new Child();

        //反射
        Class.forName("com.chachan53.class13annotation.reflection.Child");
    }
}

测试结果:

Main类加载
父类加载
子类加载
  1. 类被动引用
public class Test06 {
    static {
        System.out.println("Main类加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //不产生类的引用
        System.out.println(Child.b);//Main类加载 父类加载 0
		//or
        Child[] array = new Child[3];//Main类加载
        //or
        System.out.println(Child.M);//Main类加载 1
    }
}

类加载器的作用

  • 作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在队中生成java.lang.Class对象,作为方法区中类数据的访问入口
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但类被加载到类加载器中,将维持加载(缓存)一段时间。JVM垃圾回收机制可以回收这些Class对象
  • 类加载器种类:
    • 引导类加载器:自带,负责Java平台核心库(rt.jar),无法直接获取
    • 扩展类加载器:负责jre/lib/ext目录下的 jar包或-D java.ext.dirs指定目录下的jar包装入工作库
    • 系统类加载器:复制java -classpath或 -D Java.class.path所指目录下的类与jar包装入工作,最常用
  • 双亲委派机制:创建新包时会检测向上的类加载器是否有相同名称的包,有该包无法使用
//获取系统类的加载器
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@74a14482

//获取扩展类加载器的父类加载器(根加载器,无法读取)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1); //null

//当前类的加载器:自定义类是用户加载器加载的
ClassLoader classLoader = Class.forName("com.chachan53.class13annotation.reflection.Test07").getClassLoader();
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@18b4aac2

//JDK内置类的加载器:根加载器,无法读取
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader); //null

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

学习参考:狂神说Java反射和注解

posted @ 2022-04-22 10:36  chachan53  阅读(59)  评论(0编辑  收藏  举报