JVM(三): 类加载

123 阅读3分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

类装载子系统

  • 类装载子系统负责从文件系统或网络中加载.class文件
  • 把加载后的Class文件存放在方法区中
  • ClassLoader负责class文件的加载,是否可以运行,由Execution Engine决定
  • 如果调用构造器实例化对象, 对象存在堆中

类加载过程

加载

预加载

虚拟机启动时加载,加载的是/lib/rt.jar下的.Class文件。

-XX: +TraceClassLoading

运行时加载

用到的时候, 去内存查看.class文件是否被加载, 没有的话就会按照类的全限定名来加载。

  • 获取.class的二进制流
  • 将类信息,静态变量, 字节码, 常量这些.class文件放到方法区
  • 在内存中生成Class对象,作为访问入口, HotSpot的Class对象放在方法区中
连接

验证

确保.class文件的字节流中的信息符合虚拟机要求

  • 文件格式
  • 元数据
  • 字节码
  • 符号引用

准备

正式为类变量分配内存,设置初始值, 都是在方法区分配

  • 赋初始值的变量是不被final修饰的static变量, 如果是final修饰的话, 直接赋值为最终值。

解析

将常量池中的符号引用替换为直接引用

  • 类或接口的解析
  • 类方法的解析
  • 接口方法的解析
  • 字段解析
初始化

初始化阶段执行类构造器方法的过程,是编译器自动收集类的所有类变量的赋值和静态语句块的语句合并产生的, 静态语句块只能访问到定义在静态语句块之前的变量, 后面的变量只能赋值,无法访问。

类的方法不用显式调用父类构造器, 虚拟机保证在子类的方法执行前,先执行父类的方法。

接口的方法不需要先执行父接口的方法。接口的实现类在初始化时也不会执行接口的方法

JVM保证一个类的方法在多线程环境中被正确的加锁同步。

类加载器

JVM只有程序第一次主动使用类的时候才会去加载类。支持两种类型: 引导类加载器和自定义类加载器。引导类加载器是由c/c++实现,自定义类加载器由java实现。jvm规范自定义类加载器派生ClassLoader。

主要有引导类加载器BootStrapClassLoader, 自定义类加载器Extension Class Loader, System Class Loader .

启动类加载器
  • 使用c/c++实现, 嵌套在jvm内部
  • 加载核心类库(lib/rt.jar, resource.jar, sum.boot.class.path),用于提供JVM自身需要的类
  • 不继承ClassLoader
扩展类加载器
  • sun.misc.Launcher$ExtClassLoader实现
  • java.ext.dirs属性指定的目录中加载类库, 或从jre/lib/ext子目录下加载类库, 派生ClassLoader
  • 父类加载器为启动类加载器
系统类加载器
  • sun.misc.Launcher$AppClassLoader实现
  • 默认类加载器, 加载环境变量classpath或java.class.path的类库,派生ClassLoader
  • 父类加载器为系统类加载器
  • ClassLoader#getSystemClassLoader方法可以获取到
自定义类加载器
  • 隔离加载类
  • 修改类加载方式
  • 扩展加载源
  • 防止源码泄露
public class MyClassLoader extent ClassLoader{
    
    private String path;
    
    public MyClassLoader(ClassLoader parent, String path){
        super(parent);
        this.path = path;
    }
    public MyClassLoader(String path){
        this.path = path;
    }
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;
        try{
            String file = path + name+".class";
            bis = new BufferedInputStream(new FileInputStream(file));
            baos = new ByteArrayOutputStream();
            
            int len;
            byte[] data = new byte(1024);
            while((len = bis.read(data)) != -1){
                baos.write(data, 0, len);
            }
            byte[] bytes = baos.toByteArray();
            return defineClass(null, bytes, 0, bytes.length);
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try{
                bis.close();
            }catch(Exception e){
            	e.printStackTrace();
        	}
            try{
                baos.close();
            }catch(Exception e){
            	e.printStackTrace();
        	}
        }
        
        return null;
    }
}

双亲委派

一个类加载器收到类加载请求,不会自己尝试加载这个类,而是委托给父类加载器,只有父加载器找不到的时候才会由子类加载器加载。

原因

  • 保护代码
  • 判断一个类是否是某个类型时, 如果类加载器不一样,也不是同一个类型。