`

ClassLoader-学习

阅读更多

相关文章:
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/#download
http://blog.csdn.net/zhoudaxia/article/details/35897057
ClassLoader:类装载器是用来把类(class)装载进JVM的。

在java语言中,是个非常重要的概念。平时我们接触不太多。但是在以下领域我们需要了解ClassLoader的特性:
1、热部署。
2、在类加载阶段,修改字节码,增加特殊功能。(一般用的比较少)
3、jvm通过特定的类加载器实现安全性的要求

JAVA程序对类的使用方式可分为两种:
主动使用
被动使用

所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们
主动使用的情况:
1、创建类的实例
2、访问某个类或者接口的静态变量,或者对其进行赋值
3、调用类的静态方法
4、反射
5、初始化一个类的子类(接口不适用)
6、JAVA虚拟机启动时被标明类启动类的类


先了解下关于ClassLoader的基本知识:
1、已经加载的类,不能重复加载,否则会抛出重复加载的异常。为了保证类型安全,必须保证在任何时刻,只要使用相同class loader对象装载同名的类,那么得到的class实例都是相同的。

    为了做到这一点,就不能采用系统默认的类加载器委托规则,也就是说我们定制的类加载器在加载时,可以打破规则,自定义加载的顺序,当然这个是要以为功能的需要为目的。
     类的加载又分为显式加载和隐式加载。大家使用 new 关键字创建类实例时,其实就隐式地包含了类的加载过程。对于类的显式加载来说,比较常用的是 Class.forName,其实,它们都是通过调用 ClassLoader 类的 loadClass 方法来完成类的实际加载工作的。直接调用 ClassLoader 的 loadClass 方法是另外一种不常用的显式加载类的技术。
2、java是动态加载类的,并且是逐级别的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式。
  (1)首先加载核心API,让系统最基本的运行起来
  (2)加载扩展类
  (3)加载用户自定义的类



3、一个Class 要想卸载,有一下要求:
1、该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
2、加载该类的ClassLoader已经被GC。
3、该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

 

代码验证:

private static void testBasic(){
		/**
		 * ouput: null 启动类加载器(Bootstrap ClassLoader)
		 *  这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中
		 * ,用来加载java的核心库,此类加载器并不继承于java.lang
		 * .ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.
		 */
		System.out.println(List.class.getClassLoader());

		/**
		 * ouput: sun.misc.Launcher$AppClassLoader@38da9246
		 * 这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,
		 * 这个类加载器是CLassLoader中的getSystemClassLoader
		 * ()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.
		 */
		System.out.println(ClassLoaderTest.class.getClassLoader());

		/**
		 * output:sun.misc.Launcher$ExtClassLoader@15b94ed3 扩展类加载器(ExtendsionClassLoader):     
		 * 这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库,用来加载java的扩展库,
		 * 开发者可以直接使用这个类加载器.
		 */
		System.out.println(DNSNameService.class.getClassLoader());
	}

 

内部class loader:
1、bootstrap class loader --引导类加载器,它负责加载Java的核心类【java.* 】,不是 java.lang.ClassLoader的子类,这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中。

 

2、extension classloader -扩展类加载器(sun.misc.Launcher$ExtClassLoader@15b94ed3),它负责加载JRE的扩展目录【javax.* 】(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。

 

3、system classloader -系统(也称为应用)类加载器(sun.misc.Launcher$AppClassLoader@38da9246

null),它负责加载系统或用户实现的类,在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径,如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。ClassLoader.getSystemClassLoader()获取。


4、User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。


ClassLoader加载过程:

classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。



ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

    
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
// 检查类是否已被装载过    
Class c = findLoadedClass(name);        
if (c == null ) {        
         // 指定类未被装载过        
         try {        
                 if (parent != null ) {        
                         // 如果父类加载器不为空, 则委派给父类加载        
                         c = parent.loadClass(name, false );        
                 } else {        
                         // 如果父类加载器为空, 则委派给启动类加载加载        
                         c = findBootstrapClass0(name);        
                 }        
         } catch (ClassNotFoundException e) {        
                 // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其        
                 // 捕获, 并通过findClass方法, 由自身加载        
                 c = findClass(name);        
         }        
 }
}



扩展ClassLoader方法:
1、复写方法   public    Class findClass(String name)
2、或者复写public     byte [] loadClassData(String name)



ClassLoader的方法说明:
1、findLoadedClass:每个类加载器都维护有自己的一份已加载类名字空间,其中不能出现两个同名的类。凡是通过该类加载器加载的类,无论是直接的还是间接的,都保存在自己的名字空间中,该方法就是在该名字空间中寻找指定的类是否已存在,如果存在就返回给类的引用,否则就返回 null。这里的直接是指,存在于该类加载器的加载路径上并由该加载器完成加载,间接是指,由该类加载器把类的加载工作委托给其他类加载器完成类的实际加载。
2、getSystemClassLoader:Java2 中新增的方法。该方法返回系统使用的 ClassLoader。可以在自己定制的类加载器中通过该方法把一部分工作转交给系统类加载器去处理。
3、defineClass:该方法是 ClassLoader 中非常重要的一个方法,它接收以字节数组表示的类字节码,并把它转换成 Class 实例,该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。
4、loadClass:加载类的入口方法,调用该方法完成类的显式加载。通过对该方法的重新实现,我们可以完全控制和管理类的加载过程。
5、resolveClass:链接一个指定的类。这是一个在某些情况下确保类可用的必要方法,详见 Java 语言规范中“执行”一章对该方法的描述。

自定义类加载器初探:

package com.gym.backadmin.controller;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
 * 自定义类加载器
 * @author xinchun.wang 
   @email: 532002108@qq.com
 * @createTime 2015-4-5 下午7:54:11
 */
public class MyClassLoader extends ClassLoader {
	/**
	 * just a test 
	 */
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] classData = loadClassData(name);
		return defineClass(classData, 0, classData.length);
	}
	
	private byte[] loadClassData(String urlString) {
		try {			
			//return byteOutput.toByteArray();
			FileInputStream in =new FileInputStream(urlString);			
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			FileChannel channel =in.getChannel();            
            WritableByteChannel outchannel = Channels.newChannel(out); 
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 
            while (true) { 
                int i = channel.read(buffer); 
                if (i == 0 || i == -1) { 
                    break; 
                } 
                buffer.flip(); 
                outchannel.write(buffer); 
                buffer.clear(); 
            }             
			byte[] bytes =out.toByteArray();
			out.close();
			in.close();
			return bytes;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	public static void main(String[] args) throws Exception {
		MyClassLoader loader = new MyClassLoader();
		Class<?> userClass = loader.loadClass("D:\\gym\\haha\\com\\gym\\backadmin\\service\\user\\BasicUserService.class");
		Object userService = userClass.newInstance();
		Method m = userClass.getMethod("getUser", new Class[]{}); 
	    Object result = m.invoke(userService, new Object[]{}); 
	    System.out.println(result);
	}
}


运行结果:
{username=ooooooooooooo}
请注意:我把BasicUserService.class 文件放到了一个随意的目录。然后去加载二进制文件的。

/**
 * 
 * @author xinchun.wang 
   @email: 532002108@qq.com
 */
public class BasicUserService implements UserService {
	public Map<String, Object> getUser() {
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("username", "ooooooooooooo");
		return model;
	}
}



另外:Object userService = userClass.newInstance();
如果改为:BasicUserService userService = (BasicUserService)userClass.newInstance();
会出现:


原因如下:
因为在 Java 中,即使是同一个类文件,如果是由不同的类加载器实例加载的,那么它们的类型是不相同的。在上面的例子中 userClass 是由 MyClassLoader 加载的,而 userService 变量类型声名和转型里的 BasicUserService 类却是由 run 方法所属的类的加载器(默认为 AppClassLoader)加载的,因此是完全不同的类型,所以会抛出转型异常。

 

特别注意:对于接口:
1、初始化一个类并不会先初始化它所实现的接口。
2、初始化一个接口时,并不会先初始化它的父接口。

/**
 * @author xinchun.wang 
   @email: 532002108@qq.com
 * @createTime 2015-4-7 上午10:58:57
 */
public class ImplementsTest {

	public static void main(String[] args) {
		A a2 = new A2();
		System.out.println(a2); // 注意A没有被初始化
		System.out.println(a2.a); //才会对A接口进行初始化,可以注释掉本行 做测试
	}

	static interface A {
		public static final A a = new A1();
	}

	static class A1 implements A {
		static {
			System.out.println("hello world in A");
		}
	}

	static class A2 implements A {

	}
}

  • 大小: 19.6 KB
  • 大小: 5.8 KB
  • 大小: 75.8 KB
  • 大小: 175.2 KB
1
2
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics