已加载但找不到入口点dllregisterserver_JVM之类加载机制详解

本文详细介绍了Java程序的运行机制,从启动过程到类加载器的工作原理,再到双亲委派机制的实现与优势。通过分析,揭示了如何打破这种机制并给出了示例,例如Tomcat服务器内部的做法。
摘要由CSDN通过智能技术生成

一、java程序如何运行的

当我们写好一段java代码之后,jvm是如何帮助我们执行这段程序代码的?底层的执行机制是什么?

带着以上两个问题,我们进行一下深入研究。

首先清楚一点,java是一个面向对象的语言,在java中一切皆对象,也就是java中的类,我们的任何代码都无法脱离类而独自存在,如果你想要执行java程序代码,那么必然就需要有一个主类,里面有一个程序启动的入口方法,也就是所谓的public static void main(String[] args){ }静态方法,而JVM就是通过这个入口开始运行程序代码的。

我们知道JVM底层是使用C++语言实现的,当我们启动程序时,相当于创建了一个JVM进程运行在操作系统中,JVM会把我们运行的程序代码编译成.class文件,所谓的字节码文件,保存到磁盘中,然后找到执行主类的字节码文件加载到jvm内存中(方法区),加载对应的类,调用启动方法main()。

jvm调用一些java类的方法时,是需要先进行类的加载的,只有被加载好的类才能被使用。

那么类加载,需要一个专门用来加载类的工具,我们称之为类加载器。

52aa412dc8a3c00b13da8da033df8b71.png
  1. 首先C++帮我们创建jvm
  2. C++创建一个引导类加载器,它是最顶层的类加载器
  3. 然后C++调用java的代码创建jvm的启动器Launcher的实例 Launcher由引导类加载器加载
  4. 创建Launcher实例过程中,会创建两个加载器:ExtClassLoad 和 AppClassLoad 加载器
  5. 然后使用AppClassLoad加载器按照双亲委派机制加载需要加载的类,之后调用main方法启动。
  6. java程序执行结束,jvm销毁。

二、类加载器

java类的加载是使用类加载器加载,那么类加载器有哪些?分别负责加载哪些类?如何工作的?

类加载器有哪些?分别负责加载哪些类?

  1. 引导类加载器(BootStrapClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的rt.jar,charset.jar等核心类库中类
  2. 扩展类加载器(ExtClassLoader):负责加载支撑jvm运行的位于jre的lib目录下的ext扩展目下的核心类库中的类
  3. 应用类加载器(AppClassLoader):负责加载应用程序中自己创建类
  4. 自定义类加载器:负责加载自定义路径下的类

类加载过程分为:加载->验证->准备->解析->初始化

7ab3c69123c3db096f3366e93447b43c.png

类加载器的创建

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    // 静态变量 该类在被加载的初始化阶段 被实例化 调用构造方法  单例模式
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    //jvm调用此方法 获取实例
    public static Launcher getLauncher() {
        return launcher;
    }

    //构造方法
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //创建ExClassLoader扩展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            /**
            ** 创建AppClassLoader应用类加载器 上一步创建的扩展类加载器被作为父加载器传入        
            ** AppClassLoader的构造器中
            ** 创建完成后  赋值给loader变量
            **/
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                    ;
                } catch (InstantiationException var6) {
                    ;
                } catch (ClassNotFoundException var7) {
                    ;
                } catch (ClassCastException var8) {
                    ;
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

    public ClassLoader getClassLoader() {
        //返回的是AppClassLoader加载器
        return this.loader;
    }
}

首先C++创建引导类加载器,

然后由引导类加载器加载Launcher,在加载Launcher的初始化阶段 给launcher静态变量赋值时,调用Launcher的构造方法(由此可见,类的静态代码块先于构造方法被调用)

在构造方法中创建两个构造器:ExtClassLoader,AppClassLoader。

调用launcher.getClassLoader()方法获取类加载器,获取到加载器后 进行类的加载工作。

分析源码可见:所有的加载器都继承ClassLoader加载器

b30f020df783e9ae9a20f75f4ca78ff4.png

类加载机制默认遵循双亲委派机制且是懒加载。

三、双亲委派机制

类加载器在加载类时默认遵循双亲委派机制

5aac4e6581d4199fe4c3e1c29d39f9e9.png

双亲委派机制

一般一个类从AppClassLoader开始,在AppClassLoader中先找是否已经加载过,如果已经加载过则直接返回,

若未加载过,则丢给父加载器ExtClassLoader加载,ExtClassLoader先找自己是否加载过,

若未加载过则丢给父加载器BootStrapClassLoader引导类加载器,BootStrapClassLoader会在自己的加载路径中找,找不到

则再回丢给ExtClassLoader加载,ExtClassLoader在自己的加载路径找 找不到回丢给AppClassLoader加载,AppClassLoader在自己加载路径中找到了 则加载成功。

双亲委派机制的优点:

  1. 沙箱安全:加入你自定义创建一个类String 全类名java.lang.String。在加载的时候,会最终丢给最顶层的引导类加载器加载,而加载器发现自己已经加载过了(加载的是jre下lib目录下的rt.jar核心库的类,并不是你创建的String类),只要全类名相同,加载器就认为加载过了,直接返回。这就防止有人对java的核心类库的类进行篡改。
  2. 避免重复加载:在整个加载过程中,一个类被任何一个加载器加载过了,就不会在此被加载,避免了重复加载。

四、实现原理

类加载是由各个加载器加载的,每个加载类都有两个核心方法 loadClass() 和findClass();

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先 查看自己是否已经加载过当前类 已加载过 直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                //没有加载过
                long t0 = System.nanoTime();
                try {
                 
                    if (parent != null) {
                        //委托父加载器进行加载
                        c = parent.loadClass(name, false);
                    } else {
                        //没有父加载器了 当前加载器为ExtClassLoader  找BootStrapClassLoader加载器加载
                        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(String name, boolean resolve)方法中

3870089bf1b96b5d96891375ec5d257d.png

五、如何打破双亲委派机制

上文已经说过双亲委派机制就是在loadClass()方法实现的,那么我们如何打破双亲委派机制呢?

打破双亲委派,我们只需要在类加载时,不在委托父加载器就可以了,就那么简单,如何实现?

自定义加载器,重写loadClass()就可以了。

/**
 * @description: 自定义类加载器  父加载器默认AppClassLoader 应用类加载器
 * @author: zhangbing
 * @create: 2020-06-09 13:59
 **/
public class MyClassLoader extends ClassLoader {

    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }


    /**
     * 重写loadClass 打破双亲委派机制
     *
     * @param name
     * @param resolve
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先,查找当前加载器是否已经加载过,已经加载过 则直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                //当前加载器没有加载过
                long t0 = System.nanoTime();
                if (!name.startsWith("main.jvm.classLoad")) {
                    //jdk的类 不允许打破双亲委派机制
                    c = getParent().loadClass(name);
                } else {
                    //自定义的类 使用自定义加载器加载
                    c = findClass(name);
                }
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();

                // 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[] bytes = loadByte(name);
        return defineClass(name, bytes, 0, bytes.length);

    }

    private byte[] loadByte(String fullPathname) {
        String path = fullPathname.replaceAll(".", "/");
        byte[] bytes = null;
        try {
            FileInputStream fileInputStream = new FileInputStream(classPath + "/" + path + ".class");
            int available = fileInputStream.available();
            bytes = new byte[available];
            fileInputStream.read(bytes);
            fileInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();
        }

        return bytes;
    }

    
    public static void main(String[] args) {
        //创建一个自定义加载器 传入自定义类路径
        MyClassLoader myClassLoader = new MyClassLoader("D:/workspace/test/");
        try {
            Class<?> aClass = myClassLoader.loadClass("main.jvm.classLoad.User", false);
            System.out.println("使用的类加载器:" + aClass.getClassLoader());
            Object o = aClass.newInstance();
            Method testMethod = aClass.getDeclaredMethod("testMethod", null);
            testMethod.invoke(o, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

代码运行结果:

182368f3c199a5ee478a59507c7e80e1.png

打破双亲委派机制成功!!!

我们熟知的tomcat内部就没有使用默认的双亲委派机制,内部打破了双亲委派机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值