Java虚拟机类加载器

目录

何为类加载器

类与类加载器

类加载器的双亲委派模型

双亲委派模型好处

双亲委派模型是如何实现的?


文章摘自:深入理解Java虚拟机 第二版 周志明著 

本文主要讲解"类加载器"。

  • 何为类加载器

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类二进制字节流”这个动作放刀Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为”类加载器“。

  • 类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。

对于任意一个类,都需要由它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。即判断两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Calss文件,被同一个虚拟机加载,只要它们类加载器不同,那这两个类就不相等。这说的相等包括代表类的Calss对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,亦包括instanceof关键字做对象所属关系判定等情况。

package com.gary.test.classloader;

import java.io.IOException;
import java.io.InputStream;

public class ClassloaderTest {
	public static void main(String[] args) throws Exception {
		
		ClassLoader myLoader = new ClassLoader() {
			@Override
			public Class<?> loadClass(String name) throws ClassNotFoundException {
				try {
					String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
					
					InputStream is = getClass().getResourceAsStream(fileName);
					
					if (is == null) {
						return super.loadClass(name);
					}
					
					byte[] b = new byte[is.available()];
					is.read(b);
					return defineClass(name, b, 0, b.length);
				} catch (IOException e) {
					throw new  ClassNotFoundException(name); 
				}
			}
		};
		
		Object obj = myLoader.loadClass("com.gary.test.classloader.ClassloaderTest").newInstance();
		
		System.out.println(obj.getClass());
		System.out.println(obj instanceof com.gary.test.classloader.ClassloaderTest);
	}
}

结果是

class com.gary.test.classloader.ClassloaderTest
false

可以看出我们使用这个类加载器加载得到的类,并实例化这个类的对象,但是这个对象与与类com.gary.test.classloader.ClassloaderTest做所属类型检查的时候却返回了false。即两个CalssLoaderTest类,一个由系统应用程序类加载器加载的,另外一个是由我们自定义的类加载器加载的,虽然都是来自同一个Class文件,但是依然是两个独立的类。

  • 类加载器的双亲委派模型

  • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

  • 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:

  • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

如图下所示:

 这并不是一个强制性的请求,而是Java设计者推荐给开发者的一种类加载器实现方式。

双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的父类加载器中,只有当父加载器反馈自己无法完成这个加载请求时候,子加载器才回尝试自己去加载。

  • 双亲委派模型好处

从其工作过程可以看出一个显而易见的好处:Java类随着它的类加载器一起具备了一种优先级的层次关系。如java.lang.Object,它存放在rt.jar,无论哪一个类加载器yao加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,用户如果自己编写了一个称为java.lang.Object的类,并放在程序的CalssPath中,那系统中将会出现多个不同的Object类,Java体系中最基础的行为也就无法保证了。

  • 双亲委派模型是如何实现的?

实现双亲委派的代码集中在java.lang.ClaassLaoder的loadCalss方法中。如下图所示为jdk1.8中代码:

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

可以看出:先查是否已经被加载过,若没有加载则调用父加载器的loadCalss方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass方法进行类加载。

System.nanoTime();可用于计时,其返回值是从确定的值算起的,但是值任意,可能是一个未来的时间,所以返回的值可能为负数。

可以看出:想要定义类加载器,就需要重写findClass方法。

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

 

可以看出:我们需要在findCalss方法中实现将一个指定类名称转为Class对象。

如果读取一个指定的名称的类为字节数组的话,可以用defineCalss方法把字节数组转为Calss对象。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

一个例子:

package com.gary.test.classloader;

public class TestForClssLoder {
	
	public void hello() {
		System.out.println("我是由:" + 
				getClass().getClassLoader().getClass() + "加载的");
	}
}
package com.gary.test.classloader;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;

public class Test {
	static class MyClassLoader extends ClassLoader {
		private String classPath;
		
		public MyClassLoader() {
		}

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

		@Override
		protected Class<?> findClass(String name) throws ClassNotFoundException {
			
			try {
				String filename = classPath + "/" + name.replaceAll("\\.", "/") + ".class";
				FileInputStream fis = new FileInputStream(filename);
				int len = fis.available();
				byte[] b = new byte[len];
				fis.read(b);
				fis.close();
				
				return defineClass(name, b, 0, b.length);
			} catch (Exception e) {
				e.printStackTrace();
				throw new  ClassNotFoundException();
			} 
		}
		
		public static void main(String[] args) throws Exception {
			MyClassLoader classLoader = new MyClassLoader();
			Class<?> clazz = classLoader.loadClass("com.gary.test.classloader.TestForClssLoder");
			Object object = clazz.newInstance();
			Method method = clazz.getDeclaredMethod("hello");
			method.invoke(object);
		}
		
		
		
	}
}
我是由:class sun.misc.Launcher$AppClassLoader加载的

根据双亲委派模型可知通过sun.misc.Launcher$AppClassLoader 类加载器(Application ClassLoader)加载。

为了让我们自定义的类加载器加载,我们把TestForClssLoder.class文件放入到其他目录,并删除bin目录下的TestForClssLoder.class。

package com.gary.test.classloader;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;

public class Test {
	static class MyClassLoader extends ClassLoader {
		private String classPath;
		
		public MyClassLoader() {
		}

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

		@Override
		protected Class<?> findClass(String name) throws ClassNotFoundException {
			
			try {
				String filename = classPath + "/" + name.replaceAll("\\.", "/") + ".class";
				FileInputStream fis = new FileInputStream(filename);
				int len = fis.available();
				byte[] b = new byte[len];
				fis.read(b);
				fis.close();
				
				return defineClass(name, b, 0, b.length);
			} catch (Exception e) {
				e.printStackTrace();
				throw new  ClassNotFoundException();
			} 
		}
		
		public static void main(String[] args) throws Exception {
			MyClassLoader classLoader = new MyClassLoader("D:/test");
			Class<?> clazz = classLoader.loadClass("com.gary.test.classloader.TestForClssLoder");
			Object object = clazz.newInstance();
			Method method = clazz.getDeclaredMethod("hello");
			method.invoke(object);
		}
		
		
		
	}
}
我是由:class com.gary.test.classloader.Test$MyClassLoader加载的

成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值