JVM-类加载机制

main方法是如何执行的?

这里有一个简单的java方法,以这个代码说明一下;

public class Demo1 {
    public static void main(String[] args) {
        System.err.println("Demo1");
    }
}

1.编译成.class文件;
2.使用java命令调用这个calss文件;
3.java命令会启动c++程序,c++程序会启动jvm进程,实例化bootstartp类加载器再调用java的 sun.misc.Launcher.getLauncher() 方法实例化ExtClassLoader 和AppClassLoader;
4.完成类加载的流程
5.调用main方法的入口,执行方法;

在这里插入图片描述

类加载流程

1.加载;从磁盘,网络 可以获取的任何二进制输入流
2.验证:验证字节码文件的格式
3.准备:将字节码文件中的static变量设置默认的初始值,如果是final 则在这一步就完成了真实赋值;
4.解析:将字节码文件中的static方法的符号引用转为直接引用; 在字节码文件中方法的描述都是指向常量池中的字面量,真实调用时应该是指向某个内存地址; 这个转换过程就是符号引用转直接引用;
5.初始化:将字节码文件中的static变量赋值,执行static静态代码块;
字节码文件被加载到jvm之后,会在方法区存储。主要存储运行时常量池,接口信息,字段信息,方法信息,类加载器信息,对应的Class实例信息;

类加载器的信息:保留被哪个类加载器加载的信息;
对应的Class实例信息:字节码文件被加载到JVM后,会在堆区创建一个对应的Class实例,作为开发人员访问方法区类元信息的入口和切入点;

SUN.MISC.LAUNCHER

JVM启动时,创建BootStrap类加载器,调用sun.misc.launcher.getLauncher()方法;

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
   	//1.类加载时,在初始化阶段完成赋值,调用类的默认构造方法
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;
	//3.jvm调用这个方法时,此类已经实例化完成,返回对应的单例实例;
    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
    	//2.默认构造方法
        Launcher.ExtClassLoader var1;
        try {
        	//2.1创建extClassLoader
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        	//2.2将extClassLoader作为AppClassLoader的parent属性传入,创建AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

注意:java中的类是懒加载的,只有使用时才会加载;如遇到new,getstatic,putstatic指令的时候;对应创建一个新对象,调用static方法,获得static变量;

public class LazyLoadDemo {
    static {
        System.err.println("LazyLoadDemo Class init");
    }

    public static void main(String[] args) {
        A a=new A();
        B b=null;
    }


    static public class A{
        static {
            System.err.println("A Class init");
        }
    }

    static public class B{
        public static String bb="bb";
        static {
            System.err.println("B Class init");
        }
        public B(){
            System.err.println("B exam init");
        }
    }
}
main方法中模拟3种情况:
1.A a=new A();
   B b=null;
   输出
  LazyLoadDemo Class init
  A Class init   说明B类并未被加载;

2.A a=new A();
  String s=B.bb;
  输出
  LazyLoadDemo Class init
 A Class init
 B Class init  说明B类被加载,但是并没有被实例化,static 属性的使用不会触发对象的实例化;

3.A a=new A();
  B b=new B();
  输出
  LazyLoadDemo Class init
  A Class init
  B Class init
  B exam init

各类加载器负责加载的内容

public class ClassLoaderDuty {
    public static void main(String[] args) {
        //rt.jar
        System.out.println(Launcher.class.getClassLoader());
        //ext/*
        System.out.println(DESKeyFactory.class.getClassLoader());
        //classpath
        System.out.println(ClassLoaderDuty.class.getClassLoader());
        System.out.println();
        System.out.println("bootstrapLoader加载以下文件:");
        URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
        URL[] urLs = bootstrapClassPath.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"));
    }
}

运行上述程序可以在控制台输出各ClassLoader加载的文件,AppClassLoader虽然输出很多,但是由于双亲委派机制的存在导致它的加载范围是有限的。

双亲委派机制

双亲委派机制指需要加载一个类时,先到当前的类加载器中寻找如果没有就委托父类去加载一直到BootStrap如果还没有找到,就再往下委托;

双亲委派机制保证了
沙箱安全:因为类加载是使用类的全限定名查找的,如果没有双亲委派,那我们自己开发一个java.lang.String 就可能把jdk的给替换掉;
避免类重复加载: 父加载器加载过的类避免子类重复加载;

双亲委派的实现代码主要是在ClassLoader.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);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	//如果查找不到,委托给父类
                	//如果没有父类,委托给bootStrap
                	//例如: app 加载一个User.class --->委托给Ext --->ext 没有父类,只能委托给bootStrap
                	//bootStrap加载不到,返回C=null,继续往下执行Ext再自己去加载,Ext加载不到再返回给APP, App再自己去加载
                    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);

                    // 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;
        }
    }

通过以上代码可以观察得出,loadClass实现了双亲委派的逻辑实现,真正加载类是调用了findClass方法;
下面我们自定义一个类加载器,分别实现这两个方法观察一下区别;

public class CustomClassLoader extends ClassLoader{
    private String path;

    public CustomClassLoader(String path) {
        this.path = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data=new byte[0];
        try {
             data=loadByte(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name,data,0,data.length);
    }

    private byte[] loadByte(String name) throws IOException {
    	name = name.replaceAll("\\.", "/");
        String classPath=path+"/"+name+".class";
        FileInputStream inputStream=new FileInputStream(classPath);
        int available = inputStream.available();
        byte[] data=new byte[available];
        inputStream.read(data);
        inputStream.close();;
        return data;
    }
}

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        CustomClassLoader classLoader=new CustomClassLoader("d:/Demo");
        Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
        System.err.println(demo1.getClassLoader());
    }
}

可以看到输出的类加载器是 sun.misc.Launcher$AppClassLoader@18b4aac2, 为什么还是APP呢? 因为Demo1 这个Class文件还存在于ClassPath下,由于双亲委派机制会由APP来加载, 把ClassPath下的Demo1.class删除后,就会输出:com.jtfu.jvm.custom.CustomClassLoader@1540e19d

打破双亲委派

	怎么打破双亲委派呢? 上面提到双亲委派的逻辑在loadClass方法中,那么我们重写这个方法;
public class CustomClassLoader1 extends ClassLoader{
    private String path;

    public CustomClassLoader1(String path) {
        this.path = path;
    }

    @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) {
                long t0 = System.nanoTime();
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 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;
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data=new byte[0];
        try {
            data=loadByte(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name,data,0,data.length);
    }

    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll("\\.", "/");
        String classPath=path+"/"+name+".class";
        FileInputStream inputStream=new FileInputStream(classPath);
        int available = inputStream.available();
        byte[] data=new byte[available];
        inputStream.read(data);
        inputStream.close();;
        return data;
    }
}

 public static void main(String[] args) throws ClassNotFoundException {
        CustomClassLoader1 classLoader=new CustomClassLoader1("d:/Demo");
        Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
        System.err.println(demo1.getClassLoader());
    }

注意,这里已经把上面测试删除的Demo1.class恢复了;
运行会提示错误, d:\Demo\java\lang\Object.class (系统找不到指定的文件。) 当然,java所有的类都继承Object,那该咋办呢?既然提示找不到那就在对应目录下创建一个Object.class好了
在这里插入图片描述
再次运行,发现还是报错提示 Prohibited package name: java.lang; 由于jvm的安全机制,不允许以这个包名命名;
再去重新修改一下代码,当只加载com.jtfu.jvm 的文件时,打破双亲委派机制;

if (c == null && name.startsWith("com.jtfu.jvm")) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 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();
                }else {
                    c=this.getParent().loadClass(name);
                }

再次运行成功输出自义定类加载器:com.jtfu.jvm.custom.CustomClassLoader1@1540e19d

注意

一个jvm中可以存在多个包名,类名相同的Class实例,这是因为他们的ClassLoader不同所以判断两个Class是否相同,还需要判断它们的类加载器是否相同! 下面的代码使用自定义类加载器加载的Demo1 与 ClassPath下的Demo1 包名,类名完全相同; 但是它们的类加载器不同;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        CustomClassLoader1 classLoader=new CustomClassLoader1("d:/Demo");
        Class<?> demo1 = classLoader.loadClass("com.jtfu.jvm.Demo1");
        Object d1 =  demo1.newInstance();

        System.err.println(d1);

        Demo1 dd1=new Demo1();

        System.err.println(dd1);

        System.err.println(dd1.getClass().equals(d1.getClass()));

        Demo1 dd2=new Demo1();
        System.err.println(dd1.getClass().equals(dd2.getClass()));
    }
}

com.jtfu.jvm.Demo1@14ae5a5
com.jtfu.jvm.Demo1@7f31245a
false
true

项目开发中实际应用到打破双亲委派机制的地方

1.数据库连接
2.tomcat自定义类加载器

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值