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文件,就可以看到不同的打印结果.
相关推荐
随着越来越多的第三方语言(Groovy、Scala、JRuby等)...第9~10章介绍了虚拟机的核心——Class文件结构,以及虚拟机中类的装载系统。第11章介绍了虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。
8. **类装载器优化**:优化主要包括减少类装载的数量、避免类装载器泄露以及合理设计类装载器层次。例如,使用全限定类名作为缓存的key可以减少不必要的类装载;确保当不再使用一个类装载器时,及时释放其引用,避免...
《实战Java虚拟机——JVM故障诊断与性能优化》内容...第9~~10章介绍了虚拟机的核心——Class文件结构,以及虚拟机中类的装载系统。第11章介绍了虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。
- 判断当前字节的最高位(即第8位)是否为1。 - 如果最高位为1,则进行左移操作并在最后一位加上1;如果最高位为0,则直接进行左移操作。 - 将处理后的字节写入新的文件中。 3. **文件写入**:使用`...
第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。 《实战Java虚拟机——JVM故障诊断与...
第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。, 《实战Java虚拟机——JVM故障诊断与...
第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。 《实战Java虚拟机——JVM故障诊断与...
第八章 使用Test::Class进行单元测试 编写测试用例 创建测试夹具 继承测试 用Test::Class跳过测试 用Test::Class标注TODO测试 第九章 其他类型的测试 编写可测试的程序 发生了什么事? 测试程序 测试交互式程序 ...
第9~10章介绍了Java虚拟机的核心——Class文件结构,以及Java虚拟机中类的装载系统。第11章介绍了Java虚拟机的执行系统和字节码,并给出了通过ASM框架进行字节码注入的案例。, 《实战Java虚拟机——JVM故障诊断与...
第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 ...
第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 解析...
第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 ...
第八章使用Test::Class进行单元测试 编写测试用例 创建测试夹具 继承测试 用Test::Class跳过测试 用Test::Class标注TODO测试 第九章其他类型的测试 编写可测试的程序 发生了什么事? 测试程序 测试交互式程序 测试...
第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 ...
class装载流程 ClassLoader模式 ClassLoader的使用实例分析 热替换例子 详细介绍ClassLoader的原理和应用。分析2个案例,说明ClassLoader的使用。 第七课 性能监控工具 线程死锁分析 OOM分析 介绍常用的JVM诊断和...
第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...
#### 第八章 异常的实现 ##### 8.1 Java中的异常 Java提供了丰富的异常处理机制,MiniJavaVM需要实现相应的异常处理逻辑。 ##### 8.2 异常在MiniJavaVM中的实现 MiniJavaVM中的异常处理主要包括异常的抛出、捕获...
《自己动手写Java虚拟机及class文件解析分析工具(java8运行)》是一份深入探讨Java虚拟机(JVM)工作原理以及如何解析与分析Java类文件(.class)的资源。通过使用Go语言实现一个简化的JVM,这份资料旨在帮助读者...