jvm原理(20)Launcher类源码分析以及forName方法底层剖析

Posted 魔鬼_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm原理(20)Launcher类源码分析以及forName方法底层剖析相关的知识,希望对你有一定的参考价值。

上一节我们通过【ClassLoader.getSystemClassLoader()】得到系统类加载器,那么本节看一下这个方法的doc以及一些细节,方便我们更好的理解:
public static ClassLoader getSystemClassLoader()
Returns the system class loader for delegation. This is the default delegation parent for new ClassLoader instances, and is typically the class loader used to start the application.
返回用于代理的系统类加载器,对于一个类加载器实例来说这是默认的父级代理,也是最典型的用于启动应用的类加载器。
This method is first invoked early in the runtime’s startup sequence, at which point it creates the system class loader and sets it as the context class loader of the invoking Thread.
这个方法最早在运行时启动的就会被调用,在这个调用点上回去创建胸膛类加载器,并且将其设置在调用线程为【上下文加载器】。
The default system class loader is an implementation-dependent instance of this class.
默认的系统类加载器是一个与这个类实现相关的实例
If the system property “java.system.class.loader” is defined when this method is first invoked then the value of that property is taken to be the name of a class that will be returned as the system class loader. The class is loaded using the default system class loader and must define a public constructor that takes a single parameter of type ClassLoader which is used as the delegation parent. An instance is then created using this constructor with the default system class loader as the parameter. The resulting class loader is defined to be the system class loader.
如果系统属性【java.system.class.loader】被定义,那么这个属性的值对应的class会被默认的系统类加载器 加载,并且作为系统类加载器,这个被定义的加载器必须有一个单个参数的构造器,参数的类型是ClassLoader,用来代理父加载器,使用这个构造器传入默认的的系统类加载器会创建一个类加载器的实例,这个实例会成为系统类加载器。
If a security manager is present, and the invoker’s class loader is not null and the invoker’s class loader is not the same as or an ancestor of the system class loader, then this method invokes the security manager’s checkPermission method with a RuntimePermission(“getClassLoader”) permission to verify access to the system class loader. If not, a SecurityException will be thrown.
安全略过,主要是介绍一些安全接入的权限是否可以接入

伏笔:
1、【上下文加载器】
2、定义ClassLoader类型的构造器
方法体:

    public static ClassLoader getSystemClassLoader() 
        initSystemClassLoader();//初始化系统类加载器
        if (scl == null) 
            return null;
        
        //下边的是安全相关的,不做介绍
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) 
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        
        return scl;
    

进入到initSystemClassLoader():

PS:
sclSet、scl 成员变量介绍:
    // The class loader for the system
    // @GuardedBy("ClassLoader.class")
    系统类加载器在ClassLoader的引用
    private static ClassLoader scl;

    // Set to true once the system class loader has been set
    // @GuardedBy("ClassLoader.class")
    //如果系统类加载器被加载过,即scl成员变量不是null,那么sclSet的值为true
    private static boolean sclSet;

    private static synchronized void initSystemClassLoader() 
        //系统类加载器是否被设置
        if (!sclSet) 
             //双重判断,即sclSet是true,但是系统类加载器是null,排除非法异常
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
                //通过sun.misc.Launcher获取系统类加载器。sun.misc.Launcher是系统类加载器,扩展类加载器上层的一个概念,并且Oracle不是开源的
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) 
                Throwable oops = null;
                scl = l.getClassLoader();
                try 
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                 catch (PrivilegedActionException pae) 
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) 
                        oops = oops.getCause();
                    
                
                if (oops != null) 
                    if (oops instanceof Error) 
                        throw (Error) oops;
                     else 
                        // wrap the exception
                        throw new Error(oops);
                    
                
            
            sclSet = true;
        
    

我们在idea里边看到的sun.misc.Launcher.getLauncher()的实现是反编译工具给出的,oracle并没有给出源码,但是我们可以去openjdk获取他的源码,也可以使用http://grepcode.com/search查看源码:
http://grepcode.com/search/?query=sun.misc.launcher
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/misc/Launcher.java#Launcher
sun.misc.Launcher.getLauncher()z返回值是sun.misc.Launcher内部的一个静态成员变量【 private static Launcher launcher = new Launcher();】
那么我们看一下Launcher的构造器:

public Launcher() 
68         // Create the extension class loader
            //获得系统类加载器
69         ClassLoader extcl;
70         try 
                //ExtClassLoader是Launcher的一个内部类,getExtClassLoader()getExtClassLoader()获取扩展类加载器,
                /**
                    getExtClassLoader()方法里边返回扩展类加载器实例,并且通过系统属性java.ext.dirs加载其需要加载的类
                */
71             extcl = ExtClassLoader.getExtClassLoader();
72          catch (IOException e) 
73             throw new InternalError(
74                 "Could not create extension class loader", e);
75         
76 
77         // Now create the class loader to use to launch the application
            //获取系统类加载器,并且将扩展类加载器作为他的委托传入
78         try 
79             loader = AppClassLoader.getAppClassLoader(extcl);
80          catch (IOException e) 
81             throw new InternalError(
82                 "Could not create application class loader", e);
83         
84 
85         // Also set the context class loader for the primordial thread.
            //将系统类加载器作为当前线程上下文的加载器
86         Thread.currentThread().setContextClassLoader(loader);
87 
88         // Finally, install a security manager if requested
89         String s = System.getProperty("java.security.manager");
90         if (s != null) 
91             SecurityManager sm = null;
92             if ("".equals(s) || "default".equals(s)) 
93                 sm = new java.lang.SecurityManager();
94              else 
95                 try 
96                     sm = (SecurityManager)loader.loadClass(s).newInstance();
97                  catch (IllegalAccessException e) 
98                  catch (InstantiationException e) 
99                  catch (ClassNotFoundException e) 
100                 catch (ClassCastException e) 
101                
102            
103            if (sm != null) 
104                System.setSecurityManager(sm);
105             else 
106                throw new InternalError(
107                    "Could not create SecurityManager: " + s);
108            
109        
110    

扩展类加载器获取加载文件数组:

169        private static File[]  getExtDirs() 
                //加载来源来自于系统属性java.ext.dirs
170            String s = System.getProperty("java.ext.dirs");
171            File[] dirs;
172            if (s != null) 
173                StringTokenizer st =
174                    new StringTokenizer(s, File.pathSeparator);
175                int count = st.countTokens();
176                dirs = new File[count];
177                for (int i = 0; i < count; i++) 
178                    dirs[i] = new File(st.nextToken());
179                
180             else 
181                dirs = new File[0];
182            
183            return dirs;
184        

getAppClassLoader的方法:
入参是扩展类加载器

267        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
268            throws IOException
269        
                //通过系统属性java.class.path获取加载路径
270            final String s = System.getProperty("java.class.path");
271            final File[] path = (s == null) ? new File[0] : getClassPath(s);
272
273            // Note: on bugid 4256530
274            // Prior implementations of this doPrivileged() block supplied
275            // a rather restrictive ACC via a call to the private method
276            // AppClassLoader.getContext(). This proved overly restrictive
277            // when loading  classes. Specifically it prevent
278            // accessClassInPackage.sun.* grants from being honored.
279            //
280            return AccessController.doPrivileged(
281                new PrivilegedAction<AppClassLoader>() 
282                    public AppClassLoader More ...run() 
283                    URL[] urls =
284                        (s == null) ? new URL[0] : pathToURLs(path);
                        //构造系统类加载器的时候将他的委托父类传入,以及加载路径数组
285                    return new AppClassLoader(urls, extcl);
286                
287            );
288        
289
290        final URLClassPath ucp;
291
292        /*
293         * Creates a new AppClassLoader
294         */
295        AppClassLoader(URL[] urls, ClassLoader parent) 
                //调用父类URLClassLoader,注意不管是系统类加载器还是扩展类加载器都是继承来自ClassLoader
                //CLassLoader里边之前说过有一个硬编码的成员变量【private final ClassLoader parent;】
                //此处的parent一直溯源到ClassLoader 类,将ClassLoader 的parent赋值为当前方法传入的parent入参,
                //也就说明了系统类加载器的委托父类就是扩展类加载器
296            super(urls, parent, factory);
297            ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
298            ucp.initLookupCache(this);
299        

然后我们再回过头来看看idea里边反编译出来的获取系统类加载器的方法:

大家可能会有疑问,为什么反编译出来的变量都是var1、var2之类的呢?其实这个在龙哥的kotlin课程中【27_Kotlin函数使用综述与显式返回类型分析】一讲里边介绍过,Java中并不总是在class 字节码中携带变量名,但是在kotlin里边就不是这样的,因为kotlin里边有具名参数的概念。

ok,我们回到initSystemClassLoader()方法,通过调用【 sun.misc.Launcher l = sun.misc.Launcher.getLauncher();】获取到系统类加载器之后,赋值给ClassLoader的scl成员变量之后,还有一块代码:

                try 
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                 catch (PrivilegedActionException pae) 
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) 
                        oops = oops.getCause();
                    
                

这块逻辑的作用是看看是否设置了系统属性【java.system.class.loader】,即自定义的系统类加载器,如果设置了那么实例化自定义的系统类加载器返回,替代之前获取的系统类加载器,如果没有设置直接返回默认的系统类加载器。

class SystemClassLoaderAction
    implements PrivilegedExceptionAction<ClassLoader> 
    private ClassLoader parent;

    SystemClassLoaderAction(ClassLoader parent) 
        this.parent = parent;
    

    public ClassLoader run() throws Exception 
        String cls = System.getProperty("java.system.class.loader");
        //自定义系统类加载器未定义,直接返回之前默认获取的系统类加载器
        if (cls == null) 
            return parent;
        
        //如果定义了自定义的系统类加载器那么实例化并返回,替代默认的系统类加载器
        //etDeclaredConstructor(new Class<?>[]  ClassLoader.class )这个就是我们之前
        //(https://blog.csdn.net/wzq6578702/article/details/79835695)
        //在MyTest16里边加入一个参数是ClassLoader的构造器的原因,
        //因为自定义的的加载器在这里会被反射调用,
        //
        Constructor<?> ctor = Class.forName(cls, true, parent)
            .getDeclaredConstructor(new Class<?>[]  ClassLoader.class );
        ClassLoader sys = (ClassLoader) ctor.newInstance(
            new Object[]  parent );
            //同样的道理 将自定义系统类加载器作为当前线程的上下文加载器。
        Thread.currentThread().setContextClassLoader(sys);
        return sys;
    

那么接下来介绍一下 Class.forName(cls, true, parent),Class.forName方法大部分做web开发的都是在开始写jdbc的时候接触,那么先看一下doc:
java.lang.Class
public static Class

    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) 
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            //获取调用此forName方法的类的Class对象A
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) 
                //获取A的类加载器
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                //安全检查
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) 
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                
            
        
        //name:要被加载的|initialize:是否初始化|loader:指定的类加载器(自定义加载器此处是系统类加载器)|caller调用者的Class对象
        return forName0(name, initialize, loader, caller);
    
    //forName0是一个本地方法
    /** Called after security check for system loader access checks have been made. */
    private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;

另外和只有一个参数的forName的区别:

   public static Class<?> forName(String className)
                throws ClassNotFoundException 
        Class<?> caller = Reflection.getCallerClass();
        // className:要加载的Class名字 | 是否初始化:此处需要初始化 |调用者的类加载器 | 调用者的Class对象
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    

以上是关于jvm原理(20)Launcher类源码分析以及forName方法底层剖析的主要内容,如果未能解决你的问题,请参考以下文章

6. 通过demo分析自定义类加载器以及Launcher源码分析

Launcher类源码分析

别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析JVM篇二

从源码透彻理解JVM类加载机制

Android 4.0 Launcher2源码分析——主布局文件(转)

Jetcd源码以及raft算法的分析(下)