`

第八章 Class装载系统

    博客分类:
  • jvm
 
阅读更多

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文件,就可以看到不同的打印结果.

  • 大小: 157.6 KB
  • 大小: 156.1 KB
  • 大小: 257.8 KB
分享到:
评论

相关推荐

    实战JAVA虚拟机 JVM故障诊断与性能优化

    随着越来越多的第三方语言(Groovy、Scala、JRuby等)...第9~10章介绍了虚拟机的核心——Class文件结构,以及虚拟机中类的装载系统。第11章介绍了虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。

    深入JVM内核—原理、诊断与优化视频教程-6. 类装载器

    8. **类装载器优化**:优化主要包括减少类装载的数量、避免类装载器泄露以及合理设计类装载器层次。例如,使用全限定类名作为缓存的key可以减少不必要的类装载;确保当不再使用一个类装载器时,及时释放其引用,避免...

    实战Java虚拟机——JVM故障诊断与性能优化 pdf

    《实战Java虚拟机——JVM故障诊断与性能优化》内容...第9~~10章介绍了虚拟机的核心——Class文件结构,以及虚拟机中类的装载系统。第11章介绍了虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。

    利用类装载器动态加载类并启动类

    - 判断当前字节的最高位(即第8位)是否为1。 - 如果最高位为1,则进行左移操作并在最后一位加上1;如果最高位为0,则直接进行左移操作。 - 将处理后的字节写入新的文件中。 3. **文件写入**:使用`...

    实战JAVA虚拟机++JVM故障诊断与性能优化.pdf

    第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。 《实战Java虚拟机——JVM故障诊断与...

    实战JAVA虚拟机

    第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。, 《实战Java虚拟机——JVM故障诊断与...

    实战java虚拟机

    第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。 《实战Java虚拟机——JVM故障诊断与...

    Perl Testing程序高手秘笈

    第八章 使用Test::Class进行单元测试 编写测试用例 创建测试夹具 继承测试 用Test::Class跳过测试 用Test::Class标注TODO测试 第九章 其他类型的测试 编写可测试的程序 发生了什么事? 测试程序 测试交互式程序 ...

    实战Java虚拟机——JVM故障诊断与性能优化

    第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。, 《实战Java虚拟机——JVM故障诊断与...

    深入JAVA虚拟机(第2版)

    第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info入口 S.1.6 ...

    深入java虚拟机第二版

    第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info 入口 S.1.6 解析...

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析constant_class_info入口 8.1.5 解析constant_fieldref_info入口 s.1.6 ...

    Perl Testing程序高手秘籍

    第八章使用Test::Class进行单元测试 编写测试用例 创建测试夹具 继承测试 用Test::Class跳过测试 用Test::Class标注TODO测试 第九章其他类型的测试 编写可测试的程序 发生了什么事? 测试程序 测试交互式程序 测试...

    深入Java虚拟机

    第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info 入口 S.1.6 ...

    深入JVM内核 - 原理、诊断与优化

    class装载流程 ClassLoader模式 ClassLoader的使用实例分析 热替换例子 详细介绍ClassLoader的原理和应用。分析2个案例,说明ClassLoader的使用。 第七课 性能监控工具 线程死锁分析 OOM分析 介绍常用的JVM诊断和...

    Thinking in Java 中文第四版+习题答案

    第8章 对象的容纳 8.1 数组 8.1.1 数组和第一类对象 8.1.2 数组的返回 8.2 集合 8.2.1 缺点:类型未知 8.3 枚举器(反复器) 8.4 集合的类型 8.4.5 再论枚举器 8.5 排序 8.6 通用集合库 8.7 新集合 8.7.1 使用 8.7.2...

    一个Java虚拟机的设计和实现

    #### 第八章 异常的实现 ##### 8.1 Java中的异常 Java提供了丰富的异常处理机制,MiniJavaVM需要实现相应的异常处理逻辑。 ##### 8.2 异常在MiniJavaVM中的实现 MiniJavaVM中的异常处理主要包括异常的抛出、捕获...

    自己动手写Java虚拟机及class文件解析分析工具(java8运行)

    《自己动手写Java虚拟机及class文件解析分析工具(java8运行)》是一份深入探讨Java虚拟机(JVM)工作原理以及如何解析与分析Java类文件(.class)的资源。通过使用Go语言实现一个简化的JVM,这份资料旨在帮助读者...

Global site tag (gtag.js) - Google Analytics