深入分析ClassLoader工作机制
2016-12-10 00:26
288 查看
ClassLoader 较为深入分析。
from <深入分析Java Web>加载CLASS到JVM中,审查每个类应该由谁加载,父优先的等级加载机制。
加载机制
ClassLoader类结构分析
ClassLoader抽象类,有很多子类,一般在实现自己的ClassLoader时候,一般都会继承URLClassLoader这个子类,因为这个类已经实现了大部分的工作,就像Servlet通过会直接HttpServlet一样。打开源码:
几个重要的方法
protected final Class
等级加载机制
一层一层判断是否应该由本层ClassLoader加载,并通过在哪一层加载确定类的加载级别。Bootstrap ClassLoader :主要加载JVM自身工作需要的类,这个ClassLoader完全由JVM自己控制的。不准守普通的加载规则,没有父类和子类。
sun.misc.Launcher java程序的入口就是sun.misc.Launcher,jdk的扩展类加载器ExtClassLoader和系统类加载器AppClassLoader都是Launcher的内部类。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。下列代码是Launcher构建的过程。
public Launcher() { Launcher.ExtClassLoader var1; ... var1 = Launcher.ExtClassLoader.getExtClassLoader(); ... this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); .... }
类图:
![](http://i.imgur.com/gzKo70q.jpg)
可以看到APPClassLoader和ExtClassLoader都继承自URLClassLoader,在构建Launcher过程中,先创建ExtClassLoader,然后根据ExtClassLoader作为父加载器创建APPClassLoader。然后使用Launcher的getLauncher方法得到的就是APPClassLoader。如果在Java应用中没有其他的ClassLoader,则除了”java.ext.dirs”目录下的类由ExtClassLoader加载的外,其他的都是由APPClassLoader。看一下源码。
ExtClassLoader:
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { final File[] var0 = getExtDirs();//这里 try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction() { public Launcher.ExtClassLoader run() throws IOException { int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { MetaIndex.registerDirectory(var0[var2]); } return new Launcher.ExtClassLoader(var0);///这里 } }); } catch (PrivilegedActionException var2) { throw (IOException)var2.getException(); } } private static File[] getExtDirs() { String var0 = System.getProperty("java.ext.dirs"); ... }
APPClassLoader:使用ExtClassLoader作为父类加载器
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path");//这里 final File[] var2 = var1 == null?new File[0]:Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0);//这里 } }); }
这是URLClassLoader的:
public URLClassLoader(URL[] urls, ClassLoader parent,....){}
加载Class文件到内存中的2种方式
一种是隐式加载,一种是显式加载。隐式加载: 就是不需要在代码中调用ClassLoader来加载的类,而是通过JVM来自动加载这些需要的类到内存中的方式,比如在我们继承或引用某些类的时候,JVM分析当前类引用的类不在内存中,那么就会自动加载类到内存中。
显示加载:就是在代码中调用ClassLoader来加载一个类,比如
this.getClass().getClassLoader().loadClass()或者
Class.forName()或者自定义的ClassLoader的loadClass方法。
混合的方式,就是在自定义ClassLoader时候引用其他类,就算是隐式加载。
加载class的过程
加载过程,简单地看了一下代码。大概是:—>findClass(final String name)
—> defineClass(String name, Resource res)
—>Class
加载字节码到内存中
在抽象类ClassLoader中,很多实现都交给子类实现,如如何找到,如何加载到内存中。就是findClass()方法。最常用的URLClassLoader的findClass源码如下;protected Class<?> findClass(final String name) throws ClassNotFoundException { try { return AccessController.doPrivileged( new PrivilegedExceptionAction<Class>() { public Class run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res != null) { try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { throw new ClassNotFoundException(name); } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } }
简要说明一下:首先将Path,类名的包定义,由’.’换为路径的’/’。并将.class加上。然后ucp(URLClassPath类型)定义哪里找到这个class文件,读取它的字节流。然后通过defineClass来实例类class对象。根据URL传过来的是Jar包还是实际Class文件,就能创建不同的Loader了,这样就实现加载Class文件到内存中。下图还可以看出Loader是URLClassPath的内部类。
![](http://i.imgur.com/xMSnfY0.jpg)
常见加载类错误分析
ClassNotFoundException
这个非常常见的错误,一般问题是找不到.class文件,就是classpath路径和你想要的class文件路径不在一个地方。如果不知道classpath路径是什么,可以通过下列代码方式找到。this.getClass().getClassLoader().getResource("").toString();
通常加载一个不知道的类时就会发生这样的错误。
public class notFound{ public static void main(String []args){ try { Class.forName("NotFoundClass"); }catch(ClassNotFoundException e){ e.printStackTrace(); } } }
显示加载的几种方式:
Class.forName();
ClassLoader的loadClass();
ClassLoader的findSystemClass();
UnsatisfiedLinkError
这个问题不常见,出现的场景就是调用某些本地lib中的native方法时,没有找到这样的lib。报出的错误。/** * 文件描述: * 作者: bamboo * 时间: 2016/12/9 */ public class NoLibException { public native void nativeMethod(); static { System.loadLibrary("NoLib"); } public static void main(String[] args) { new NoLibException().nativeMethod(); } }
java.lang.UnsatisfiedLinkError: no NoLib in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886) at java.lang.Runtime.loadLibrary0(Runtime.java:849) at java.lang.System.loadLibrary(System.java:1088) at CCF.NoLibException.<clinit>(NoLibException.java:11)
ClassCastException
强制转换异常,比如说想把String强制转换为Integer代码
import java.util.HashMap; import java.util.Map; /** * 文件描述: * 作者: bamboo * 时间: 2016/12/9 */ public class CastException { public static Map m = new HashMap() { { put("a", "2"); } }; public static void main(String[] args) { Integer integer= (Integer) m.get("a"); System.out.println(integer); } }
结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at CCF.CastException.main(CastException.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
实现自己的ClassLoader
之前写的博客。http://blog.csdn.net/newpidian/article/details/52831121 主要实现以下3个功能
获取类加载器
根据类名称加载类
获取指定包下的所有类
加载自定义格式的Class文件
实现一个从远端服务器,经过加密的class文件的网络传输,在本地解密,并通过defineClass方法创建这个类的实例的方法。代码如下:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.net.URL; /** * 文件描述: * 作者: bamboo * 时间: 2016/12/9 */ public class NetClassLoader extends ClassLoader { private String classPath; private String packageName; public NetClassLoader(ClassLoader parent, String classPath, String packageName) { super(parent); this.classPath = classPath; this.packageName = packageName; } public NetClassLoader(String classPath, String packageName) { this.classPath = classPath; this.packageName = packageName; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (aClass != null) { return aClass; } if (packageName.startsWith(name)) { byte[] classData = getData(name); if (classData == null) { throw new ClassNotFoundException(); } else { defineClass(name, classData, 0, classData.length); } } return super.loadClass(name); } private byte[] getData(String className) { String path = classPath + File.separator + className.replace('.', File.separatorChar) + ".class"; try { URL url = new URL(path); InputStream is = url.openStream(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] deCode(byte[] src) { byte[] decode = null; ///字节编码处理 return decode; } }
实现类的热部署
在JVM加载类之前会先判定要请求的类是否已经被加载过来,就是通过findLoadedClass()来返回类实例,如果已经加载了,则loadClass()会冲突,如何判断是不是同一个类有2个条件。类名是否完全一样,包括包名。
看这个类的ClassLoader是否完全一样。这里指的是是不是同一个ClassLoader实例。即使是同一个ClassLoader类的不同实例,加载的类也不一样。
所以实现热部署,可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名类。
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * 文件描述: * 作者: bamboo * 时间: 2016/12/9 */ public class ClassReloader extends ClassLoader { private String classPath; public ClassReloader(ClassLoader parent, String classPath) { super(parent); this.classPath = classPath; } public ClassReloader(String classPath) { this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getData(String className) { String path = classPath + className+".class"; try { InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { try { String path = "C:\\Users\\bamboo\\Desktop\\单源\\Leetcode\\out\\"; ClassReloader reloader = new ClassReloader(path); Class r = reloader.findClass("AllOne"); System.out.println((r.newInstance())); ClassReloader reloader1 = new ClassReloader(path); Class r1 = reloader1.findClass("AllOne"); System.out.println((r1.newInstance())); } catch (Exception e) { e.printStackTrace(); } } }
输出
AllOne@26a30589 AllOne@ec5aba9
如果将
Class r1 = reloader1.findClass("AllOne");改为
Class r1 = reloader.findClass("AllOne");
则会抛出duplicate class definition,类的重复定义。
AllOne@642423ad Exception in thread "main" java.lang.LinkageError: loader (instance of ParallelBasic/ClassReloader): attempted duplicate class definition for name: "AllOne" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:800) at java.lang.ClassLoader.defineClass(ClassLoader.java:643) at ParallelBasic.ClassReloader.findClass(ClassReloader.java:32) at ParallelBasic.ClassReloader.main(ClassReloader.java:61) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
一个问题:如果使用不同的ClassLoader实例加载同一个类会怎么样,会不会导致JVM的PermGen(永久代)n区无限增大??答案是否定的,why,因为ClassLoader对象也是对象,在没有被持有引用的时候,也会被JVM回收。值得注意的是,被ClassLoader加载的类的字节码会一直保存在JVM的PermGen中,这个数据一般是在Full GC的时候才会被回收,所以,如果应用大量使用动态类加载。Full GC又不是太频繁,也要注意PermGen的大小,防止内存溢出。
相关文章推荐
- 深入分析ClassLoader工作机制
- 详细深入分析 Java ClassLoader 工作机制
- 深入分析ClassLoader工作机制
- 详细深入分析 Java ClassLoader 工作机制
- 深入分析 Java ClassLoader 工作机制
- 深入分析 Java I/O 的工作机制
- (转)深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 转载:深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 【转载】深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制
- 深入分析 Java I/O 的工作机制