一、Jvm类加载机制

今天的博客主题

       Java虚拟机 ——》 Jvm类加载机制


类加载过程

在一开始学习 Java 语言的时候,对 javac 和 java 命令应该都不陌生。

这两个命令对应了你的 JDK 安装目录:C:\Program Files\Java\jdk1.8.0_151\bin 目录下的javac.exe 和 java.exe 。

这个目录下不仅仅包含了这两个命令,比如:javap、jmap、jstack、jstat 等等的一些命令。

可以尝试的去使用下,后面也会对这些命令有解释。

javac 命令是把写好的 .java 文件,编译成 Java 可执行的 .class 文件。

java 命令是把 .class 文件,通过类加载器给加载到 JVM 里去执行。

这个类是怎么被加载到 JVM 里面的呢?

加载流程是什么样的呢?

loadClass 的类加载过程主要有以下几步:

加载 》 验证 》 准备 》 解析 》 初始化 》 使用 》 卸载

加载:在硬盘上查找并通过IO读入字节码文件(使用到类的时候才会加载,比如调用类的main()方法,new对象等等)在这个加载阶段会在内存中生成一个代表类的 java.lang.Class() 对象,做为方法区这个类的各种数据访问入口。

验证:校验字节码文件的正确性。

准备:给类的静态变量分配内存,并赋予默认值。

解析:将符号引用替换为直接引用。把一些静态方法(符号引用,比如main()方法) 替换为指向数据所存的内存指针或句柄等(直接引用),这就是所谓的静态链接过程(会在类加载期间完成)。动态链接是在程序运行期间完成的,将符号引用替换为直接引用。

初始化:对类的静态变量初始化为指定的值,执行静态代码块。

当类被加载到方法区中后主要包含了:运行时常量池,类型信息,字段信息,方法信息,类加载器的引用(当前类到类加载器实例的引用),对应class实例的引用(类加载器在加载类信息放到方法区中后,会创建一个对应的 Class 类型的对象实例放到堆(Heap)中,访问方法区中类定义的入口和切入点) 等信息。

注意:jar包或war包里的类不是一次性全部被加载进来的,只有在主类运行过程中,使用到其他的类的时候,才会去加载这些类。懒加载机制,节省空间

public class JvmSample {

    static{
        System.out.println(">>>加载JvmSample");
    }

    public static void main(String[] args) {
        A a = new A();
        // B 是不会被加载的。只有在 new 的时候,才会被加载
        B b1 = null;
    }
}

class A{
    static {
        System.out.println(">>>加载 A");
    }
    public A(){
        System.out.println(">>>初始化 A");
    }
}

class B{
    static {
        System.out.println(">>>加载 B");
    }
    public B(){
        System.out.println(">>>初始化 B");
    }
}

类加载器

类加载过程主要是依赖类加载器来实现的,主要有以下几种类加载器。

1)BootstrapLoader(引导类加载器):负责加载支持JVM运行,位于JRE的lib目录下的核心类库,比如rt.jar,charsets.jar等。

2)ExtClassLoader(扩展类加载器):负责加载支撑JVM运行的,位于JRE的lib目录下的ext扩展目录中的类库。

3)AppClassLoader(应用程序类加载器):负责加载ClassPath路径下的类库,主要就是自己写的那些类。

4)自定义类加载器:负责加载用户自定义路径下的类库。

看下 classLoader.loadClass 是怎么去加载类的。

类加载器示例

public class JvmClassLoaderSample {

    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(JvmClassLoaderSample.class.getClassLoader());

        System.out.println();

        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

        System.out.println();

        System.out.println("bootstrapLoader加载的类文件:");
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        for (URL urL : urLs) {
            System.out.println(urL);
        }

        System.out.println();
        System.out.println("extClassloader加载的类文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载的类文件:");
        System.out.println(System.getProperty("java.class.path"));
    }
}

>>>>>>>>>>>>>>>运行结果>>>>>>>>>>>>>>>>
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4e50df2e
null

bootstrapLoader加载的类文件:
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_151/jre/classes
......

extClassloader加载的类文件:
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

appClassLoader加载的类文件:
C:\Program Files\Java\jdk1.8.0_151\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_151\jre\lib\ext\localedata.jar;
......

类加载器初始化的过程

在类加载的过程会创建一个JVM启动器实例 sun.misc.Launcher

一个单例的Launcher,确保整个虚拟机内只有一个 Launcher 实例

private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
    return launcher;
}

Launcher 构造方法,创建了扩展类加载器和应用程序类加载器

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    
    ...省略部分代码...
}

在调用 Launcher 类的 getClassLoader() 方法,返回的就是 Launcher.AppClassLoader,一般都是用这个加载器来加载我们自己写的程序代码。

public ClassLoader getClassLoader() {
    return this.loader;
}

双亲委派机制

JVM类加载器是有亲子层级结构的。

类加载是有一个双亲委派机制的,就是在加载某个类的时候,先委托父加载器去寻找要加载的这个类, 这个父类找不到就再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到这个类,则在自己的类加载路径中查找并载入。

简单说就是:类加载的时候先找父类加载,父类都加载不了,自己加载。

为什么需要双亲委派机制?

1)沙箱安全机制:就是自己写的 java.lang.String.class 不会被加载进去,防止核心类库被篡改。

2)避免类重复加载:一个类只被一个加载器加载一次,保证被加载类的唯一性。

类加载示例:

就是自己写一个 String 类包路径和Java rt.jar包下的 String 路径一样 java.lang.String

package java.lang;

public class String {

    public static void main(String[] args) {
        System.out.println(" my String ");
    }
}

》》》执行结果:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

自定义类加载器

实现自定义类加载器只需要继承 ClassLoader 类。

ClassLoader 类有两个核心方法:loadClass(String, boolean) 实现了双亲委派机制,

findClass(String) 默认实现是一个空方法,自定义类加载器就是重写这个方法。

/**
 * 自定义类加载器
 */
static class MyClassLoader extends ClassLoader {

    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    private byte[] loadByte(String fileName) throws Exception {
        String newFileName = fileName.replace(".", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + newFileName + ".class");
        int length = fis.available();
        byte[] bytes = new byte[length];
        fis.read(bytes);
        fis.close();
        return bytes;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            // defineClass 将字节数组转换为类的实例
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}

// Test Class
public class Test{	
	public void test(){
		System.out.println("Hello World");
	}
}

// 测试
private static void customClassLoader() throws Exception {
    MyClassLoader myClassLoader = new MyClassLoader("D:/StudyIT/Jvm");
    Class clazz = myClassLoader.loadClass("Test");
    Object object = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("test", null);
    method.invoke(object, null);
    System.out.println(clazz.getClassLoader().getClass().getName());
}

// 输出结果
Hello World
com.uzi.jvm.JvmClassLoaderSample$MyClassLoader

打破双亲委派机制?

Tomcat类加载器?

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值