第八章 Class装载系统

Class装载系统

一.class文件的装载过程

   class文件通常以文件的形式存在,只有被虚拟机装载的class类型才能在程序中使用.系统装载CLass类型可以分为加载,连接和初始化.

 

 
   1.1类装载的条件

    Class只有在必须要使用的时候才会被加载,一个类或者接口在初次使用前必须要初始化. 被动使用不会引起类的初始化.

     下面的例子,只会初始化Parent.   (PS:虽然Child没被初始化,但是已经被加载了)加上 -XX:+TraceClassLoading观察类加载过程.

      final字段由于它的不变性,虚拟机进行了优化,把这个变量直接放到常量池中.

class Child extends Parent {
   static{
	   System.out.println("child init");
   }
   public static int a = 2;
}
public class ClassMain{
	public static void main(String[] args) {
		System.out.println(Child.v);
                System.out.println(Child.a)
	}
}
class Parent{
	public static int v = 1;
	static{
		System.out.println("parent init");
	}
}
 

 

   1.2 加载类

    加载类,java虚拟机必须完成以下工作:

    1.通过类的全名,获取class的二进制流.

    2.解析类的二进制数据为方法区内的数据结构

    3.创建java.lang.class类的实例,作为该类型

  

二.掌握classloader

   classloader主要工作在CLass装载的加载阶段,主要作用是从系统外部获得Class 二进制数据.

   1.认识classloader,看懂类加载

    所有的类都是Classloader加载的,然后交给虚拟机进行后续的工作.因此,它只能影响到类的加载,而无法通过Classloader去改变类的其他行为.

   2.clasloader分类

   在标准java程序中,java虚拟机会创建3类Classloader.它们分别是BootStrap ClassLoader(启动类加载器),Extension ClassLoader(扩展类加载器),App ClassLoader(应用类加载器,系统类加载器).每个应用程序还可以拥有自定义Classloader,扩展java虚拟机获取Class数据的能力.

  当系统需要使用一个类时,判断类是否被加载,会从当前底层类开始判断。当需要加载一个类时,从顶层开始加载直到成功.

 .  


 

            启动类加载器是C实现的,没有java类与之对应.系统的核心类就是又启动类加载器进行加载的,它也是虚拟机的核心组件.扩展类和应用类加载器都有对应的java对象可供使用.

         启动类的加载器负责加载核心类,比如rt.jar中的java类;扩展类加载器用于加载%JAVA_HOME%/lib/ext/*.jar;应用类加载器用于加载用户程序的类.自定义加载器一般也是用于加载用户程序类.

            

public class ClassLoaderParent {
    public static void main(String[] args) {
           //核心类会被启动类加载器加载,所以返回的是null

     System.out.println(String.class.getClassLoader());

             ClassLoader cl = ClassLoaderParent.class.getClassLoader();
 		while(cl != null){
			System.out.println(cl);
			cl = cl.getParent();
		}
	}
} 
             
null
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
 

 

   3.classloader的双亲委托模式

       系统中的classloader协同工作时,默认会使用双亲委托模式.在加载的时候,系统会判断当前类是否已经被加载,如果被加载,直接使用.如果没被加载,会先请求双亲处理,处理失败则会自己加载.

       下面的代码,在eclipse中执行,默认会打出默认版本的信息.如果把修改版本的java文件用eclipse打包成find.jar.

      用启动命令-Xbootclasspath/a:/Users/zcf1/jvm/find.jar.那么会打印出修改版本的信息.这说明了如果要加载一个类,会先从顶层的加载器开始加载.

       

//默认版本
public class FindClassAppPath {
     public void print(){
    	 System.out.println("I'm in app loader");
     }
}
      
//修改版本
public class FindClassAppPath {
     public void print(){
    	 System.out.println("I'm in boot loader");
     }
}
 

 

  

public class FindClassLoader {
      public static void main(String[] args) {
	      FindClassAppPath fcap = new FindClassAppPath();
	      fcap.print();
	 }
}
 

 

    修改一下FindClassLoader,用当前classloader去加载FindClassAppPath.class.再去调用的时候,会先从当前类的classloader开始寻找.new 一个类的时候会需要加载这个类,首先从底层的应用类加载器开始判断,如果存在,就不会请求上层的类加载器了.如果都不存在,会从最上层的加载器开始加载.

    

public class FindClassLoader {
	public static void main(String[] args) throws ClassNotFoundException, Exception, SecurityException {
		ClassLoader cl = FindClassLoader.class.getClassLoader();
		File f = new File(
				"/Users/zcf1/Documents/workspace/Learn/target/classes/com/zcf/jvm/chapter10/FindClassAppPath.class");

		InputStream in = new FileInputStream(f);

		ByteBuffer buf = ByteBuffer.allocate(10000);
		int temp = 0;
		while ((temp = in.read()) != -1) {
			buf.put((byte) temp);
		}
		in.close();
		byte[] tempbytes = new byte[buf.position()];
		buf.flip();
		buf.get(tempbytes);
		Method m = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
		m.setAccessible(true);
		m.invoke(cl, tempbytes, 0, tempbytes.length);
		m.setAccessible(false);
		FindClassAppPath fca = new FindClassAppPath();
		System.out.println(fca.getClass().getClassLoader());
		fca.print();
	}
}
 

  4.突破双亲模式

   

public class SelfDefinedClassLoader extends ClassLoader{
    private String filePath;
    
	public SelfDefinedClassLoader(String filePath) {
		this.filePath = filePath;
	}

	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		Class<?> cl = findClass(name);
		if(cl == null){
			System.out.println("自定义加载器加载类失败");
			cl =super.loadClass(name);
		}
		return  cl;
	}
	
	private String getFilePath(String name){
		StringBuffer absolue = new StringBuffer( this.filePath + File.separator+
				name.replace(".", "/")+".class");
//		int lastIndex = absolue.lastIndexOf("/");
//		absolue.replace(lastIndex, lastIndex+1, ".");
		return absolue.toString();
	}
	
    protected Class<?> findClass(String name){
    	//先从当前classloader中查找
    	Class<?> cl = this.findLoadedClass(name);
    	
    	if( cl == null){
    		File f = new File(getFilePath(name));
            FileInputStream in;
			try {
				in = new FileInputStream(f);
				FileChannel fc = in.getChannel();
	    		ByteArrayOutputStream bs = new ByteArrayOutputStream();
	    		WritableByteChannel wb = Channels.newChannel(bs);
	    		ByteBuffer buf = ByteBuffer.allocate(1024);
	    		while(true){
	    			int i  = fc.read(buf);
	    			if( i ==0 || i==-1 ){
	    				break;
	    			}
	    			buf.flip();//limit = position,position = 0;
	    			//把buf里面的数据写到outstream里
	    			wb.write(buf);
	    			buf.clear();//limit = capaticy,position = 0
	    		}
	    		in.close();
	    		byte[] bytes = bs.toByteArray();
	    		cl = defineClass(name, bytes, 0, bytes.length);
			} catch (FileNotFoundException e) {
				// TODO 请把关键信息(包括堆栈)记入日志,并删除下面一行。
				e.printStackTrace();
			} catch (IOException e) {
				// TODO 请把关键信息(包括堆栈)记入日志,并删除下面一行。
				e.printStackTrace();
			}
    	}
    	return cl;
    	
    	
    }
    
    public static void main(String[] args) throws ClassNotFoundException {
		//System.out.println("com.zcf".replaceAll("\\.", "/"));
    	SelfDefinedClassLoader sdc = new SelfDefinedClassLoader("/Users/zcf1/Documents/workspace/Learn/target/classes/");
		Class cl = sdc.loadClass("com.zcf.jvm.chapter10.FindClassAppPath");
		System.out.println(cl.getClassLoader());
		System.out.println("类加载器树");
		ClassLoader cd = cl.getClassLoader();
		while( cd != null){
			
			System.out.println(cd.getParent());
			cd = cd.getParent();
		}
	}
}
 

 

   首先试图由OrderClassLoader加载object类,但是路径中没有,加载失败 ,随后由应用类加载器加载成功,接着加载FindClassAppPath,orderClassLoader在指定路径下找到该类信息并加载成功.如果上面的路径/Users/zcf1/Documents/workspace/Learn/target/classes/填写错误,因为FindClassAppPath在classpath下,照样会由Appclassloader加载成功.

 

java.io.FileNotFoundException: /Users/zcf1/Documents/workspace/Learn/target/classes/java/lang/Object.class (No such file or directory)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at com.zcf.jvm.chapter10.SelfDefinedClassLoader.findClass(SelfDefinedClassLoader.java:69)
	at com.zcf.jvm.chapter10.SelfDefinedClassLoader.loadClass(SelfDefinedClassLoader.java:45)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.zcf.jvm.chapter10.SelfDefinedClassLoader.findClass(SelfDefinedClassLoader.java:86)
	at com.zcf.jvm.chapter10.SelfDefinedClassLoader.loadClass(SelfDefinedClassLoader.java:45)
	at com.zcf.jvm.chapter10.SelfDefinedClassLoader.main(SelfDefinedClassLoader.java:103)
自定义加载器加载类失败
com.zcf.jvm.chapter10.SelfDefinedClassLoader@7852e922
类加载器树
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@5c647e05
null

5.热替换实现

     java不像其他脚本语言,通过替换文件的形式来实现热替换.只能通过classloader来做文章.由不同的ClassLoader加载的同名类属于不同的类型,不能相互转换.

     

public class ClassInDifClassLoader {
	public static void main(String[] args) throws ClassNotFoundException, Exception, SecurityException {
		
		ClassLoader cl = ClassInDifClassLoader.class.getClassLoader();
		File f = new File(
				"/Users/zcf1/Documents/workspace/Learn/target/classes/com/zcf/jvm/chapter10/FindClassAppPath.class");
        InputStream in = new FileInputStream(f);
		ByteBuffer buf = ByteBuffer.allocate(10000);
		int temp = 0;
		while ((temp = in.read()) != -1) {
			buf.put((byte) temp);
		}
		in.close();
		byte[] tempbytes = new byte[buf.position()];
		buf.flip();
		buf.get(tempbytes);
                //加载到应用加载器中
		Method m = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
		m.setAccessible(true);
		m.invoke(cl, tempbytes, 0, tempbytes.length);
		m.setAccessible(false);
                //加载到启动类加载器中,转换成应用类加载器的class   转换失败
		FindClassAppPath fca = (FindClassAppPath) cl.getParent().loadClass("com.zcf.jvm.chapter10.FindClassAppPath").newInstance();
		System.out.println(fca.getClass().getClassLoader());
		fca.print();
	}
}

   

Exception in thread "main" java.lang.ClassCastException: com.zcf.jvm.chapter10.FindClassAppPath cannot be cast to com.zcf.jvm.chapter10.FindClassAppPath
	at com.zcf.jvm.chapter10.ClassInDifClassLoader.main(ClassInDifClassLoader.java:52)

     利用这个特点,可以用来进行热替换的模拟.

   

 

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;

/**
 * 〈一句话功能简述〉
 * 〈功能详细描述〉
 *  热替换classLoder
 * @author zcf1
 * @version Ver 1.0 2018年1月17日
 * @since Ver 1.0
 */
public class ReplaceClsClassLoader extends ClassLoader{
private String filePath;
    
	public ReplaceClsClassLoader(String filePath) {
		this.filePath = filePath;
	}
        private String getFilePath(String name){
		int lastIndex = name.lastIndexOf(".");
		StringBuffer absolue = new StringBuffer( this.filePath + File.separator+
			name.substring(lastIndex+1)+".class");
		
		
		return absolue.toString();
	}
	
    protected Class<?> findClass(String name){
    	//先从当前classloader中查找
    	Class<?> cl = this.findLoadedClass(name);
    	
    	if( cl == null){
    		File f = new File(getFilePath(name));
            FileInputStream in;
			try {
				in = new FileInputStream(f);
				FileChannel fc = in.getChannel();
	    		ByteArrayOutputStream bs = new ByteArrayOutputStream();
	    		WritableByteChannel wb = Channels.newChannel(bs);
	    		ByteBuffer buf = ByteBuffer.allocate(1024);
	    		while(true){
	    			int i  = fc.read(buf);
	    			if( i ==0 || i==-1 ){
	    				break;
	    			}
	    			buf.flip();//limit = position,position = 0;
	    			//把buf里面的数据写到outstream里
	    			wb.write(buf);
	    			buf.clear();//limit = capaticy,position = 0
	    		}
	    		in.close();
	    		byte[] bytes = bs.toByteArray();
	    		cl = defineClass(name, bytes, 0, bytes.length);
			} catch (FileNotFoundException e) {
				// TODO 请把关键信息(包括堆栈)记入日志,并删除下面一行。
				e.printStackTrace();
			} catch (IOException e) {
				// TODO 请把关键信息(包括堆栈)记入日志,并删除下面一行。
				e.printStackTrace();
			}
    	}
    	return cl;
    	
    	
    }
    
    public static void main(String[] args) throws ClassNotFoundException, Exception, IllegalAccessException {
		while(true){
			ReplaceClsClassLoader sdc = new ReplaceClsClassLoader("/Users/zcf1/jvm/");
			Class cl = sdc.loadClass("com.zcf.jvm.chapter10.FindClassAppPath");
			Object ob = cl.newInstance();
			Method method = ob.getClass().getMethod("print", new Class[]{});
			method.invoke(ob, new Object[]{});
			Thread.sleep(3000);
		}

	}
}

   替换?/Users/zcf1/jvm/中的FindClassAppPath.class文件,就可以看到不同的打印结果.

猜你喜欢

转载自zcf9916.iteye.com/blog/2407907