JVM加载class文件的原理机制

1 JVM简介

JVM是我们Javaer的最基本功底了,刚开始学Java的时候,一般都是从“Hello World”开始的,然后会写个复杂点class,然后再找一些开源框架,比如Spring,Hibernate等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError,明天是网站系统报了个OutOfMemoryError,这种错误又很难重现,只有分析Javacore和dump文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了。我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢?—— JVM。

JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。

JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO,NO, JVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

2 JVM的组成部分

我们先把JVM这个虚拟机画出来,如下图所示:

JVM加载class文件的原理机制

从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互。我们再来看下JVM有哪些组成部分,如下图所示:

 该图参考了网上广为流传的JVM构成图,大家看这个图,整个JVM分为四部分:

q Class Loader 类加载器

类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:

    ClassFile {

      u4magic;

      u2minor_version;

      u2 major_version;

      u2constant_pool_count;

      cp_infoconstant_pool[constant_pool_count-1];

      u2access_flags;

      u2this_class;

      u2super_class;

      u2interfaces_count;

      u2interfaces[interfaces_count];

      u2fields_count;

      field_infofields[fields_count];

      u2methods_count;

      method_infomethods[methods_count];

      u2attributes_count;

      attribute_infoattributes[attributes_count];

    }

需要详细了解的话,可以仔细阅读《JVM Specification》的第四章“The class File Format”,这里不再详细说明。

友情提示:ClassLoader只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由ExecutionEngine负责的。

q Execution Engine 执行引擎

执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。

q Native Interface本地接口

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。

q Runtime data area运行数据区

运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。

 

整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!

 

JVM加载class文件的原理机制

1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中

2.java中的类大致分为三种:

1.系统类

2.扩展类

3.由程序员自定义的类

3.类装载方式,有两种

1.隐式装载,程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,

2.显式装载,通过class.forname()等方法,显式加载需要的类

隐式加载与显式加载的区别:

两者本质是一样?,

4.类加载的动态性体现

一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再

运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现

5.java类装载器

Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:

BootstrapLoader-负责加载系统类

|

--ExtClassLoader-负责加载扩展类

|

--AppClassLoader-负责加载应用类

为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型

6.类加载器之间是如何协调工作的

前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。

在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性

/**
 


 * @author Jamson Huang
 


 *
 


 */
  



public
 
class
 TestClass {  



  


    /**
 


     * @param args
 


     */
  



    public
 
static
 
void
 main(String[] args)  
throws
 Exception{  



        //调用class加载器
  



        ClassLoader cl = TestClass.class
.getClassLoader();  



        System.out.println(cl);  


        //调用上一层Class加载器
  



        ClassLoader clParent = cl.getParent();  


        System.out.println(clParent);  


        //调用根部Class加载器
  



        ClassLoader clRoot = clParent.getParent();  


        System.out.println(clRoot);  


          


    }  


  


}  
/**
 * @author Jamson Huang
 *
 */
public class TestClass {

	/**
	 * @param args
	 */
	public static void main(String[] args)  throws Exception{
		//调用class加载器
		ClassLoader cl = TestClass.class.getClassLoader();
		System.out.println(cl);
		//调用上一层Class加载器
		ClassLoader clParent = cl.getParent();
		System.out.println(clParent);
		//调用根部Class加载器
		ClassLoader clRoot = clParent.getParent();
		System.out.println(clRoot);
		
	}

}
/**
 


 * @author Jamson Huang
 


 *
 


 */
  



public
 
class
 Test1 {  



  


    /**
 


     * @param args
 


     */
  



    public
 
static
 
void
 main(String[] args)
throws
 Exception {  



        System.out.println(Test1.class
.getClassLoader());  



          


        Test2 test2 = new
 Test2();  



          


        test2.print();  


    }  


  


}  


/**
 


 * @author Jamson Huang
 


 *
 


 */
  



public
 
class
 Test2 {  



    public
 
void
 print(){  



        System.out.println(Test2.class
);  



        System.out.println(this
.getClass());  



        System.out.println(Test2.class
.getClassLoader());  



    }  


}  
/**
 * @author Jamson Huang
 *
 */
public class Test1 {

	/**
	 * @param args
	 */
	public static void main(String[] args)throws Exception {
		System.out.println(Test1.class.getClassLoader());
		
		Test2 test2 = new Test2();
		
		test2.print();
	}

}
/**
 * @author Jamson Huang
 *
 */
public class Test2 {
	public void print(){
		System.out.println(Test2.class);
		System.out.println(this.getClass());
		System.out.println(Test2.class.getClassLoader());
	}
}
try
{   



URL url = new
 URL(
"file:/d:/test/lib/"
);   



URLClassLoader urlCL = new
 URLClassLoader(
new
 URL[]{url});   



Class c = urlCL.loadClass("TestClassA"
);   



TestClassA object = (TestClassA)c.newInstance();   


object.method();   


}catch
(Exception e){   



e.printStackTrace();   


}   
try{ 
URL url = new URL("file:/d:/test/lib/"); 
URLClassLoader urlCL = new URLClassLoader(new URL[]{url}); 
Class c = urlCL.loadClass("TestClassA"); 
TestClassA object = (TestClassA)c.newInstance(); 
object.method(); 
}catch(Exception e){ 
e.printStackTrace(); 
}

我们通过自定义的类加载器实现了TestClassA类的加载并调用method()方法。分析一下这个程序:首先定义URL指定类加载器从何处加载类,URL可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统(包含JAR文件)。上述范例当中我们从file:/d:/test/lib/处寻找类;然后定义URLClassLoader来加载所需的类,最后即可使用该实例了。

9.类加载器的阶层体系

讨论了这么多以后,接下来我们仔细研究一下Java的类加载器的工作原理:

当执行java***.class的时候,java.exe会帮助我们找到JRE,接着找到位于JRE内部的jvm.dll,这才是真正的Java虚拟机器,最后加载动态库,激活Java虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器――BootstrapLoader,BootstrapLoader是由C++所撰写而成,这个BootstrapLoader所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载Launcher.java之中的ExtClassLoader,并设定其Parent为null,代表其父加载器为BootstrapLoader。然后BootstrapLoader再要求加载Launcher.java之中的AppClassLoader,并设定其Parent为之前产生的ExtClassLoader实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是,Launcher$ExtClassLoader.class与Launcher$AppClassLoader.class都是由BootstrapLoader所加载,所以Parent和由哪个类加载器加载没有关系。

下面的图形可以表示三者之间的关系:

BootstrapLoader<---(Extends)----AppClassLoader<---(Extends)----ExtClassLoader

这三个加载器就构成我们的Java类加载体系。他们分别从以下的路径寻找程序所需要的类:

BootstrapLoader:sun.boot.class.path

ExtClassLoader:java.ext.dirs

AppClassLoader:java.class.path

这三个系统参量可以通过System.getProperty()函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。

相关推荐