Java Class Loader?

开发 后端
类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

1. ClassLoader

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class 类的一个实例。

2. ClassLoader Hierarchy

JVM在加载类时,使用的是双亲委托模式(delegation model),也就是说除了Bootstrap ClassLoader之外,每个ClassLoader都有一个Parent ClassLoader。ClassLoader是按需进行加载class文件。当ClassLoader试图加载一个类时,首先检查本地缓冲,查看类是否已被加载,如果类没有被加载,尝试委托给父ClassLoader进行加载,如果父ClassLoader加载失败,才会由该ClassLoader进行加载,从而避免了重复加载的问题。一下为类装载器层次图:

 

 

Bootstrap ClassLoader:负责加载java_home/lib目录下的核心类或- Xbootclasspath指定目录下的类。

Extension ClassLoader:负责加载java_home/lib/ext目录下的扩展类或 -Djava.ext.dirs 指定目录下的类。

System ClassLoader:负责加载-classpath/-Djava.class.path所指的目录下的类。

如果类App1在本地缓冲中没有class文件(没有被加载),那么它会自底向上依次查找是否已经加载了类,如果已经加载,则直接返回该类实例的引用。如果BootstrapClassLoader也未成功加载该类,那么会抛出异常,然后自顶向下依次尝试加载,如果到App1 ClassLoader还没有加载成功,那么会抛出ClassNotFoundException异常给调用者。

Java代码

  1. public static void main(String[] args) {     
  2.         ClassLoader cl = ClassLoader.getSystemClassLoader();     
  3.         while(cl != null){     
  4.             System.out.println(cl);     
  5.             System.out.println("parent class loader: " + cl.getParent());     
  6.             cl = cl.getParent();     
  7.         }     
  8.     }  

 

Java代码

  1. sun.misc.Launcher$AppClassLoader@19821f    
  2. parent class loader: sun.misc.Launcher$ExtClassLoader@addbf1    
  3. sun.misc.Launcher$ExtClassLoader@addbf1    
  4. parent class loader: null   

 

我们看到,当前系统类装载器为AppClassLoader,AppClassLoader的父类装载器是ExtClassLoader,ExtClassLoader的父装载器为null,表示为BootstrapClassLoader。BootstrapClassLoader由JVM采用本地代码实现,因此没有对应的Java类,所以ExtClassLoader的getParent()返回null。

ClassLoader的职责之一是保护系统名字空间。以下为ClassLoader类部分代码:

Java代码

  1. private ProtectionDomain preDefineClass(String name,     
  2.                         ProtectionDomain protectionDomain)     
  3.     {     
  4.     if (!checkName(name))     
  5.         throw new NoClassDefFoundError("IllegalName: " + name);     
  6.     
  7.     if ((name != null) && name.startsWith("java.")) {     
  8.         throw new SecurityException("Prohibited package name: " +     
  9.                     name.substring(0, name.lastIndexOf('.')));     
  10.     }     
  11.     if (protectionDomain == null) {     
  12.         protectionDomain = getDefaultDomain();     
  13.     }     
  14.     
  15.     if (name != null)     
  16.         checkCerts(name, protectionDomain.getCodeSource());     
  17.     
  18.     return protectionDomain;     
  19.     } 

 

那么,当我们定义如下类Foo,虽然能够通过编译,但是会报java.lang.SecurityException: Prohibited package name: java.lang异常,因为我们试图将Foo类写入到java.lang包下。

Java代码

  1. package java.lang;     
  2.     
  3. public class Foo {     
  4.          
  5.     public static void main(String args[]) throws Exception {     
  6.         Foo f = new Foo();     
  7.         System.out.println(f.toString());     
  8.     }     
  9. }   

 

3. 定制ClassLoader

Java自带的ClassLoader类的定义为:

Java代码

  1. public abstract class ClassLoader{      

 

启动类加载器是JVM通过调用ClassLoader.loadClass()方法。

Java代码

  1. public Class loadClass(String name) throws ClassNotFoundException {     
  2.     return loadClass(name, false);     
  3.     }     
  4.     
  5. protected synchronized Class loadClass(String name, boolean resolve)     
  6.     throws ClassNotFoundException     
  7.     {     
  8.     // First, check if the class has already been loaded     
  9.     Class c = findLoadedClass(name);     
  10.     if (c == null) {     
  11.         try {     
  12.         if (parent != null) {     
  13.             c = parent.loadClass(name, false);     
  14.         } else {     
  15.             c = findBootstrapClass0(name);     
  16.         }     
  17.         } catch (ClassNotFoundException e) {     
  18.             // If still not found, then invoke findClass in order     
  19.             // to find the class.     
  20.             c = findClass(name);     
  21.         }     
  22.     }     
  23.     if (resolve) {     
  24.         resolveClass(c);     
  25.     }     
  26.     return c;     
  27.     }     
  28.     
  29. protected Class findClass(String name) throws ClassNotFoundException {     
  30.     throw new ClassNotFoundException(name);     
  31.     }    

 

loadClass(String name, boolean resolve)方法中的resolve如果为true,表示分析这个Class对象,包括检查Class Loader是否已经初始化等。loadClass(String name) 在加载类之后不会对该类进行初始化,直到***次使用该类时,才会对该类进行初始化。

那么,我们在定制ClassLoader的时候,通常只需要覆写findClass(String name)方法。在findClass(String name)方法内,我们可以通过文件、网络(URL)等形式获取字节码。以下为获取字节码的方法:

Java代码

  1. public InputStream getResourceAsStream(String name);     
  2. public URL getResource(String name);     
  3. public InputStream getResourceAsStream(String name);     
  4. public Enumeration getResources(String name) throws IOException;   

 

在取得字节码后,需要调用defineClass()方法将字节数组转换成Class对象,该方法签名如下:

Java代码

  1. protected final Class defineClass(String name, byte[] b, int off, int len,     
  2.                      ProtectionDomain protectionDomain)     
  3.     throws ClassFormatError  

 

对于相同的类,JVM最多会载入一次。如果同一个class文件被不同的ClassLoader载入(定义),那么载入后的两个类是完全不同的。

Java代码

  1. public class Foo{     
  2.     //     
  3.     private static final AtomicInteger COUNTER = new AtomicInteger(0);     
  4.     
  5.     public Foo() {     
  6.         System.out.println("counter: " + COUNTER.incrementAndGet());     
  7.     }     
  8.          
  9.     public static void main(String args[]) throws Exception {     
  10.         URL urls[] = new URL[]{new URL("file:/c:/")};     
  11.         URLClassLoader ucl1 = new URLClassLoader(urls);     
  12.         URLClassLoader ucl2 = new URLClassLoader(urls);     
  13.         Class c1 = ucl1.loadClass("Foo");     
  14.         Class c2 = ucl2.loadClass("Foo");     
  15.         System.out.println(c1 == c2);     
  16.         c1.newInstance();     
  17.         c2.newInstance();     
  18.     }     
  19. }   

 

以上程序需要保证Foo.class文件不在classpath路径下。从而使AppClassLoader无法加载Foo.class。

输出结果:

Java代码

  1. false    
  2. counter: 1    
  3. counter: 1 

 

4. Web应用的ClassLoader

绝大多数的EJB容器,Servlet容器等都会提供定制的ClassLoader,来实现特定的功能。但是通常情况下,所有的servlet和filter使用一个ClassLoader。每个jsp都使用一个独立的ClassLoader。

5. 隐式(implicit)和显示(explicit)的加载

隐式加载:我们使用new关键字实例化一个类,就是隐身的加载了类。

显示加载分为两种:

java.lang.Class的forName()方法;

java.lang.ClassLoader的loadClass()方法。

Class.forName()方法有两个重载的版本:

Java代码

  1. public static Class forName(String className)      
  2.                 throws ClassNotFoundException {     
  3.         return forName0(className, true, ClassLoader.getCallerClassLoader());     
  4.     }     
  5.     
  6. public static Class forName(String name, boolean initialize,     
  7.                    ClassLoader loader)     
  8.         throws ClassNotFoundException   

 

可以看出,forName(String className)默认以true和ClassLoader.getCallerClassLoader()调用了三参数的重载方法。ClassLoader.getCallerClassLoader()表示以caller class loader加载类,并会初始化类(即静态变量会被初始化,静态初始化块中的代码也会被执行)。如果以false和ClassLoader.getCallerClassLoader()调用三参数的重载方法,表示加载后的类不会被初始化。

ClassLoader.loadClass()方法在类加载后,也同样不会初始化类。

6. 两个异常(exception)

NoClassDefFoundError: 当java源文件已编译成.class文件,但是ClassLoader在运行期间搜寻路径load某个类时,没有找到.class文件则抛出这个异常。

ClassNotFoundException: 试图通过一个String变量来创建一个Class类时不成功则抛出这个异常

【编辑推荐】

  1. 解答WebLogic与JVM六大疑问
  2. 解决JVM***内存设置问题
  3. 调用weblogic设置jvmheap大小
  4. 详解Tomcat配置JVM参数步骤
  5. 深入学习JVM内存设置原理和调优
责任编辑:金贺 来源: ITEYE博客
相关推荐

2017-01-11 19:05:45

AndroidAndroid Loa详解

2012-02-09 10:18:55

Java

2021-05-31 05:36:43

WebpackJavaScript 前端

2013-07-02 14:33:35

JavaClass

2012-11-06 10:02:04

JavaJadEclipse

2021-04-30 08:28:15

WebpackLoaderPlugin

2022-04-29 14:38:49

class文件结构分析

2020-11-17 09:55:48

Java

2022-05-29 17:37:39

LinuxUbuntuPHP

2014-11-03 15:44:07

2010-03-17 17:20:15

Java class线

2012-03-05 11:09:01

JavaClass

2017-11-28 15:36:25

换扶技术Android资源

2020-11-18 08:17:14

Java源码Class

2017-02-07 09:54:43

JVMJavaClass

2021-08-12 09:48:21

Webpack Loa工具Webpack

2021-11-30 11:17:23

自定义配置插件

2010-01-06 10:08:16

Boot Loader

2018-10-26 15:54:16

JavaClass常量池

2021-02-01 14:10:16

JavaClass.forNaClassLoader
点赞
收藏

51CTO技术栈公众号