ClassLoader

             1.ClassLoader作用

                顾名思义,就是用来加载Class文件到JVM,以供程序使用的。我们知道,java程序可以动态加载类定义,而这个动态加载机制就是通过ClassLoader来实现的。

                那么就有一个问题:既然ClassLoader是用来加载类到JVM的,那么ClassLoader又是如何加载的呢?难道它不是java类?确实有一个ClassLoader不是用java编写的,而是c++,是JVM实现的一部分,这个ClassLoader就是bootstrap ClassLoader(启动类加载器)。这个ClassLoader在JVM运行的时候加载java核心的类来满足java程序最基本的需求,其中就包括其他各个java实现的ClassLoader,如ExtClassLoader,这个ClassLoader用来加载java的扩展API,也就是/lib/ext中的类,一个是AppClassLoader,这个ClassLoader是用来加载用户机器上ClassPath目录中的Class,通常在没有指定ClassLoader的情况下,程序员自定义的类就由该ClassLoader进行加载。


             2.加载ClassLoader过程

                   当运行一个程序的时候,JVM启动,运行bootstrap ClassLoader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也是此时被加载的),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载ClassPath下定义的Class。


             3.ClassLoader的双亲委托模式

                   每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会有一个parent ClassLoader,我们可以看到ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoader的parent。注意:这个parent不是被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrap ClassLoader。那么这个parent有什么用呢?

                   假设我们自定义了一个ClientClassLoader,我们使用这个自定义的ClassLoader加载java.lang.String,那么这里String是否会被这个ClassLoader加载呢?事实上,java.lang.String这个类不会被ClientClassLoader加载,而是被bootstrap ClassLoader加载。为何会这样?原因就是双亲委托模式,因为在任何一个自定义ClassLoader加载一个类之前,都会先委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功时,才会自己加载。

                  在上面情况中,因为java.lang.String是属于java核心API的一个类,所以当使用ClientClassLoader加载它时,该ClassLoader会先委托它的父亲ClassLoader进行加载,上面讲过,当parent为null的时候,ClassLoader的parent就是bootstrap ClassLoader,所以在ClassLoader的最顶层就是bootstrap ClassLoader,因此最终委托到bootstrap的时候,bootstrap ClassLoader就会返回String的Class。

                 下面来看一下ClassLoader中加载类的代码

    protected synchronized Class<?> loadClass(String name, boolean resolve)  throws ClassNotFoundException
    {
		// First, check if the class has already been loaded
		Class c = findLoadedClass(name);
		if (c == null) 
		{
			try 
			{
				if (parent != null) 
				{
					c = parent.loadClass(name, false);
				}
				else 
				{
					c = findBootstrapClass0(name);
				}
			} 
			catch (ClassNotFoundException e)
			{
				// If still not found, then invoke findClass in order
				// to find the class.
				c = findClass(name);
			}
		}
		if (resolve) 
		{
			resolveClass(c);
		}
		return c;
    }
                 从上面的代码可以看出,我们加载一个类的大致过程与之前的描述是一致的,而我们要实现自定义ClassLoader时,只用实现findClass这个方法即可(实现查找Class文件用来加载)。


             4.使用双亲委托模式的原因

                    1)可以避免重复加载。

                          当父亲加载了该类的时候,就没有必要再使用子ClassLoader再加载一次。

                    2)考虑到安全i因素。

                          如果不使用这一种委托模式,那么我们就可以随时使用自定义的String来动态替代java核心API中定义的类型,这样会存在很大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义ClassLoader时无法加载一个自定义的String。


             5.类加载过程

            

      6.ClassLoader与命名空间

                     (1)定义类加载器

                               简单地说,就是实际加载类的ClassLoader

                     (2)初始类加载器

                                不是由他加载的,但是是他的祖先加载的,就叫初始类加载器

                               举个例子:如果代码中用到File这个类,在没有自定义ClassLoader的情况下,File类先交给AppClassLoader,由于双亲委托模式,又会交给ExtClassLoader,再交给BootstrpClassLoader。这样的话,File类实际上是由BootstrapClassLoader来加载的,所以BootstrapClassLoader就是定义类加载器,AppClassLoader和ExtClassLoader都叫做初始类加载器。

                      (3)命名空间

                                在代码中,包名+类名就构成了唯一一个类,但是在jvm中,这还不够,必须再加上ClassLoader。也就是说:

                                代码空间:ClassLoader + 包名 + 类名  才能构成唯一一个class

                                命名空间还有两个规则:

                       <1>如果class被某个ClassLoader的parent或者祖先加载了,那么在这个ClassLoader命名空间中也会添加这个类,即也能访问

                      <2>如果class被平级或者其他分支的下级ClassLoader加载了,肯定就不能访问了。

                              eg:

                               

                                  如果MyClassA.class文件被UserDefinedClassLoader2(CL2)加载,那么CL3是访问不到这个类的;但是如果被ApplicationClassLoader加载,那么CL1是可以访问的



      7.类的生命周期

                     当我们编写一个java源文件后,经过编译会生成一个  后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能在java虚拟机中运行。java类的生命周期就是指一个class文件从加载到卸载的全过程。

                     一个java类的完整的生命周期会经历加载、连接、初始化、使用、卸载五个过程。当然也有在加载货连接之后没有初始化就直接使用的情况。现在主要研究类加载器所执行的部分,也就是加载、连接和初始化

                 

                         1)加载:查找并加载类的class文件

                            实际上这个过程对应ClassLoader中的defineClass方法:

    /**
     * Converts an array of bytes into an instance of class <tt>Class</tt>.
     * Before the <tt>Class</tt> can be used it must be resolved.
 
     *
     * @param  name
     *         The expected <a href="#name">binary name</a> of the class, or
     *         <tt>null</tt> if not known
     *
     * @param  b
     *         The bytes that make up the class data.  The bytes in positions
     *         <tt>off</tt> through <tt>off+len-1</tt> should have the format
     *         of a valid class file as defined by the <a
     *         href="http://java.sun.com/docs/books/vmspec/">Java Virtual
     *         Machine Specification</a>.
     *
     * @param  off
     *         The start offset in <tt>b</tt> of the class data
     *
     * @param  len
     *         The length of the class data
     *
     * @return  The <tt>Class</tt> object that was created from the specified
     *          class data.
     *
     */
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
                       可以看出byte[]  b中实际上就是class文件的字节码,会返回具体的Class


                         2)连接:

                                 -验证:确保被加载的类的正确性

                                 -准备:为类的静态变量分配内存,并将其初始化为默认值

                                 -解析:把类中的符号引用转换为直接引用

    /**
     * Links the specified class.  This (misleadingly named) method may be
     * used by a class loader to link a class.  If the class <tt>c</tt> has
     * already been linked, then this method simply returns. Otherwise, the
     * class is linked as described in the "Execution" chapter of the <a
     * href="http://java.sun.com/docs/books/jls/">Java Language
     * Specification</a>.
     * </p>
     *
     * @param  c
     *         The class to link
     *
     * @throws  NullPointerException
     *          If <tt>c</tt> is <tt>null</tt>.
     *
     * @see  #defineClass(String, byte[], int, int)
     */
    protected final void resolveClass(Class<?> c) {
	check();
	resolveClass0(c);
    }

                         3)初始化:为类的静态变量赋予正确的初始值

                 从上面可以看出,类的静态变量赋了2次值,什么原因呢?原因是,在连接过程中为静态变量赋值为默认值,也就是说只要定义了静态变量,不管是否设置了初始值,都会先按照静态变量的类型给他一个默认值(如int为0)。到了初始化过程,系统就检查是否用户是否为静态变量设置了初始值,如果设置了就将静态变量设置为指定值。

                 

                

             7.Class类

                    每个被ClassLoader加载的class文件,最终都会以Class类的实例被程序员引用,我们可以把Class类当做是普通类的一个模板,JVM根据这个模板生成对应的实例,并最终被程序员使用。

                    Class.forName()是将类加载并且进行初始化,在JDBC中通常使用如下写法:

Class.forName("com.mysql.jdbc.Driver");
                  同样也可以使用如下写法:

com.mysql.jdbc.Driverdriver = new com.mysql.jdbc.Driver();
                 效果是完全一样的。

                 这么做是为了在后面调用DriverManager时,自己选的数据库连接Driver已经加载了,可以看一下加载代码:

publicclass Driver extends NonRegisteringDriver implements java.sql.Driver
{
    static
    {
       try
       {
           java.sql.DriverManager.registerDriver(newDriver());
       }
       catch (SQLException E) 
       {
           throw  new RuntimeException("Can't register driver!");
       }
}
            可以看出这是一个静态代码块,所以在类加载并初始化完成之后,就完成了注册。

           但是如果使用ClassLoader来加载,即如下写法:

ClassLoader cl = new ClassLoader();
cl.loadClass("com.mysql.jdbc.Driver")
           这样 只是在JVM中加载了该类,并没有进行初始化,所以必然会报错

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值