一. 类加载器java
JVM中的类加载器:在jvm中,存在两种类加载器,
a) Boostrap ClassLoader:这个是由c++实现的,因此在方法区并无Class对象的实例存在。用于加载JAVA_HOME/bin目录下的jar包c++
b) 其余类加载器:由java实现,能够在方法区找到其Class对象。这里又细分为几个加载器web
扩展类加载器(Extension ClassLoader):它负责用于加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量指定所指定的路径中全部类库,开发者能够直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的能够经过程序来查看。System.getProperty("java.ext.dirs")api
应用程序类加载器(Application ClassLoader):负责加载用户类路径上指定的类库。开发者能够直接使用这个类加载器。ps:在没有指定自定义类加载器的状况下,这就是程序的默认加载器。数组
自定义类加载器(User ClassLoader):缓存
双亲委派模型:避免因为Class字节码被屡次加载。底层类加载器在收到一个类加载的请求的时候,都先把请求转发给其父加载器(并非一个继承的关系),父类查找不到才会让子类去加载。若是强制只有双亲委派模型,那么,web服务器的隔离是没法实现的。tomcat
因为最近想作一个相似tomcat同样的简易版web服务器来加深理解http请求的处理过程。咱们都清楚,每一个web应用在tomcat中均可以使用本身版本的jar。除了少许的包,如Servlet-api.jar,还有一些java原生的包以外,tomcat是会为每一个不一样的应用加载不一样的jar包或者class,且彼此之间不会相互影响。这一步是经过自定义类加载器来实现的,在虚拟机层面,判断两个Class是否相等的前提是他们是同一个类加载器加载的,不然就没有意义了。这篇文章简单的实现一个自定义的加载过程,PS:这个例子并无破坏双亲委派模型,由于例子中依然会查找父类,若是找不到再使用子类加载。接下来笔者会再更新破坏双亲委派模型的博客,这里挖个坑。
首先自定义类加载器,最重要的就是先继承ClassLoader这个类。加载器的加载流程是,给出一个Class文件的全限定名,而后调用loadClass方法,这个方法每部会如今本身已经加载的类中查找,若是找到就返回。找不到则向父类查找,若是父类都找不到这才开始本身加载,调用findClass方法。因此咱们只要覆盖findClass方法就能够实现本身定义的加载了。顺带一提,ClassLoader中有一个方法叫defineClass(String, byte[], int, int);这个方法经过传进去一个Class文件的字节数组,就能够方法区生成一个Class对象。因此要实现findClass的目标就很明确了,只要将Class文件读取进来,而后生成byte数组,调用defineClass方法就能够了。服务器
@Override protected Class<?> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; }
那么如何找到Class文件呢?手动生成/网络下载咱们就暂时不谈论。这里只说两种最多见的,一是直接.class文件中查找,二是从jar包中加载。
从class文件中加载很是简单。只要找到相应的文件,就能够经过字节流读取进来。代码以下:网络
input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray();
从jar读取则相对麻烦一点,java给咱们提供了一个专门用来读取jar包文件的类,抽象成一个JarFile的对象。经过调用这个对象的getInputStream方法,也是能够获取文件的输入流,从而读取字节数组。笔者作了一点相应的缓存,若是每次查找文件都要先读取jar文件,再遍历查找class文件是很是耗时的操做。因而,笔者选择再加载以前,把全部的jar包中的全部class读取到内存中,保存在一个map对象中。创建一个全限定名和字节数组的映射。这样在加载阶段,就能省下不少的时间了。所有的代码以下jvm
package com.chasel.cloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 自定义的类加载器【子类优先】 * @author hujiancai * @description * @data 2017年3月11日 * @version v_0.1 */ public class MyWebAppLoader extends ClassLoader{ /** * lib:表示加载的文件在jar包中 * 相似tomcat就是{PROJECT}/WEB-INF/lib/ */ private String lib; /** * classes:表示加载的文件是单纯的class文件 * 相似tomcat就是{PROJECT}/WEB-INF/classes/ */ private String classes; /** * 采起将全部的jar包中的class读取到内存中 * 而后若是须要读取的时候,再从map中查找 */ private Map<String, byte[]> map; /** * 只须要指定项目路径就好 * 默认jar加载路径是目录下{PROJECT}/WEB-INF/lib/ * 默认class加载路径是目录下{PROJECT}/WEB-INF/classes/ * @param webPath * @throws MalformedURLException * @throws SecurityException * @throws NoSuchMethodException */ public MyWebAppLoader(String webPath) throws NoSuchMethodException, SecurityException, MalformedURLException{ lib = webPath + "WEB-INF/lib/"; classes = webPath + "WEB-INF/classes/"; map = new HashMap<String,byte[]>(64); preReadJarFile(); } /** * 按照父类的机制,若是在父类中没有找到的类 * 才会调用这个findClass来加载 * 这样只会加载放在本身目录下的文件 * 而系统自带须要的class并非由这个加载 */ @Override protected Class<?> findClass(String name){ try { byte[] result = getClassFromFileOrMap(name); if(result == null){ throw new FileNotFoundException(); }else{ return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 从指定的classes文件夹下找到文件 * @param name * @return */ private byte[] getClassFromFileOrMap(String name){ String classPath = classes + name.replace('.', File.separatorChar) + ".class"; File file = new File(classPath); if(file.exists()){ InputStream input = null; try { input = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(input != null){ try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } }else{ if(map.containsKey(name)) { //去除map中的引用,避免GC没法回收无用的class文件 return map.remove(name); } } return null; } /** * 预读lib下面的包 */ private void preReadJarFile(){ List<File> list = scanDir(); for(File f : list){ JarFile jar; try { jar = new JarFile(f); readJAR(jar); } catch (IOException e) { e.printStackTrace(); } } } /** * 读取一个jar包内的class文件,并存在当前加载器的map中 * @param jar * @throws IOException */ private void readJAR(JarFile jar) throws IOException{ Enumeration<JarEntry> en = jar.entries(); while (en.hasMoreElements()){ JarEntry je = en.nextElement(); String name = je.getName(); if (name.endsWith(".class")){ String clss = name.replace(".class", "").replaceAll("/", "."); if(this.findLoadedClass(clss) != null) continue; InputStream input = jar.getInputStream(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } byte[] cc = baos.toByteArray(); input.close(); map.put(clss, cc);//暂时保存下来 } } } /** * 扫描lib下面的全部jar包 * @return */ private List<File> scanDir() { List<File> list = new ArrayList<File>(); File[] files = new File(lib).listFiles(); for (File f : files) { if (f.isFile() && f.getName().endsWith(".jar")) list.add(f); } return list; } /** * 添加一个jar包到加载器中去。 * @param jarPath * @throws IOException */ public void addJar(String jarPath) throws IOException{ File file = new File(jarPath); if(file.exists()){ JarFile jar = new JarFile(file); readJAR(jar); } } }