JVM 详解

ClassLoader一个常常出现又让不少人望而却步的词,本文将试图以最浅显易懂的方式来说解 ClassLoader,但愿能对不了解该机制的朋友起到一点点做用。

要深刻了解ClassLoader,首先就要知道ClassLoader是用来干什么的,顾名思义,它就是用来加载Class文件到JVM,以供程序使用的。咱们知道,java程序能够动态加载类定义,而这个动态加载的机制就是经过ClassLoader来实现的,因此可想而知ClassLoader的重要性如何。

看到这里,可能有的朋友会想到一个问题,那就是既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类?

没有错,在这里确实有一个ClassLoader不是用java语言所编写的,而是JVM实现的一部分,这个ClassLoader就是bootstrap classloader(启动类加载器),这个ClassLoader在JVM运行的时候加载java核心的API以知足java程序最基本的需求,其中就包括用户定义的ClassLoader,这里所谓的用户定义是指经过java程序实现的ClassLoader,一个是ExtClassLoader,这个ClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类,一个是AppClassLoader,这个ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,一般在没有指定ClassLoader的状况下,程序员自定义的类就由该ClassLoader进行加载。

当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),而后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

上面大概讲解了一下ClassLoader的做用以及一个最基本的加载流程,接下来将讲解一下ClassLoader加载的方式,这里就不得不讲一下ClassLoader在这里使用了双亲委托模式进行类加载。

每个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每一个ClassLoader都会有一个parent ClassLoader,咱们能够看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader,若是这个parent为null,那么就默认该ClassLoader的parent是bootstrap classloader,这个parent有什么用呢?

咱们能够考虑这样一种状况,假设咱们自定义了一个ClientDefClassLoader,咱们使用这个自定义的ClassLoader加载java.lang.String,那么这里String是否会被这个ClassLoader加载呢?事实上java.lang.String这个类并非被这个ClientDefClassLoader加载,而是由bootstrap classloader进行加载,为何会这样?实际上这就是双亲委托模式的缘由,由于在任何一个自定义ClassLoader加载一个类以前,它都会先委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader没法加载成功后,才会由本身加载,在上面这个例子里,由于java.lang.String是属于java核心API的一个类,因此当使用ClientDefClassLoader加载它的时候,该ClassLoader会先委托它的父亲ClassLoader进行加载,上面讲过,当ClassLoader的parent为null时,ClassLoader的parent就是bootstrap classloader,因此在ClassLoader的最顶层就是bootstrap classloader,所以最终委托到bootstrap classloader的时候,bootstrap classloader就会返回String的Class。

咱们来看一下ClassLoader中的一段源代码:
java

Java代码   收藏代码
  1. protectedsynchronized Class loadClass(String name, boolean resolve)  程序员

  2. throws ClassNotFoundException  bootstrap

  3.   {  api

  4. // 首先检查该name指定的class是否有被加载缓存

  5. Class c = findLoadedClass(name);  安全

  6. if (c == null) {  网络

  7. try {  app

  8. if (parent != null) {  jvm

  9. //若是parent不为null,则调用parent的loadClass进行加载ide

  10. = parent.loadClass(name, false);  

  11.    } else {  

  12. //parent为null,则调用BootstrapClassLoader进行加载

  13.        c = findBootstrapClass0(name);  

  14.    }  

  15.    } catch (ClassNotFoundException e) {  

  16. //若是仍然没法加载成功,则调用自身的findClass进行加载            

  17.        c = findClass(name);  

  18.    }  

  19. }  

  20. if (resolve) {  

  21.    resolveClass(c);  

  22. }  

  23. return c;  

  24.   }  



从上面一段代码中,咱们能够看出一个类加载的大概过程与以前我所举的例子是同样的,而咱们要实现一个自定义类的时候,只须要实现findClass方法便可。

为何要使用这种双亲委托模式呢?

第一个缘由就是由于这样能够避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

第二个缘由就是考虑到安全因素,咱们试想一下,若是不使用这种委托模式,那咱们就能够随时使用自定义的String来动态替代java核心api中定义类型,这样会存在很是大的安全隐患,而双亲委托的方式,就能够避免这种状况,由于String已经在启动时被加载,因此用户自定义类是没法加载一个自定义的ClassLoader。

上面对ClassLoader的加载机制进行了大概的介绍,接下来不得不在此讲解一下另一个和ClassLoader相关的类,那就是Class类,每一个被ClassLoader加载的class文件,最终都会以Class类的实例被程序员引用,咱们能够把Class类看成是普通类的一个模板,JVM根据这个模板生成对应的实例,最终被程序员所使用。

咱们看到在Class类中有个静态方法forName,这个方法和ClassLoader中的loadClass方法的目的同样,都是用来加载class的,可是二者在做用上却有所区别。
Class<?> loadClass(String name)
Class<?> loadClass(String name, boolean resolve)
咱们看到上面两个方法声明,第二个方法的第二个参数是用于设置加载类的时候是否链接该类,true就链接,不然就不链接。

说到链接,不得不在此作一下解释,在JVM加载类的时候,须要通过三个步骤,装载、链接、初始化。装载就是找到相应的class文件,读入JVM,初始化就不用说了,最主要就说说链接。

链接分三步,第一步是验证class是否符合规格,第二步是准备,就是为类变量分配内存同时设置默认初始值,第三步就是解释,而这步就是可选的,根据上面loadClass方法的第二个参数来断定是否须要解释,所谓的解释根据《深刻JVM》这本书的定义就是根据类中的符号引用查找相应的实体,再把符号引用替换成一个直接引用的过程。有点深奥吧,呵呵,在此就很少作解释了,想具体了解就翻翻《深刻JVM吧》,呵呵,再这样一步步解释下去,那就不知道何时才能解释得完了。

咱们再来看看那个两个参数的loadClass方法,在JAVA API 文档中,该方法的定义是protected,那也就是说该方法是被保护的,而用户真正应该使用的方法是一个参数的那个,一个参数的loadclass方法实际上就是调用了两个参数的方法,而第二个参数默认为false,所以在这里能够看出经过loadClass加载类实际上就是加载的时候并不对该类进行解释,所以也不会初始化该类。而Class类的forName方法则是相反,使用forName加载的时候就会将Class进行解释和初始化,forName也有另一个版本的方法,能够设置是否初始化以及设置ClassLoader,在此就很少讲了。

不知道上面对这两种加载方式的解释是否足够清楚,就在此举个例子吧,例如JDBC DRIVER的加载,咱们在加载JDBC驱动的时候都是使用的forName而非是ClassLoader的loadClass方法呢?咱们知道,JDBC驱动是经过DriverManager,必须在DriverManager中注册,若是驱动类没有被初始化,则不能注册到DriverManager中,所以必须使用forName而不能用loadClass。

经过ClassLoader咱们能够自定义类加载器,定制本身所须要的加载方式,例如从网络加载,从其余格式的文件加载等等均可以,其实ClassLoader还有不少地方没有讲到,例如ClassLoader内部的一些实现等等,原本但愿可以讲得简单易懂一点,但是结果本身看回头好像感受并不怎么样,郁闷,看来本身的文笔仍是差太多了,但愿可以给一些有须要的朋友一点帮助吧。


JVM在加载类的时候,都是经过ClassLoader的loadClass()方法来加载class的,loadClass(String name)方法:

使用的是双亲委托模式:

jvm启动时,会启动jre/rt.jar里的类加载器:bootstrap classloader,用来加载java核心api;而后启动扩展类加载器ExtClassLoader加载扩展类,并加载用户程序加载器AppClassLoader,并指定ExtClassLoader为他的父类;

当类被加载时,会先检查在内存中是否已经被加载,若是是,则再也不加载,若是没有,再由AppClassLoader来加载,先从jar包里找,没有再从classpath里找;

若是自定义loader类,就会存在这命名空间的状况,不一样的加载器加载同一个类时,产生的实例实际上是不一样的;

Java代码

  
  
  
  
  1. public Class<?> loadClass(String name) throws ClassNotFoundException {

  2. return loadClass(name, false);

  3. }

  4. public Class<?> loadClass(String name) throws ClassNotFoundException {

  5. return loadClass(name, false);

  6. }

loadClass(String name)方法再调用loadClass(String name, boolean resolve)方法:

◆ name - 类的二进制名称

◆ resolve - 若是该参数为 true,则分析这个类

Java代码

  
  
  
  
  1. protectedsynchronized Class<?> loadClass(String name, boolean resolve)

  2. throws ClassNotFoundException

  3. {

  4. // First, check if the class has already been loaded

  5. //JVM 规范规定ClassLoader能够在缓存保留它所加载的Class,若是一个Class已经被加载过,则直接从缓存中获取

  6. Class c = findLoadedClass(name);

  7. if (c == null) {

  8. try {

  9. if (parent != null) {

  10. c = parent.loadClass(name, false);

  11. } else {

  12. c = findBootstrapClass0(name);

  13. }

  14. } catch (ClassNotFoundException e) {

  15. // If still not found, then invoke findClass in order

  16. // to find the class.

  17. c = findClass(name);

  18. }

  19. }

  20. if (resolve) {

  21. resolveClass(c);

  22. }

  23. return c;

  24. }

  25. protectedsynchronized Class<?> loadClass(String name, boolean resolve)

  26. throws ClassNotFoundException

  27. {

  28. // First, check if the class has already been loaded

  29. //JVM 规范规定ClassLoader能够在缓存保留它所加载的Class,若是一个Class已经被加载过,则直接从缓存中获取

  30. Class c = findLoadedClass(name);

  31. if (c == null) {

  32. try {

  33. if (parent != null) {

  34. c = parent.loadClass(name, false);

  35. } else {

  36. c = findBootstrapClass0(name);

  37. }

  38. } catch (ClassNotFoundException e) {

  39. // If still not found, then invoke findClass in order

  40. // to find the class.

  41. c = findClass(name);

  42. }

  43. }

  44. if (resolve) {

  45. resolveClass(c);

  46. }

  47. return c;

  48. }

若是ClassLoader并无加载这个class,则调用findBootstrapClass0:

Java代码

  
  
  
  
  1. private Class findBootstrapClass0(String name)

  2. throws ClassNotFoundException

  3. {

  4. check();

  5. if (!checkName(name))

  6. thrownew ClassNotFoundException(name);

  7. return findBootstrapClass(name);

  8. }

  9. private Class findBootstrapClass0(String name)

  10. throws ClassNotFoundException

  11. {

  12. check();

  13. if (!checkName(name))

  14. thrownew ClassNotFoundException(name);

  15. return findBootstrapClass(name);

  16. }

该方法会调用check()方法来判断这个类是否已经初始化,而且经过checkName(name)来判断由name指定的这个类是否存在最后调用findBootstrapClass(name):

Java代码

  
  
  
  
  1. privatenative Class findBootstrapClass(String name)

  2. throws ClassNotFoundException;

  3. privatenative Class findBootstrapClass(String name)

  4. throws ClassNotFoundException;

而这个findBootstrapClass方法是一个native方法,这是咱们的root loader,这个载入方法并不是是由JAVA所写,而是C++写的,它会最终调用JVM中的原生findBootstrapClass方法来完成类的加载。

若是上面两个都找不到,则使用findClass(name)来查找指定类名的Class:

Java代码

  
  
  
  
  1. protected Class<?> findClass(String name) throws ClassNotFoundException {

  2. thrownew ClassNotFoundException(name);

  3. }

  4. protected Class<?> findClass(String name) throws ClassNotFoundException {

  5. thrownew ClassNotFoundException(name);

  6. }

JDK5.0中的说明:

使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在经过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。默认实现抛出一个ClassNotFoundException。

因此,咱们在自定义类中,只须要重写findClass()便可。

MyClassLoader类:

Java代码

  
  
  
  
  1. publicclass MyClassLoader extends ClassLoader {

  2. private String fileName;

  3. public MyClassLoader(String fileName) {

  4. this.fileName = fileName;

  5. }

  6. protected Class<?> findClass(String className) throws ClassNotFoundException {

  7. Class clazz = this.findLoadedClass(className);

  8. if (null == clazz) {

  9. try {

  10. String classFile = getClassFile(className);

  11. FileInputStream fis = new FileInputStream(classFile);

  12. FileChannel fileC = fis.getChannel();

  13. ByteArrayOutputStream baos = new

  14. ByteArrayOutputStream();

  15. WritableByteChannel outC = Channels.newChannel(baos);

  16. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  17. while (true) {

  18. int i = fileC.read(buffer);

  19. if (i == 0 || i == -1) {

  20. break;

  21. }

  22. buffer.flip();

  23. outC.write(buffer);

  24. buffer.clear();

  25. }

  26. fis.close();

  27. byte[] bytes = baos.toByteArray();

  28. clazz = defineClass(className, bytes, 0, bytes.length);

  29. } catch (FileNotFoundException e) {

  30. e.printStackTrace();

  31. } catch (IOException e) {

  32. e.printStackTrace();

  33. }

  34. }

  35. return clazz;

  36. }

  37. privatebyte[] loadClassBytes(String className) throws

  38. ClassNotFoundException {

  39. try {

  40. String classFile = getClassFile(className);

  41. FileInputStream fis = new FileInputStream(classFile);

  42. FileChannel fileC = fis.getChannel();

  43. ByteArrayOutputStream baos = new

  44. ByteArrayOutputStream();

  45. WritableByteChannel outC = Channels.newChannel(baos);

  46. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  47. while (true) {

  48. int i = fileC.read(buffer);

  49. if (i == 0 || i == -1) {

  50. break;

  51. }

  52. buffer.flip();

  53. outC.write(buffer);

  54. buffer.clear();

  55. }

  56. fis.close();

  57. return baos.toByteArray();

  58. } catch (IOException fnfe) {

  59. thrownew ClassNotFoundException(className);

  60. }

  61. }

  62. private String getClassFile(String name) {

  63. StringBuffer sb = new StringBuffer(fileName);

  64. name = name.replace('.', File.separatorChar) + ".class";

  65. sb.append(File.separator + name);

  66. return sb.toString();

  67. }

  68. }

  69. publicclass MyClassLoader extends ClassLoader {

  70. private String fileName;

  71. public MyClassLoader(String fileName) {

  72. this.fileName = fileName;

  73. }

  74. protected Class<?> findClass(String className) throws ClassNotFoundException {

  75. Class clazz = this.findLoadedClass(className);

  76. if (null == clazz) {

  77. try {

  78. String classFile = getClassFile(className);

  79. FileInputStream fis = new FileInputStream(classFile);

  80. FileChannel fileC = fis.getChannel();

  81. ByteArrayOutputStream baos = new

  82. ByteArrayOutputStream();

  83. WritableByteChannel outC = Channels.newChannel(baos);

  84. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  85. while (true) {

  86. int i = fileC.read(buffer);

  87. if (i == 0 || i == -1) {

  88. break;

  89. }

  90. buffer.flip();

  91. outC.write(buffer);

  92. buffer.clear();

  93. }

  94. fis.close();

  95. byte[] bytes = baos.toByteArray();

  96. clazz = defineClass(className, bytes, 0, bytes.length);

  97. } catch (FileNotFoundException e) {

  98. e.printStackTrace();

  99. } catch (IOException e) {

  100. e.printStackTrace();

  101. }

  102. }

  103. return clazz;

  104. }

  105. privatebyte[] loadClassBytes(String className) throws

  106. ClassNotFoundException {

  107. try {

  108. String classFile = getClassFile(className);

  109. FileInputStream fis = new FileInputStream(classFile);

  110. FileChannel fileC = fis.getChannel();

  111. ByteArrayOutputStream baos = new

  112. ByteArrayOutputStream();

  113. WritableByteChannel outC = Channels.newChannel(baos);

  114. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  115. while (true) {

  116. int i = fileC.read(buffer);

  117. if (i == 0 || i == -1) {

  118. break;

  119. }

  120. buffer.flip();

  121. outC.write(buffer);

  122. buffer.clear();

  123. }

  124. fis.close();

  125. return baos.toByteArray();

  126. } catch (IOException fnfe) {

  127. thrownew ClassNotFoundException(className);

  128. }

  129. }

  130. private String getClassFile(String name) {

  131. StringBuffer sb = new StringBuffer(fileName);

  132. name = name.replace('.', File.separatorChar) + ".class";

  133. sb.append(File.separator + name);

  134. return sb.toString();

  135. }

  136. }

该类中经过调用defineClass(String name, byte[] b, int off, int len)方法来定义一个类:

Java代码

  
  
  
  
  1. protectedfinal Class<?> defineClass(String name, byte[] b, int off, int len)

  2. throws ClassFormatError

  3. {

  4. return defineClass(name, b, off, len, null);

  5. }

  6. protectedfinal Class<?> defineClass(String name, byte[] b, int off, int len)

  7. throws ClassFormatError

  8. {

  9. return defineClass(name, b, off, len, null);

  10. }

注:MyClassLoader加载类时有一个局限,必需指定.class文件,而不能指定.jar文件。该类中的大部分代码是从网上搜索到的,是出自一牛人之笔,只是不知道原帖在哪,但愿不会被隐藏。

MainClassLoader类:

Java代码

  
  
  
  
  1. publicclass MainClassLoader {

  2. publicstaticvoid main(String[] args) {

  3. try {

  4. MyClassLoader tc = new MyClassLoader("F:\\OpenLib\\");

  5. Class c = tc.findClass("Test");

  6. c.newInstance();

  7. } catch (ClassNotFoundException e) {

  8. e.printStackTrace();

  9. } catch (IllegalAccessException e) {

  10. e.printStackTrace();

  11. } catch (InstantiationException e) {

  12. e.printStackTrace();

  13. }

  14. }

  15. }

  16. publicclass MainClassLoader {

  17. publicstaticvoid main(String[] args) {

  18. try {

  19. MyClassLoader tc = new MyClassLoader("F:\\OpenLib\\");

  20. Class c = tc.findClass("Test");

  21. c.newInstance();

  22. } catch (ClassNotFoundException e) {

  23. e.printStackTrace();

  24. } catch (IllegalAccessException e) {

  25. e.printStackTrace();

  26. } catch (InstantiationException e) {

  27. e.printStackTrace();

  28. }

  29. }

  30. }

最后是一个简单的Test测试类:

Java代码

  
  
  
  
  1. publicclass Test

  2. {

  3. public Test() {

  4. System.out.println("Test");

  5. }

  6. publicstaticvoid main(String[] args) {

  7. System.out.println("Hello World");

  8. }

  9. }  

相关文章
相关标签/搜索