java的类加载器 classLoader

java的类加载过程 就是将我们通过classloader的代码转换为二级制的字节码,然后用一个class对象指向它,让我们可以取出数据。
在这里插入图片描述

我们知道一个类如果需要加载,那么必然会经历类的加载器,那么类加载器有哪些:
1.最顶层的类加载器,Bootstrap加载器
2.加载扩展包的加载器 Extension
3.加载classpath的加载器,App
4.自定义的ClassLoader
我们必须知道这个类加载器是层层递进的 比如bootstrap是Extension的父类 extension又是app的父类
注意:此处的父类不是继承关系的父类,而是双亲委派过程中的递归层次的父类。
简单的示例:

public static void main(String[] args) {//有四种类加载器 Bootstrap 是核心类的加载器(lib/rt.jar charset.jar) 由c++实现
        //Extension 加载扩展 jar包 jre/lib/ext/*.jar,或由-Djava.ext.dirs指定
        //App 加载classPath指定内容 就是我们自己写的类
        //Custom ClassLoader 自定义的classLoader
        System.out.println(String.class.getClassLoader());
        System.out.println(sun.awt.HKSCS.class.getClassLoader());
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
        System.out.println(com.gsx.ClassLoader.classloader_test_01.class.getClassLoader());

        //获取父类的类加载器  这里的父不是指的继承关系的父 是双亲委派加载过程中需要查找的一个层次关系
        //所有加载器都是由Bootstrap加载器加载的
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
        System.out.println(com.gsx.ClassLoader.classloader_test_01.class.getClassLoader().getClass().getClassLoader());
    }

双亲委派: 所谓双亲委派 就是类加载器在寻找类的过程中的层次关系,如果说我们遇到一个类需要被加载,那么首先从它的加载器进行加载(如果它的类加载器是我们的自定义类加载器),首先竟然到CustomClassLoader中进行加载,去它对应的缓存区域找,如果没有 向它的父类 AppClassLoader中寻找,如果App缓存中没有,那么会去到Extension缓存中去寻找,没找到,然后再去BootStrap缓存中去寻找,如果都没有找到 此时会一层一层的往下走,首先看Bootstrap能否加载这个类,无法加载,然后回到Extension中加载,无法加载,然后去App中加载,无法加载,最后回到自定义的加载器中,加载成功返回class对象,不成功抛出classNotFoundExection。

为什么会出现双亲委派这一个过程,为了安全性,如果说有人写了一个String对象,妄图覆盖掉java.lang的String,里面有一些东西 比如遇到账号密码等就发送到它的电脑,那么这样就会造成极大的安全隐患,通过双亲委派,向上找,再Bootstrap加载器中,他会直接将java内置的String返回给你 不会用你自定义的。
在这里插入图片描述
其实类加载的过程就是将.class文件二进制读入jvm所占有的内存中,然后返回给我们一个class对象指向这个内存,这样我们的就可以同class对象获取我们的类的属性。

这里有一段代码可以查看java内置的各个类加载器的默认的加载路径:

public static void main(String[] args) {//获取我们java中各个类加载器 所定义的加载路径
        String pathBoot=System.getProperty("sun.boot.class.path");//bootstrap 类加载器
        System.out.println(pathBoot.replaceAll(";",System.lineSeparator()));
        System.out.println("=======================================================");
        String pathExt=System.getProperty("java.ext.dirs");//extension 类加载器
        System.out.println(pathExt.replaceAll(";",System.lineSeparator()));
        System.out.println("=======================================================");
        String pathApp=System.getProperty("java.class.path");// app类加载器
        System.out.println(pathApp.replaceAll(";",System.lineSeparator()));
        System.out.println("=======================================================");
    }

简单的源码阅读:
我们加载一个类会调用loadClass对象 我们进入该对象中查看:
在这里插入图片描述

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);//从缓存中查看这个class是否已经加载了
            if (c == null) {//如果没有加载
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//这里的父加载器 就是我们上面提到的那个层次关系
                        c = parent.loadClass(name, false);;//就去父加载器中去寻找
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);//没有找到调用findClass方法  我们只需要重写这个方法即可

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

简单的自定义类加载器:

public class MyClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file=new File("E:/java_project/",name.replaceAll(".","/").concat(".class"));//将class文件以二进制的形式读入
        try {
            FileInputStream fileInputStream=new FileInputStream(file);
            ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
            int b=0;
            while((b=fileInputStream.read())!=0){
                byteArrayOutputStream.write(b);
            }
            byte[] bytes=byteArrayOutputStream.toByteArray();//获取到的二级制转换为字节数组
            byteArrayOutputStream.close();
            fileInputStream.close();
            return defineClass("Hello.class",bytes,0,bytes.length);//将二进制的文件 读入我们的jvm所占有的的内存空间中 让他生产class对象  这个defineClass是classLoader提供的对象
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);//这里是抛异常 classNotfoundException
    }

    public static void main(String[] args) throws Exception{
        ClassLoader c=new MyClassLoader();
        Class clazz = c.loadClass("com.gsx.ClassLoader.Hello");
        Hello o = (Hello)clazz.newInstance();
        o.m();

        System.out.println(o.getClass().getClassLoader());
    }
}

java代码的编译是混合模式 意思就是对于指向次数没有那么多的时候会编译为字节码解释执行,但对于短时间内被许多次调用的方法会使用jit直接编译成为二进制码 由硬件来执行。
在这里插入图片描述
如果 public static int count=2;放在上面 那么得到的值是3,如果放在下面得到的值是2,因为放在下面过后,我们的上面语句先执行,此时我们的count是默认值0,没有初始化 调用构造方法后 count变为1,然后count进行初始化 赋值 为2.

public class Linking {//java再loading之后 会进行Linking 里面有 三个步骤 1.Verification  严重文件是否符合jvm规范
    //2.Preparation 静态成员变量赋默认值
    //3.Resolution 将类,方法,属性等符号引用解析为直接引用 常量池中的各种符号解析为指针,偏移量等内存地址的直接引用
    public static void main(String[] args) {
        System.out.println(T.count);
    }
    static class T{//load到内存中 先赋默认值 对象是null int 是1 然后才赋初值  所以顺序不同的时候另一个还没有赋初值 可能会导致结果不同
        public static T t=new T();
        public static int count=2;

        public T(){
            count++;
        }
    }
}

指令重排:

public class SingleTon {
    private static volatile SingleTon INSTANCE;
    private SingleTon(){
    }
    public static SingleTon getInstance(){//双重检查的单例模式 需要加volatile参数  主要是为了不让指令重排
        if(INSTANCE==null){
            synchronized (SingleTon.class){
                if(INSTANCE==null) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE=new SingleTon();//次处我们已经 申请到了内存 同时已经进行了属性的默认值的赋值 但是 线程暂停了 其他线程进入
                    //由于对象已经创建完成 只是没有初始化 直接返回了INSTANCE给其他地方用了 导致出错 因为使用的值并不是我的值 而是jvm赋予的默认值
                }
            }
        }
        return INSTANCE;
    }
}

如下的一个代码:

public class OrderSorted {//指令重排

    public static void main(String[] args) {
        OrderSorted orderSorted=new OrderSorted();
    }
}

它的字节码,按这样运行没有问题 可是指令重排 可能3和4会交换位置 将我们没有初始化的对象拿来使用:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值