类加载机制详解

一、类加载过程

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

加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如
main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接
程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化:对类的静态变量初始化为指定的值,执行静态代码块

静态链接:main方法中调用hello静态方法#7就属于符号引用,在常量池中可以找到,类加载 时,该符号引用会被换成jmm内存中的地址
动态链接:就是普通方法的符号引用在程序运行期间换成内存地址

  Constant pool:
   #1 = Methodref          #8.#24         // java/lang/Object."<init>":()V
   #2 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #27            // test
   #4 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #30            // com/demo/Test
   #6 = Methodref          #5.#24         // com/demo/Test."<init>":()V
   #7 = Methodref          #5.#31         // com/demo/Test.hello:()V
   #8 = Class              #32            // java/lang/Object

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #5                  // class com/demo/Test
         3: dup
         4: invokespecial #6                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: pop
        10: invokestatic  #7                  // Method hello:()V

二、类加载器

1. 类加载器种类

引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如
rt.jar、charsets.jar等
  扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR
类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那
些类
自定义加载器:负责加载用户自定义路径下的类包
  
  继承关系:扩展、应用类加载器都继承URLClassLoader
     在这里插入图片描述
而这三个类加载其并不是真正的父子关系,持有关系,可以看下顶层ClassLoader中有parent属性
在这里插入图片描述

2.类加载器怎样生成、以及调用机制

在这里插入图片描述

  1. Launcher.getLauncher()怎样创建类加载器
      首先Launcher类是单列的保证jvm只有一个
      在这里插入图片描述
    看Launcher的构造方法代码如下
public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//首先创建扩展类加载器,它的父类加载器即他的parent属性因是引导类加载器是c++实现的,此处 没有传参
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        	//创建应用类加载器,并把扩展类加载器当参数,即应用类加载器parent属性是扩展类加载器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        //剩下代码此处省略

    }
  1. 从上面可以指定Launcher中的loader为应用类加载器

3 双亲委派机制

加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类![在这里插入图片描述](https://img-blog.csdnimg.cn/20210410144335961.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTkwNjU0,size_16,color_FFFFFF,t_70#pic_center)

双亲委派机制优点:
    沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
    API库被随意篡改
   避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
    次,保证被加载类的唯一性

加载过程:Launcher类中,loader.loaderClass(“xx.xx”)

   public Class<?> loadClass(String var1) throws ClassNotFoundException {
        return this.loadClass(var1, false);
    }
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(var1)) {
            //先看有没有被加载过,有直接返回加载过的
            Class var4 = this.findLoadedClass(var1);
            if (var4 == null) {
                long var5 = System.nanoTime();

                try {
                	//先从父类中获取,这是个递归
                    if (this.parent != null) {
                        var4 = this.parent.loadClass(var1, false);
                    } else {
                        var4 = this.findBootstrapClassOrNull(var1);
                    }
                } catch (ClassNotFoundException var10) {
                }
       
                if (var4 == null) {
                    long var7 = System.nanoTime();
                    //当向下委托,最终是应用类加载器自己去加载自己类
                    var4 = this.findClass(var1);
                    PerfCounter.getParentDelegationTime().addTime(var7 - var5);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
                    PerfCounter.getFindClasses().increment();
                }
            }

            if (var2) {
                this.resolveClass(var4);
            }

            return var4;
        }
    }

看下this.findClass(var1),其实ClassLoader是个空实现,最终调用URLClassLoader中的findClass(var1)

    protected Class<?> findClass(final String var1) throws ClassNotFoundException {
        Class var2;
        try {
            var2 = (Class)AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    //拼接类全限定名  
                    String var1x = var1.replace('.', '/').concat(".class");
                    //根据路径读取该类的字节
                    Resource var2 = URLClassLoader.this.ucp.getResource(var1x, false);
                    if (var2 != null) {
                        try {
                           //该方法就是加载类,不继续跟进去
                            return URLClassLoader.this.defineClass(var1, var2);
                        } catch (IOException var4) {
                            throw new ClassNotFoundException(var1, var4);
                        }
                    } else {
                        return null;
                    }
                }
            }, this.acc);
        } catch (PrivilegedActionException var4) {
            throw (ClassNotFoundException)var4.getException();
        }

        if (var2 == null) {
            throw new ClassNotFoundException(var1);
        } else {
            return var2;
        }
    }

4 自定义类加载器打破双亲委派机制

其实从上面可以看出,双亲委派在loadClass方法中实现,我们自定义类加载器只需重写该方法即可

package com.demo;

import sun.misc.PerfCounter;

import java.io.FileInputStream;
import java.lang.reflect.*;

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 length=fis.available();
        byte[] data= new byte[length];
        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();
        }

        return super.findClass(name);
    }

    @Override
    protected Class<?> loadClass(String name, boolean b) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(name)) {
            Class var4 = this.findLoadedClass(name);
            if (var4 == null) {
					//该类有当前类加载器加载,其余还是交给父类加载器,负责会报错因为加载Object不允许被私有类加载器加载
                    if(name.equals("com.demo.User")){
                        var4 = this.findClass(name);

                    }else{

                       var4= this.getParent().loadClass(name);
                    }
            }
            return var4;
        }
    }

    public static void main(String[] args) throws Exception{

        MyClassLoader   myClassLoader=new MyClassLoader("/home/kangcc/Desktop");
        Class<?>   clazz=myClassLoader.loadClass("com.demo.User");
        Object    obj=clazz.newInstance();
        System.out.println(clazz.getClassLoader());

    }
}

看源码时候存在的疑惑:

自定义类加载器parent是不是AppClassLoader?
首先自定义类加载器继承ClassLoader,自定义类加载器被加载时,ClassLoader也会被加载初始化,看下父类中parent属性是不是应用类加载器

在这里插入图片描述
子类被加载为啥也会加载父类(虽然这个问题知道,但是还是想跟着源码看下):
 demo如下
   首先Animal是父类
   People是子类
 首先我加载people类跟进去看看,此时已经完成people类的加载
  在这里插入图片描述
继续看763以后代码:可以看底层自动加载父类
在这里插入图片描述
是那个类调用去加载父类的继续看下,可以看出是本地地方,即底层应该找出父类并加载
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值