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