JAVA基础---类加载机制

加载流程:

在这里插入图片描述

一、加载

1.要加载我们首先要获取到一个二进制字节流(本地系统、数据库、网络、jar等等渠道)

2.静态存储结构转化为方法区的运行时数据结构(个人理解就是把生成的字节码,即类信息载入到方法区)

3.在heap里生成一个类对象,作为方法区的访问入口。(根据方法区的信息产生一个实例对象)

二、验证

1.验证class文件的标识:魔数(class文件结构中的咖啡北鼻)

2.验证版本号:(如,00-00 00-34)

3.验证常量池(常量类型,常量数据结构是否正确,UTF-8是否符合标准)

4.class文件的每个部分(字段表,方法表等)

5.元数据验证(父类验证,继承类验证,final验证)-------我们往往会因为Idea这类智能化工具而忽略掉这一点,工作中有其帮我们自动校验

6.字节码验证(指令验证等)

7.符号引用验证(通过符号引用是否能找到字段、方法、类)

三、准备

1.为类变量分配内存并且设置类变量的初始化阶段(只对static类变量进行内存分配,这也是为什么static属于cllinit)下面我们看一个基础的东西:

static int a = 2; // 初始化值为0
static final int b =2  //初始化值为2

因为对应常量池在准备阶段就会将b赋值,而a需要调用

四、解析

1.对符号引用进行解析

直接引用:指向目标指针或偏移量
符号引用--------->直接引用

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

如,字段解析:
1.在本类中找匹配字段如果找不到
2.如果有接口则去接口中查找,父类接口没有则继续向上寻找,找不到
3去父类中找以此类推
失败:java.lang.NoSuchFieldError

五、初始化

类的实例构造器,类的初始化
静态变量,静态代码块的初始化

类加载器:

在这里插入图片描述
首先我们可以用:-XX:+TraceClassLoading 查看下jvm都自动给我们加载了哪些类(太长就不贴出来了)

        System.out.println(System.getProperty("sun.boot.class.path"));
        System.out.println(System.getProperty("java.ext.dirs"));
  Class<?> klass = Class.forName("com.chihai.graduation_project.class_loader.Demo1");
        System.out.println(klass.getClassLoader());
        System.out.println(klass.getClassLoader().getParent());
打印结果:
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\classes
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4f2410ac

由上面打印结果我们可以看出:
Bootstarp: 加载我们lib下的核心类库(%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等,且-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录)

Ext: 加载的是lib目录下ext文件夹中的jar(还可以加载-D java.ext.dirs选项指定的目录。)

App: 载当前应用的classpath的所有类。

可以看出ExtClassLoader和AppClassLoader是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); // 为什么要传入ExtClassLoader
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        Thread.currentThread().setContextClassLoader(this.loader);   //设置AppClassLoader为线程上下文类加载器
        
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先要检查当前类是否已经被加载
            Class<?> c = findLoadedClass(name); // 调用本地方法:findLoadedClass0(),来寻找当前类
            // 如果没被加载则继续执行下面代码
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                
                    if (parent != null) {
                    	// 父加载器不为null继续递归
                        c = parent.loadClass(name, false); // 此处方法递归调用是则体现了我们的双亲委派机制
                    } else {
                    //如果父类加载器为null,则委托给BootStrap加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

          .......
    }

loadClass()方法:

上面代码我们可以清楚的看到loadClass通过不断的递归,自底向上不断寻找父类加载器。这里有一点需要注意,System.out.println(klass.getClassLoader().getParent().getParent);你会惊奇的发现ExtClassLoader的父加载器输出为null:,为什么要这么定义呢?,从上述代码可以发现如果Ext加载器的父加载器为null,就委托给Bootstarp类加载器,此时java自动帮我们将BootStrap翻译为null,从而有跳出递归的接口。这只是开始我个人主观臆测,下面有源码分析 飞雷神之术

   private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class<?> findBootstrapClass(String name);
p> <tt>Class</tt> objects for array classes are not created by class
 * loaders, but are created automatically as required by the Java runtime.
 * The class loader for an array class, as returned by {@link
 * Class#getClassLoader()} is the same as the class loader for its element
 * type; if the element type is a primitive type, then the array class has no
 * class loader. 、// 这里需要注意一一下,数组类的class对象并不是由类加载器创建,而是运行时由jvm帮我们创建

int[] a = new int[]{1,2,3};
System.out.println(a.getClass().getClassLoader());// 输出为null
========================================================================
static class A{ }
A[] b = new A[]{};
System.out.println(b.getClass().getClassLoader());// 输出sun.misc.Launcher$AppClassLoader@18b4aac2

此处容易让人误解的是:null到底代表什么?

String[] c = new String[2];
System.out.println(b.getClass().getClassLoader());//输出为null

这两个null代表的含义是否相同?其实仔细阅读官方注释就会找到答案。基础数据类型数组是没有类加载器的,它是由我们JVM主动创建的,并需要加载过程。但是String并非基础类型,所以此处的null代表 根加载器

判断两个对象是否相等其中最重要的条件:类加载器是否相同

父加载器不是父类:这里刚接触时总是容易让人觉得是真正父子关系

在这里插入图片描述
当我们调用getParent()方法时,其实正在调用的是ClassLoader中的getParent(),下面看一下源码。


    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    
@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Check access to the parent class loader
            // If the caller's class loader is same as this class loader,
            // permission check is performed.
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent; // 返回的parent
    }
    

getParent()实际上返回的就是一个ClassLoader对象parent,parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:

protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

 protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

1.由外部类创建ClassLoader时直接指定一个ClassLoader为parent。

2.由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

下面是getSystemClassLoader()源码:

 @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
     private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                 //通过Launcher获取ClassLoader
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

从上面代码我们可以清晰的看到,getSystemClassLoader()返回的是:
Launcher.getLauncher().getClassLoader(),即AppClassLoader。由此我们可以得出当我们没有指定父加载器时那么它的parent默认就是AppClassLoader。

EXT是App父加载器的原由:

launcher类中我们可以清晰的看到

ClassLoader extcl;
        
extcl = ExtClassLoader.getExtClassLoader();

loader = AppClassLoader.getAppClassLoader(extcl);

EXT的父加载器是谁:

ExtClassLoader并没有直接找到对parent的赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。
public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);   
}
public URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        acc = AccessController.getContext();
        ucp = new URLClassPath(urls, factory, acc);
    }

Ext的parent为null,但是Bootstrap为什么是它的父加载器?

BootstrapClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。然后JVM初始化sun.misc.Launcher并创建Extension ClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器。比如ExtClassLoader。这也可以解释之前通过ExtClassLoader的getParent方法获取为Null的现象。

关键方法loadClass()基本流程:

  1. Class<?> c = findLoadedClass(name);-------去检测这个class是不是已经加载过了
  2. if (parent != null) {
    c = parent.loadClass(name, false);
    } else {
    c = findBootstrapClassOrNull(name);
    }
    执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
    3.如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
    4.参数resolve是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。

如何打破双亲派机制:

自定义类加载器并重写loadClass方法

还可以用ContextClassLoader,下面会提到

public class CustomLoader extends ClassLoader{

    private static final String DEFULT_DIR = "F:\\class_loader\\";

    private String dir = DEFULT_DIR;

    private String classLoaderName;

    public String getClassLoaderName() {
        return classLoaderName;
    }

    public CustomLoader() { super();}

    // 将系统类加载器当做当前类加载器的父类
    public CustomLoader(String classLoaderName) {
        super();this.classLoaderName = classLoaderName;
    }

    @Override
    public String toString() {
        return classLoaderName;
    }

    public CustomLoader(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }


    @Override
    public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (name.startsWith("java.")){
            try {
                ClassLoader system = ClassLoader.getSystemClassLoader();
                clazz = system.loadClass(name);
                if (clazz != null){
                    if(resolve)
                    {
                         resolveClass(clazz);
                         return clazz;
                    }
                }
            }catch (Exception e){

            }
        }
        try {
            clazz = findClass(name);
        }catch (Exception e){

        }
        if(clazz == null&& getParent() != null){
            getParent().loadClass(name);
        }

        return clazz;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace(".","/");
//        String classPath = name;
        File classFile = new File(dir,classPath+".class");
        System.out.println(classPath.toString());
        System.out.println(classFile.toString());
        if(!classFile.exists()){
            throw new ClassNotFoundException("对不起对应文件不存在");
        }
        byte[] classBytes = loadClassData(classFile);
        if (classBytes == null || classBytes.length == 0){
            throw new ClassNotFoundException("类加载失败");
        }
        return this.defineClass(name,classBytes,0,classBytes.length);
    }

    private byte[] loadClassData(File classFile) {
        try {
            try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                FileInputStream fileInputStream = new FileInputStream(classFile)) {
                byte[] buffer = new byte[1024];
                int  len = 0;
                // 将java文件读取写入缓存区
                while ((len=fileInputStream.read(buffer)) != -1){

                    byteArrayOutputStream.write(buffer,0,len);
                }
                byteArrayOutputStream.flush();
                return byteArrayOutputStream.toByteArray();
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

调用我们的自定义类加载器是否能够加载自定义String类?

是我想太多,如果能随便加载的话那也太LOW了,直接抛出securityException异常。
在这里插入图片描述

类加载器命名空间:

定义:命名空间是由该记载器及其父加载器加载的类组成。
运行时包名与我们代码中的包名是不一样的,前面会加上类加载器

关于命名空间的重要说明
1.子加载器所加载的类能够访问到父加载器所加载的类
2.父加载器所加载的类无法访问到子类加载器所加载的类
3.不同的命名空间下两个类不可互相访问

类的卸载

在这里插入图片描述

ContextClassLoader :线程上下文类加载器

public class ThreadContextClassLoader {
    public static void main(String[] args) {
        ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
        System.out.println(Thread.currentThread().getContextClassLoader());
        CustomLoader customLoader = new CustomLoader("myClassLoader");
        Thread.currentThread().setContextClassLoader(customLoader);
        System.out.println(Thread.currentThread().getContextClassLoader());
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
myClassLoader
    }
}

java中定义了很多SPI(service provider interface),当我们把各大厂商实现的jar载入后,再调用java接口底层就会调到第三方jar包的实现。以JDBC为例,但是这样又不符合我们的双亲委派机制,像Driver这样的接口是存在于rt.jar中的是由BootStrap加载器加载的,而第三方Jar是由App加载的。上面我们提到过,父加载器所加载的类无法访问到子类加载器所加载的类。这里就完全不符合我们的双亲委派机制,这也是java遗留问题。这里就用到了ContextClassLoader 加载器,来打破双亲委派机制。这是java自己留的后面

 static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

在这里插入图片描述

 //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;
 private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

这样我们就可以通过切换类加载器从而实现本地接口能调用到第三方jar的类。

发布了29 篇原创文章 · 获赞 11 · 访问量 1874

猜你喜欢

转载自blog.csdn.net/chihaihai/article/details/100588521