从JDK源码级别剖析JVM类加载机制

main方法的执行流程

image-20230224180340262

1:先把文件编译成class文件

2:window下,java.exe调用jvm.dll创建虚拟机,dll文件相当于jar包。

3:c++创建引导类加载器。

4:实例化Launcher,由引导类加载器加载。

5:加载JvmTest.class文件

6:执行main方法

loadClass加载流程,类是如何加载到java虚拟机的?

static int a = 10;
int b = 5;

1:加载:将字节码文件丢到内存

2:验证:需要验证字节码格式是否正确。Cafe baba标准的字节码文件开头。

3:准备:对静态变量进行赋值,赋上默认值,将a赋为0。

4:解析:将符号引用转变成直接引用,只是将静态的能够确定被谁调用的才会被转变,这个叫做静态解析,类加载时候完成。

动态解析,在编译的时候没有办法确定被谁调用,只能在运行的时候才能判断出来,比如多态。类运行时候完成。

​ 符号引用包括三种:类、接口的符号引用。字段的符号引用。方法的符号引用。

​ 直接引用:符号的内存地址。

5:初始话:将静态遍变量真正赋值,将a赋值为10,静态代码块也会在这里执行 。

6:将class文件加载到JVM虚拟机中。

类的加载机制一般是懒加载,在只有用到的时候才会真正的加载。

public class JvmTest {
    static int b = 10;
    static {
        System.out.println("jvmTest静态方法");
        System.out.println("b的值 = " + b);
    }
    public static void main(String[] args) {
        System.out.println("执行main方法");
        new A();
    }
}
class A {
    static {
        System.out.println("A的静态方法");
    }
    A() {
        System.out.println("A的构造方法");
    }
}
class B {
    static {
        System.out.println("B的静态方法");
    }
    B(){
        System.out.println("B的构造方法");
    }
}

执行结果

jvmTest静态方法
b的值 = 10
执行main方法
A的静态方法
A的构造方法

A的静态代码块输出是在main方法之后,也就是在真正使用的时候才会被加载。然后B没有被使用,所以不会被加载。

类加载器和双亲委派机制

类加载器分为:

​ 1:引导类加载器:是由c++创建,在jvm创建之后加载,它负责加载JRE下lib目录的jar。

​ 2:扩展类加载器:负责加载JER下lib的ext,extClassLoader

​ 3:应用程序加载器:负责加载应用程序,appClassLoader

​ 4:自定义加载器:自己定义的

System.out.println("String加载类:" + String.class.getClassLoader());
System.out.println("DESKeyFactory加载类:" + com.sun.crypto.provider.DESCipher.class.getClassLoader());
System.out.println("JvmTest加载类:" + JvmTest.class.getClassLoader());

输出

String加载类:null 
DESKeyFactory加载类:sun.misc.Launcher$ExtClassLoader@372f7a8d
JvmTest加载类:sun.misc.Launcher$AppClassLoader@18b4aac2

String的加载类是引导类加载器加载的,引导类是由c++生成的,所以看不到,是个null。

extClassLoader是扩展类加载器。

AppClassLoader引用程序加载器。

Launcher

C++引导类加载完之后会实例化Launcher

class Launcher{
private ClassLoader loader;
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() {
     return launcher;
}
    ... ...
}

launcher是个静态变量,是个单例,在加载的时候就初始话好了。

初始话的时候实例化ext和app ClassLoader

public Launcher() {
//实例化扩展类加载器,它的父类没有设置值
Launcher.ExtClassLoader var1 = 
        Launcher.ExtClassLoader.getExtClassLoader();
//初始化APPclassLoader,并且将ext传入设置为父类加载器    
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}

loader赋值为appClassLoader,默认先用appClassLoader加载

双亲委派机制

image-20230224132150805

C++在加载的时候调用

Launcher.getLauncher().getClassLoader().loadClass("JvmTest.class")

Launcher的classLoader是在实例化的时候赋值了AppClassLoader。

1:AppClassLoader去自己已经加载的类里边找,如果没有向上委托

2:ExtClassLoader去自己已经加载的类里边找,如果没有向上委托。

3:引导类去自己加载的类里边找,如果没有,

4:引导类尝试加载,判断这个类是不是应该由自己加载,判断是不是JRE包路径下的,如果是加载并且返回,不是向下传播

5:ExtClassLoader判断这个类是不是ext包路径下的,如果是自己加载并返回,不是向下传播

6:AppClassLoader判断这个类是不是classPath路径下的,如果是的化加载。不是的话,ClassNotFound

为什么先由AppClassLoader,而不是引导类开始?

因为大部分类都是我们自己写的,百分之95都是存放到AppClassLoader,只有第一次加载的时候会多走一步,之后大部分都直接从AppClassLoader里边获取。

loadClass("JvmTest.class")源码

Class<?> loadClass(String name, boolean resolve){
    //从自己加载的类里边找
    Class<?> c = findLoadedClass(name);
  if (c == null) {
     if (parent != null) {
            //父加载器加载
            c = parent.loadClass(name, false);
        } else {
            //ext的父是null,这是最后一层,引导类类加载器
            c = findBootstrapClassOrNull(name);
      }
      if (c == null) {
              //由URLClassLoader实现
            c = findClass(name);
       }
   }
    return c;
}

URLClassLoader.findClass源码

Class<?> findClass(final String name){
    String path = name.replace('.', '/').concat(".class");
    //判断是不是自己要加载的。jrt/  ext/
    Resource res = ucp.getResource(path, false);
    if (res != null) {
        //真正加载class的方法
        return defineClass(name, res);
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

新增一个String类

package java.lang;
public class String {
    public static void main(String[] args) {
        System.out.println(111);
    }
}

执行报错信息:

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

我们新增的 java.lang.String,是能够在引导类加载器的JRE包下边找到的,并且返回String类,这个返回的是jdk自带的类,并不是我们自己写的类,所以会报找不到main方法。

jdk为什么要用双亲委派机制

jdk不让修改自己内部的类,沙箱安全机制,防止核心API被篡改。

避免类的重复加载,父加载器已经加载完了,自己就不用再加载了。

全盘负责

public class JvmTest {
    static User user;
}

当加载JvmTest类的时候,也会加载User类,这两个类都会由同一个类加载器加载,不会由乱七八糟的加载器加载。

自定义加载器

我们要自定义加载器,只需要实现ClassLoader,重写findClass就可以。

public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll(".", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            //真正的加载步骤
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader =
                new MyClassLoader("C:/myClassLoader");
        Class<?> clazz = myClassLoader.loadClass("com.bbk.code.User");
        Object o = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("eat");
        method.invoke(o);
        System.out.println("当前MyClassLoader的类加载器:"+MyClassLoader.class.getClassLoader());
    }
}

自定义classLoade继承ClassLoader初始话会先初始化父类,在这时候会给自定义classLoader赋值parent为AppClassLoader。具体代码体现在:

protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        ... ...
}
public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
          ... ...
        return scl;
}
private static synchronized void initSystemClassLoader() {
    ... ...
    sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
    scl = l.getClassLoader(); //返回this.loader,引导类在加载Launcher的时候会赋值为AppClassLoader
    ... ...
}

执行main方法,需要把User的class从target中删除,并且加入到我们自定义的文件夹中。执行输出

我是User类eat方法,我的加载器是com.bbk.code.MyClassLoader@5a07e868

如何打破双亲委派机制

意思就是不在委托父加载,直接自己加载类。双亲委派的逻辑代码实现在loadClass方法中。需要继承ClassLoader重写loadClass方法即可:

image-20230224173458980

把红框的代码删除掉。

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                c = findClass(name);
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

重新执行main方法,报错:

java.io.FileNotFoundException: C:myClassLoaderjavalangObject.class (系统找不到指定的路径。)

在加载User的时候,会现在父类Object,我们打破双亲委派之后,自定义的加载器不存在Object所以会报错。

我们把Object.class放入到我们的本地文件夹中。重新执行报错:

//禁止加载java.lang 包
java.lang.SecurityException: Prohibited package name: java.lang

java.lang必须由我们引导类加载器加载,沙箱安全。我们只能让我们的引导类去加载java.lang。修改代码

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                if(!name.startsWith("com.bbk")){
                    c = this.getParent().loadClass(name);
                }else{
                    c = findClass(name);
                }

            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

只有当name是由com.bbk开头的由我们加载器加载。其他的都由父加载器加载。不会报错。

展开阅读全文

页面更新:2024-03-29

标签:加载   机制   赋值   双亲   静态   源码   符号   实例   级别   代码   文件   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top