相信大多数java程序员都知道classLoader的存在,但大家想过java为什么要设计java 类加载体系吗? java类加载体系的是如何工作的?
我带着这些问题,翻阅了相关资料,写下了本篇博客。
一, 为什么要设计类加载体系
1. 使用场景
我们先看看有哪些场景会使用到java 类加载 体系:
1, java applet
这个东西估计很多人都没用过,比较古老. 但我们看看它的原理介绍就明白它是干什么的了.
它的原理如下:
含有Applet的网页的HTML文件代码中部带有<applet> 和</applet>这样一对标记,当支持Java的网络浏览器遇到这对标记时,就小应用程序代码并在本地计算机上执行该Applet.
也就是说,把java下载到本地,让本地的jvm来执行这段代码。这里面就涉及到类动态加载的相关接口。
2, tomcat,jboss等web服务器
因为tomcat.jboss本身就是java程序编写的web服务器,那么他们在加载相应的war包程序时,也涉及到类动态加载的相关接口.
还有一些像osgi等也需要涉及到 类动态加载的接口.
这些场景都有下面2个特点:
1, 程序在运行期间才决定是否需要加载某类,而不是在程序启动时决定.
2, 程序在运行期间可能会替换或者移除某些类.
java的类加载体系满足上面的所有的需求,但还有一些更重要的原因促使java必须要有自己的类加载体系, 安全.这个原因我们后面了解了它的原理后会有更深刻的体会.
二, 类加载体系是如何工作的
首先我们需要了解,java类加载体系是什么.
看下面的代码:
public class ClassLoaderExample { public static void main(String[] args) { ClassLoader a = ClassLoaderExample.class.getClassLoader(); while(a != null){ System.out.println(a.toString()); a = a.getParent(); } } }上面的代码输出:
sun.misc.Launcher$AppClassLoader@47415dbf
sun.misc.Launcher$ExtClassLoader@1471cb25
就是说:
ClassLoaderExample 这个类是AppClassLoader这个类加载器加载的,AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器为null.
2.1 类加载方式
public class A { public static void main(String[] args) throws ClassNotFoundException { B b = new B(); } } class B { static { System.out.println("static B"); } }
[Loaded B from file:/root/]
hello world.
public class A { public static void main(String[] args) throws ClassNotFoundException { //B b = new B(); Class.forName("B"); System.out.println("hello world."); } } class B { static { System.out.println("static B"); } }
[Loaded B from file:/root/]
static B
hello world.
public class A { public static void main(String[] args) throws ClassNotFoundException { //B b = new B(); // Class.forName("B"); A.class.getClassLoader().loadClass("B"); System.out.println("hello world."); } } class B { static { System.out.println("static B"); } }编译运行,输出如下:
[Loaded B from file:/root/]
hello world.
public static void main(String[] args) throws SQLException, ClassNotFoundException { // B b = new B(); classForName(); System.out.println("hello world."); } public static void classForName() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "xxxx"; String pwd = "xxx"; String userNmae = "xxx"; DriverManager.getConnection(url,userNmae,pwd); }
com.mysql.jdbc.Driver
这段代码很熟悉吧. 刚学编程时,基本上所有的同学都会接触这段代码.这句Class.forName("com.mysql.jdbc.Driver"); 到底是干什么的?
public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can\'t register driver!"); } }
2.2 委派模式
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }重要的代码是这段:
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
2.3 类加载时相关的异常
public class A { public static void main(String[] args) throws ClassNotFoundException { Class.forName("C"); } } class B { static { System.out.println("static B"); } }编译运行上面的代码:
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:171)
at A.main(A.java:9)
public class A { public static void main(String[] args) throws ClassNotFoundException { B b = new B(); } } class B{ C c = new C(); static{ System.out.println("static B"); } } class C{}
Exception in thread "main" java.lang.NoClassDefFoundError: C
at B.<init>(A.java:14)
at A.main(A.java:9)
Caused by: java.lang.ClassNotFoundException: C
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 2 more
一, 总结
经过上面的讨论,总结如下:
1, 类的加载是双亲委派模式.
2,每个类加载器只加载一次同一个类.
3,类的加载是延迟的,按需加载的.
4,Class.forName 和 classloader.loadclass,new 都可以触发类的加载. new 是强依赖,Class.forName比较灵活,事先并不知道要加载的类是谁.