- 启动类加载器(BootstrapClassLoader):在JVM运行时被创建,负责加载存放在JDK安装目录下的jrelib的类文件,或者被-Xbootclasspath参数指定的路径中,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类无法被JAVA程序直接引用。
- 扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的jrelibext的类,或者由java.ext.dirs系统变量指定路径中的所有类库,开发者也可以直接使用扩展类加载器。
- 应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
- 用户自定义类加载器(User ClassLoader):JVM自带的类加载器是从本地文件系统加载标准的java class文件,而自定义的类加载器可以做到在执行非置信代码之前,自动验证数字签名,动态地创建符合用户特定需要的定制化构建类,从特定的场所(数据库、网络中)取得java class。
注意如上的类加载器并不是通过继承的方式实现的,而是通过组合的方式实现的。而JAVA虚拟机的加载模式是一种委派模式,如上图中的1-7步所示。下层的加载器能够看到上层加载器中的类,反之则不行。类加载器可以加载类但是不能卸载类。说了一大堆,还是感觉需要拿点代码说事。
首先我们先定义自己的类加载器MyClassLoader,继承自ClassLoader,并覆盖了父类的findClass(String name)方法,如下:
public class MyClassLoader extends ClassLoader{ private String loaderName; //类加载器名称 private String path = ""; //加载类的路径 private final String fileType = ".class"; public MyClassLoader(String name){ super(); //应用类加载器为该类的父类 this.loaderName = name; } public MyClassLoader(ClassLoader parent,String name){ super(parent); this.loaderName = name; } public String getPath(){return this.path;} public void setPath(String path){this.path = path;} @Override public String toString(){return this.loaderName;} @Override public Class<?> findClass(String name) throws ClassNotFoundException{ byte[] data = loaderClassData(name); return this.defineClass(name, data, 0, data.length); } //读取.class文件 private byte[] loaderClassData(String name){ InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { is = new FileInputStream(new File(path + name + fileType)); int c = 0; while(-1 != (c = is.read())){ baos.write(c); } data = baos.toByteArray();
} catch (Exception e) { e.printStackTrace(); } finally{ try { if(is != null) is.close(); if(baos != null) baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; }}
我们如何利用我们定义的类加载器加载指定的字节码文件(.class)呢?如通过MyClassLoader加载C:\Users\Administrator\下的Test.class字节码文件,代码如下所示:
public class Client { public static void main(String[] args) { // TODO Auto-generated method stub //MyClassLoader的父类加载器为系统默认的加载器AppClassLoader MyClassLoader myCLoader = new MyClassLoader("MyClassLoader"); //指定MyClassLoader的父类加载器为ExtClassLoader //MyClassLoader myCLoader = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent(),"MyClassLoader"); myCLoader.setPath("C:\Users\Administrator\"); Class<?> clazz; try { clazz = myCLoader.loadClass("Test"); Field[] filed = clazz.getFields(); //获取加载类的属性字段 Method[] methods = clazz.getMethods(); //获取加载类的方法字段 System.out.println("该类的类加载器为:" + clazz.getClassLoader()); System.out.println("该类的类加载器的父类为:" + clazz.getClassLoader().getParent()); System.out.println("该类的名称为:" + clazz.getName()); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
运行时数据区
字节码的加载第一步,其后分别是认证、准备、解析、初始化,那么这些步骤又具体做了哪些工作,以及他们会对运行时数据区缠身什么影响呢?如下图所示: