`
samuschen
  • 浏览: 407693 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

JVM加载class文件的原理机制

 
阅读更多

1 JVM 简介

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

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

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

2 JVM 的组成部分

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


 

 

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



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

q  Class Loader 类加载器

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

    ClassFile {

      u4 magic;

      u2 minor_version;

       u2 major_version;

      u2 constant_pool_count;

      cp_info constant_pool[constant_pool_count-1];

      u2 access_flags;

      u2 this_class;

      u2 super_class;

      u2 interfaces_count;

      u2 interfaces[interfaces_count];

      u2 fields_count;

      field_info fields[fields_count];

      u2 methods_count;

      method_info methods[methods_count];

      u2 attributes_count;

      attribute_info attributes[attributes_count];

    }

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

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

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的类装载器并不是一个,而是三个,层次结构如下:
      Bootstrap Loader  - 负责加载系统类
            |
          - - ExtClassLoader  - 负责加载扩展类
                          |
                      - - AppClassLoader  - 负责加载应用类
        为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型

6. 类加载器之间是如何协调工作的
      前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。
在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性

Java代码  收藏代码
  1. /**  
  2.  * @author Jamson Huang  
  3.  *  
  4.  */   
  5. public   class  TestClass {  
  6.   
  7.     /**  
  8.      * @param args  
  9.      */   
  10.     public   static   void  main(String[] args)   throws  Exception{  
  11.         //调用class加载器   
  12.         ClassLoader cl = TestClass.class .getClassLoader();  
  13.         System.out.println(cl);  
  14.         //调用上一层Class加载器   
  15.         ClassLoader clParent = cl.getParent();  
  16.         System.out.println(clParent);  
  17.         //调用根部Class加载器   
  18.         ClassLoader clRoot = clParent.getParent();  
  19.         System.out.println(clRoot);  
  20.           
  21.     }  
  22.   
  23. }  
/**
 * @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);
		
	}

}

 

Result代码  收藏代码
  1. Run, Console中出现的log信息如下:  
  2. sun.misc.Launcher$AppClassLoader@7259da  
  3. sun.misc.Launcher$ExtClassLoader@16930e2  
  4. null  
Run, Console中出现的log信息如下:
sun.misc.Launcher$AppClassLoader@7259da
sun.misc.Launcher$ExtClassLoader@16930e2
null


可以看出TestClass是由AppClassLoader加载器加载的
AppClassLoader的Parent 加载器是 ExtClassLoader
但是ExtClassLoader的Parent为 null 是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null
【注:以下内容大部分引用java深度历险】
弄明白了上面的示例,接下来直接进入类装载的委托模型实例,写两个文件,如下:

Java代码  收藏代码
  1. /**  
  2.  * @author Jamson Huang  
  3.  *  
  4.  */   
  5. public   class  Test1 {  
  6.   
  7.     /**  
  8.      * @param args  
  9.      */   
  10.     public   static   void  main(String[] args) throws  Exception {  
  11.         System.out.println(Test1.class .getClassLoader());  
  12.           
  13.         Test2 test2 = new  Test2();  
  14.           
  15.         test2.print();  
  16.     }  
  17.   
  18. }  
  19. /**  
  20.  * @author Jamson Huang  
  21.  *  
  22.  */   
  23. public   class  Test2 {  
  24.     public   void  print(){  
  25.         System.out.println(Test2.class );  
  26.         System.out.println(this .getClass());  
  27.         System.out.println(Test2.class .getClassLoader());  
  28.     }  
  29. }  
/**
 * @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());
	}
}
Result代码  收藏代码
  1. Run,Console出现log如下:  
  2. sun.misc.Launcher$AppClassLoader@7259da  
  3. class com.java.test.Test2  
  4. class com.java.test.Test2  
  5. sun.misc.Launcher$AppClassLoader@7259da  
Run,Console出现log如下:
sun.misc.Launcher$AppClassLoader@7259da
class com.java.test.Test2
class com.java.test.Test2
sun.misc.Launcher$AppClassLoader@7259da


7. 预先加载与依需求加载

Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。

当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE 的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。

图( 2 )我们可以看到多个基础类被加载, java.lang.Object,java.io.Serializable 等等。
相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。
8. 自定义类加载机制

之前我们都是调用系统的类加载器来实现加载的,其实我们是可以自己定义类加载器的。利用 Java 提供的 java.net.URLClassLoader 类就可以实现。下面我们看一段范例:

Java代码  收藏代码
  1. try {   
  2. URL url = new  URL( "file:/d:/test/lib/" );   
  3. URLClassLoader urlCL = new  URLClassLoader( new  URL[]{url});   
  4. Class c = urlCL.loadClass("TestClassA" );   
  5. TestClassA object = (TestClassA)c.newInstance();   
  6. object.method();   
  7. }catch (Exception e){   
  8. e.printStackTrace();   
  9. }   
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 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。

下面的图形可以表示三者之间的关系:
BootstrapLoader <---(Extends)----AppClassLoader <---(Extends)----ExtClassLoader

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

BootstrapLoader : sun.boot.class.path
ExtClassLoader: java.ext.dirs
AppClassLoader: java.class.path

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

分享到:
评论

相关推荐

    jvm 加载class文件

    ### JVM加载Class文件详解 #### 一、Java与JVM中的Class文件加载机制概述 Java作为一种动态性极强的解释型编程语言,在程序运行时,Java虚拟机(JVM)负责将编译生成的`.class`文件加载到内存中进行执行。在Java...

    JVM加载class文件的原理机制.pdf

    JVM加载class文件的原理机制 JVM加载class文件的原理机制是Java虚拟机中一个非常重要的组件,负责将class文件加载到内存中,以便Java程序的执行。下面是JVM加载class文件的原理机制的详细介绍: 类加载的原理 在...

    codeegginterviewgroup#CodeEggDailyInterview#84.JVM加载class文件的原理机制

    JVM加载class文件的原理机制JVM加载class文件的原理机制 JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加

    JVM类加载机制详细讲解

    总结,JVM 类加载机制是Java平台的核心特性之一,它确保了程序的稳定运行和动态扩展能力。理解类加载器的工作原理和双亲委派模型对于优化程序性能、解决类冲突以及构建复杂的模块化系统至关重要。在实际开发中,掌握...

    Java虚拟机class文件原理

    Java虚拟机(JVM)的Class文件原理是Java程序运行的...总的来说,JVM的Class文件原理涉及到类加载器的层次结构、类的加载方式以及类加载的动态机制。理解这些原理对于优化Java程序性能和解决类加载相关问题至关重要。

    java热加载Class文件

    Java热加载Class文件是开发者在调试和优化代码时非常实用的一种技术,它允许开发者在不重启JVM的情况下更新和替换已加载的类,极大地提高了开发效率。本文将深入探讨Java热加载Class文件的相关知识点。 首先,理解...

    jvm解析编译过的class文件

    1. **加载**:JVM通过类加载器(ClassLoader)找到并读取.class文件。类加载器按照双亲委托模型工作,首先询问父加载器,如果父加载器无法加载,则由当前加载器尝试。加载过程中,JVM会创建一个对应的Class对象。 2...

    JVM实战-JVM类加载机制案例分析

    ### JVM实战-JVM类加载机制案例分析 #### 实验背景与目标 本次实验的主要目的是深入理解Java虚拟机(JVM)中的类加载机制。通过实践操作,掌握类的加载、连接与初始化过程,了解不同类型的类加载器及其工作原理,...

    JVM执行子系统原理

    通过对JVM执行子系统原理的深入分析,我们不仅了解了Class文件的具体结构和字节码指令的基本概念,还探讨了类加载机制的各个环节以及字节码执行引擎的核心组成部分。这些知识点对于深入理解JVM的工作原理、优化程序...

    class文件热加载,上传class文件实现热加载

    "class文件热加载,上传class文件实现热加载"这个主题主要涉及到Java应用的运行时动态更新机制。下面将详细介绍这个过程及其相关知识点。 1. **Java类加载器**: - Java虚拟机(JVM)通过类加载器来加载类。默认有...

    JVM组成及工作原理原理

    类加载机制JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述: 1. Bootstrap ClassLoader /启动类加载器 $JAVA_HOME 中 jre/lib/rt.jar 里所有的 class,由 C++实现,不是 ...

    面试必问之jvm与性能优化.docx.zip

    描述一下 JVM 加载 Class 文件的原理机制? 在面试java工程师的时候,这道题经常被问到,故需特别注意。 Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件...

    JVM之用Java解析class文件共10页.pdf.zi

    总结起来,"JVM之用Java解析class文件"这一主题涵盖了从class文件的结构到JVM加载机制的多个层面。通过学习这一领域,开发者可以更好地理解Java程序的生命周期,提高问题诊断能力,并能利用字节码技术实现高级编程...

    JVM的工作原理及垃圾回收机制介绍

    - **加载Class文件**:解析并加载Java程序编译后的字节码文件(.class文件)。 - **调用本地接口**:通过JNI (Java Native Interface) 调用本地代码库中的函数。 - **解释命令**:将字节码指令转换为对应平台的机器码...

    面试必问之jvm与性能优化.docx

    1. JVM加载Class文件的原理机制 JVM加载Class文件的过程分为三个主要阶段:装载、连接和初始化。 - **装载**:这是加载过程的第一步,JVM会找到并读取类文件。Java类加载器负责这个任务,它会将磁盘上的类文件加载...

    JVM类加载机制1

    Java虚拟机(JVM)的类加载机制是Java运行时环境的重要组成部分,它负责将类的字节码文件加载到内存中,进行一系列处理并使其成为可执行的Java类型。这个过程包括五个主要阶段:加载、验证、准备、解析和初始化。 1...

Global site tag (gtag.js) - Google Analytics