专栏首页技术趋势jdk-Launcher源码学习

jdk-Launcher源码学习

源码类位置:sun.misc.Launcher、java.lang.ClassLoader

注意:建议阅读前先了解一下双亲委派机制:jvm的类加载器(classloader)及类的加载过程

背景

sun.misc.Launcher是类加载器实始化过程中会创建的一个实例,也是说整个JVM启动只会创建仅有一个Launcer的实例,所以该对角是一个核心。而Lancer中使用了单例模式进行创造,通过创建完实例后再进入初始化类加载器双亲委派的实例。

为什么要有双亲委派机制?直接加载不是效率更高?

1.双亲委派机制也叫沙箱安全机制,主要是防止核 心API被随意篡改。

2.避免类重复加载,每次加载先寻找父类是否加载过,如果加载了加载类全局仅加载过1次;

分析如下

以下是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 {
        //初始化appClassLoader同时通过appClassLoader进行加载 扩展类加载器类对象(就是我们自定义类,自已编写程序)
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        //失败抛出异常提示
        throw new InternalError("Could not create application class loader", var9);
    }
    //设置当前线程为这个this.loader(就是classLoader)
    Thread.currentThread().setContextClassLoader(this.loader);
    //获取系统配置
    String var2 = System.getProperty("java.security.manager");
    //不为空 并且不等于""和default 进行单例加载(这里用到了单例模式)
    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);
        }
        //进行设置安全管理器 就是校验是不是java.lang下面的对象
        System.setSecurityManager(var3);
    }

}

Launcer下面还有一个AppClassLoader内部类值得一读,位置:sun.misc.Launcher.AppClassLoader#loadClass

//可以发现AppClassLoader是继承URLClassLoader而不是extClassLoader 所以这里的关系不是继续只是引用
static class AppClassLoader extends URLClassLoader {
    //获取当前类URL地址
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
    //通过类信息获取appClassLoader的方法
    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        //获取类路劲
        final String var1 = System.getProperty("java.class.path");
        //通过地址获取对象流
        final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
        //返回类信息
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
            public Launcher.AppClassLoader run() {
                URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }
    //构构方法(重写classLoader的方法)
    AppClassLoader(URL[] var1, ClassLoader var2) {
        //调用父类获取
        super(var1, var2, Launcher.factory);
        //初始化jdk信息并放到缓存中
        this.ucp.initLookupCache(this);
    }
    //核心类 加载类对象的方法,也是双亲委派实现的方法
    public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        //做相关的校验 比如包名称为能为空等
        int var3 = var1.lastIndexOf(46);
        if (var3 != -1) {
            SecurityManager var4 = System.getSecurityManager();
            if (var4 != null) {
                var4.checkPackageAccess(var1.substring(0, var3));
            }
        }
        //判断地址是否符合
        if (this.ucp.knownToNotExist(var1)) {
            //通过查询当前类加载器是否已加载过
            Class var5 = this.findLoadedClass(var1);
            //判断是否加载了
            if (var5 != null) {
                if (var2) {
                    //链接指定的类装入加载器,然后直接返回
                    this.resolveClass(var5);
                }

                return var5;
            } else {
                //抛出异常加载
                throw new ClassNotFoundException(var1);
            }
        } else {
            //通过父类进入加载(下面看)
            return super.loadClass(var1, var2);
        }
    }
    //获取指定代码的权限
    protected PermissionCollection getPermissions(CodeSource var1) {
        PermissionCollection var2 = super.getPermissions(var1);
        var2.add(new RuntimePermission("exitVM"));
        return var2;
    }
    //判断是否已上锁 (有可能其它对象正在加载)
    private void appendToClassPathForInstrumentation(String var1) {
        assert Thread.holdsLock(this);

        super.addURL(Launcher.getFileURL(new File(var1)));
    }
    //获取上下文信息
    private static AccessControlContext getContext(File[] var0) throws MalformedURLException {
        PathPermissions var1 = new PathPermissions(var0);
        ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
        AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
        return var3;
    }
    //初始化类加载器
    static {
        ClassLoader.registerAsParallelCapable();
    }
}

可以发现上面的双亲委派是先在AppClassLoader进行加载,如果加载不成功,则由父类加载,而父类是URLClassLoader 中再去调用extClassLoader,所以很多面试的时候直接上来就问是不是继承,这里不是继承后,从源码也可以看出来。(坑来的)

位置:java.lang.ClassLoader#loadClass(java.lang.String, boolean)

//类加载方法
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 {
                    //通过顶级父类(引导类加载器)进行加载
                    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();
                //当前类进行加载(也就是说,如果父类或者顶级父类都没办法加载就自已加载)
                //注意这里的加载是通过URLClassLoader加载的
                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();
            }
        }
        //如果传进来是true 通过链接指定类(一般不会执行,都是传false)
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

最后

通过学习launcher的核心代码可以发现,我们常说的双亲委派实现方式其实不是直接继承,而是引用或者说调用,其次,通过学习可以发现其实代码量不多(指java)很多本地方式其实是c代码或c++,其实通过学习是如何初始化和双亲委派的实现逻辑后续去学习如何打破双亲委派可以更深刻理解,当然本文没有读整个文件,有部分可能描述有误,其实有发现同学可以指出或交流。

文章分享自微信公众号:
技术趋势

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:逍遥壮士
原始发表时间:2022-04-21
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • [iOS开发]JSONModel源码学习

    首先,在这个模型类的对象被初始化的时候,遍历自身到所有的父类(直到JSONModel为止),获取所有的属性,并将其保存在一个字典里。获取传入字典的所有key,将...

    Billy Miracle
  • LinkedList源码学习

    之前学习了ArrayList,了解了其基于数组的本质,那么LinkedList是怎么实现的?显然LinkedList是链表。也就是基于链表实现。链表分为单向链表...

    写一点笔记
  • ConcurrentHashMap源码学习

    经过对hashMap的学习,现在我们来学习一下ConcurrentHashMap的机理。我们知道juc包是高并发工具包,按照之前提供的高并发集合,那么Concu...

    写一点笔记
  • 如何打破双亲委派机制?

    上文说过,jdk是通过双亲委派机制实现类的加载,但是这个加载效率及场景存在弊端,所以本文借鉴tomcat的实现方式去打破双亲委派机制实现自定义类加载器来...

    逍遥壮士
  • ​HashTable源码学习

    在之前学习HashMap的时候,我们知道在JDK8中HashMap是采用Node数组+链表+红黑树的方式实现的。那么HashTable又是怎样的?

    写一点笔记
  • ConcurrentHashMap源码学习

    既然有了HashMap为什么还会出现ConcurrentHashMap?同时ConcurrentHashMap具有什么优势?ConcurrentHashMap与...

    路行的亚洲
  • ReentranLock源码学习

    首先回答一个问题?线程的三大特性?什么时候我们需要锁?java中已经提供了synchronized,为什么还要使用ReentrantLock?AQS原理。

    路行的亚洲
  • 学习vuex源码

    昨天听完同学来我们团队做的分享之后,自己又去看了一遍源码,结合自己之前项目的一些理解,写一篇博客,这里是原文链接.

    2014v
  • ThreadPoolExecutor源码学习

    我们之前温习了Thread类,明白了Runable接口才是多线程的任务核心。那么ThreadPoolExecutor就是用维护多线程的。作为工具类,Thread...

    写一点笔记
  • CopyOnWriteArrayList源码学习

    之前我们学习过乐观锁,大概得意思就是说当多线程来操作一个数据的时候,如果是读线程的时候,就获取读锁,写锁是不能降级为读锁的,但是写锁可以降级为读锁,...

    写一点笔记
  • AQS源码学习

    AQS全称AbstractQueuedSynchronizer,是一个同步器,用来构建锁或者其他同步组件的基础框架。内部主要使用一个volatile修饰的sta...

    秃头哥编程
  • StringJoiner源码学习

    在阅读源码的时候发现了一个类StringJoiner,因为之前也没用过。感觉应该是字符串拼接的工具类。

    写一点笔记
  • HashMap源码学习

    首先实现map的子类:HashMap、HashTable、TreeMap、LinkedHashMap。

    路行的亚洲
  • ArrayList源码学习

    ArrayList是一种以数组实现的列表,而数组的优势在于有角标,因此查询的速度较快,是一种可以动态扩容的数组。我们着重了解添加、获取、替换、删除操作。

    路行的亚洲
  • ExecutorCompletionService源码学习

    之前在项目中使用过ExecutorCompletionService,当时并不知道这个类的具体原理。希里糊肚的就用了,然后实现了功能。现在算是对之前的使用疑问进...

    写一点笔记
  • ScheduledThreadPoolExecutor源码学习

    ScheduledThreadPoolExecutor看样子也是ThreadPoolExecutor的一种,因为ThreadPoolExecutor也没有什么问...

    写一点笔记
  • ​Executors源码学习

    在学习并发线程池的时候,我们基本都已经学习了一下常见的线程。但是我们发现Executors这样一个类的存在。那么这个类是干什么的?其实在分析ThreadPool...

    写一点笔记
  • HashSet源码学习

    经过前两次的HashMap和HashTable的学习,我们准备将HashSet也进行学习一下,本着之间差异不是特别大的前提。但是实际上是这样吗?我们知道set就...

    写一点笔记

扫码关注腾讯云开发者

领取腾讯云代金券