早期的java程序员可能只要懂基本语法,还有少数的项目经验就可以找到一份比较好的工作。Java和java社区的发展,更多的人了解它,深入它。现在java程序员了解一些语法我看还远远不够了,对于jvm的了解和深入是非常重要的。网民的增多,网站的刚性需求,很多网站面临高性能,高并发等等一系列的问题。没有深入jvm的java程序员是很难写出高质量高并发的代码(也许一棒子打死所有人了,但我想绝大部分是肯定的)。
Osgi也许你并不陌生,但是他底层的实现机制你可能没去了解过。如果你是个打破砂锅问到底的人,你肯定会想知道osgi是如何做到的。但是你没有了解jvm的类加载体系,你肯定很难理解osgi是如果做到类隔离等一系列的问题。不过想完整理解osgi还需要其他很多方面的知识,但是它基本的机制还是的了解jvm的类加载机制。
Java类库有些包只是定义了一个标准,具体的实现都是由具体的供应商来提供。Java与数据库连接就是一个很好的例子。Java.sql类库只是定义了java与数据库连接的标准,那么与mysql就需要msyql的驱动,oracle就需要oracle的驱动,而java.sql类库是由bootstrap classloader加载,驱动包中的类是由system classloader来加载,不同类加载器加载的类是无法相互认识,所以自然也无法正常提供功能了。jvm又是提供了什么机制让他们交互呢?如果你确实对这些问题毫无头绪的话,那么我觉得你真的要好好理解下jvm类加载体系。
这篇文章主要是介绍下jvm类加载的机制基础知识。关于其他相关涉及,有时间的话,我会单独写文章来介绍。
1、 java类加载器
1.1 Bootstrap classloader:sun jdk是用c++实现,所以在代码中你是无法获取此加载器的实例。此加载器主要负责加载$JAVA_HOME/jre/lib/rt.jar。java类中获取结果为null,这里可以用一个例子跑下证明:
public class Test {
public static void main(String[] arg) throws Exception{
ClassLoader classloader = Test.class.getClassLoader();
System.out.println(classloader);
System.out.println(classloader.getParent());
System.out.println(classloader.getParent().getParent());
}
}
输出结果:
sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
最后输出的null就是代表bootstrap classloader。
1.2 Extension classloader:主要加载扩展功能的jar,$JAVA_HOME/jre/lib/ext/*.jar。
1.3 System classloader:加载claspath中的jar包。
1.4自定义 classloader:主要加载你指定的目录中的包和类。
2、 双亲委派模型
系统运行时,我们请求加载String类,此时System Classloader自己不找classpath中的包,把请求转发给Extension Classloader,但它也不做查找,又转发给Bootstrap Classloader,但是它发现自己没有parent了。于是他在rt.jar包中找到String类并加载到jvm中提供使用。Jvm为什么要这么实现呢?其实和java安全体系有关。假设jvm不是这么实现,我们自定义一个String类,做一些破坏,那么运行jvm的机器肯定要受到损坏。具体例子:
public class String {
public static void main(String[] args) {
System.out.println("hello world");
}
}
我们运行自定义String类的时候报错了,说没有main方法,可我们定义的明明有的,嘿嘿,委派机制的缘故最后加载到的是由bootstrap classloader在rt.jar包中的String,那个类是没有main方法,因此报错了。
3、 类隔离
jvm中的类是:类加载器+包名+类名。比如:URLClassLoader1,URLClassLoader2分别加载com.test.Test的时候会加载两次,因为每个classloader中的类对于其他classloader来说是隔离的,不认识的。例子:
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
URLClassLoader ucl2 = new URLClassLoader(new URL[]{url});
Class c2 = ucl2.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
大家把Yang类存在g盘下。
public class Yang {
static {
System.out.println("Yang");
}
}
运行结果:
Yang
java.net.URLClassLoader@c17164
Yang
java.net.URLClassLoader@61de33
看到每次加载Yang类的时候都输出Yang,说明Yang类被加载了两次。
如果你不确信,可以修改下代码,让同一classloader加载Yang类两次
import java.net.URL;
import java.net.URLClassLoader;
public class CustomClassloaderTest {
public static void main(String[] args) throws Exception {
URL url = new URL("file:/g:/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class c = ucl.loadClass("Yang");
c.newInstance();
System.out.println(c.getClassLoader());
Class c2 = ucl.loadClass("Yang");
c2.newInstance();
System.out.println(c2.getClassLoader());
}
}
看看输出结果:
Yang
java.net.URLClassLoader@c17164
java.net.URLClassLoader@c17164
结果中只输出了一次Yang。因此可以证明我们最开始说的类隔离。
4、 线程上下文类加载器
我们理解了双亲委派模型,那么目前只有由下向上单向寻找类(system->extension->bootstrap)
。我们在最开始的时候说过,java.sql包中的类由bootstrap或extension classloader加载,而mysql驱动包是在classpath中由system来加载,但bootstrap中的类是无法找到system classloader中的类,此时靠线程上下文类加载器来解决。线程上下文类加载器主要就是能让jvm类加载模型具有了向下寻找的可能,bootstrap->extension->system,如果不做任何设置,线程上下文类加载器默认是system classloader。本来这里想写一个例子的,可是有点麻烦,所以下次单独写一篇关于这方面的知识。