JVM的作用就像有两只不同的铅笔,但需要把同一个笔帽套在两支不同的笔上,只有为这两支笔分别提供一个转换器,这个转换器向上的接口相同,用于适应同一个笔帽;向下的接口不同,用于适应两支不同的笔。在这个类比中,可以近似地理解两支不同的笔就是不同的操作系统,而同一个笔帽就是Java字节码程序,转换器角色则对应JVM。类似地,也可以认为JVM分为向上和向下两个部分,所有平台的JVM向上提供给Java字节码程序的接口完全相同,但向下适应的不同平台的接口则互不相同。
上面我们是初步介绍了JVM的作用,那么要深入去了解JVM我们就需要了解JVM的体系结构,请看图二:
图二是JVM的体系架构图,接下让我们一起来聊一聊每一个部分都是什么意思。
1.类装载器子系统(ClassLoader)
负责加载class文件,class文件在文件开头有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
Java编译生成的*.class文件就是通过ClassLoader进行加载的,那么这里就会有几个问题:
ClassLoader如何知道*.class文件就是需要加载的文件?
如果我手动将一个普通文件的扩展名称改为class后缀,ClassLoader会加载这个文件吗?
实际上,class文件在文件的开头是有特定的文件标识的,随便编写一个Java程序,编译生成一个class文件,打开后你都能看到如下内容:
cafe babe就是class文件的一个标识,ClassLoader负责加载有cafe babe的class文件,它将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时的数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定,请看图三:
Car.class文件通过ClassLoader进行加载到内存中,Car Class在内存中就相当一个模板,我们可以通过这个模板可以实例化成不同的实例car1、car2、car3。
不知大家会不会有一个疑问,ClassLoader加载Car.class在Java中是用什么类型的加载器加载的呢?在解答这个问题前我们先写个简单的代码看看:
//new一个Car对象
Car car = new Car();
//得到ClassLoader
ClassLoader classLoader = car.getClass().getClassLoader();
//打印结果
System.out.println(classLoader);
结果为:
我们再来看看另外一组代码:
//new两个不同的对象 Car car = new Car(); String string = new String(); //得到ClassLoader ClassLoader classLoader1 = car.getClass().getClassLoader(); ClassLoader classLoader2 = string.getClass().getClassLoader(); //打印结果 System.out.println(classLoader1); System.out.println(classLoader2);
结果为:
从上面我们可以知道,ClassLoader的打印结果一个是“sun.misc.Launcher$AppClassLoader@18b4aac2”,一个则是“null”,这是怎么回事呢,细心的朋友就可以发现这两个不同的对象中,其中car对象是我们自己写的一个类,string对象是系统自带的一个类。简单来说就是ClassLoader会根据不同的类选择不同的类加载器去进行加载。这里就牵扯到了ClassLoader的分类
ClassLoader的类别:
启动类加载器(BootStrap)
扩展类加载器(Extension)
应用程序类加载器(AppClassLoader)
用户自定义加载器
一般我们自己所写的类用的类加载器都是AppClassLoader,就是上图所示的“sun.misc.Launcher$AppClassLoader@18b4aac2”,而为什么string这个对象是”null“呢?实际上,这个“null”指的就是使用BootStrap这个加载器。
那可能有人有疑问,自己定义的类用AppClassLoader,能理解,因为car这个对象输出的类加载器名字中有AppClassLoader这个字样,但是为什么string这个对象是”null“,从哪里可用体现是用BootStrap这个加载器呢?是这样的,BootStrap累加载器相当于扩展类加载器、应用程序类加载器的祖宗,若是用了BootStrap,由于BootStrap上一级已经没有了,所以就用“null”来表示
其实我们可以找一下String这个类在JDK的位置:
$JAVA_HOME/jre/lib/rt.jar/java/lang
所有在这个路径$JAVA_HOME/jre/lib/rt.jar这个jar包下的类都是用BootStrap来加载的。
下面请看图4:
这张图就可以很清晰得看到:
1.所有在$Java_Home/jre/lib/rt.jar是通过BootStrap加载的
2.所有在$Java_Home/jre/lib/ext/*.jar是通过Extension加载的
3.所有在$CLASSPATH是通过SYSTEM加载的(应用程序类加载器也叫系统类加载器,加载当前应用的classpath的所有类)
接下来我们再来看一个例子:
如果创建一个java.lang包,然后创建String类,打印一句话执行会怎么样呢?
package java.lang; public class String { public static void main(String[] args) { System.out.println("Hello World"); } }
String { public static void main(String[] args) {
System.out.println(“Hello World”);
}
}