象牙塔的案例和资讯已经很多年没有更新了,实在没有精力更新,需要最新案例请联系客服!
--- 我们没有销售只有技术,不善于也不需要宣传,服务好已有客户足以!---
浅析Java虚拟机结构与机制 (上)
编辑:象牙塔 技术部  |  点击率:2177次

   本文主要介绍JVM的组成部分以及它们内部工作的机制和原理。在研究JVM的过程中会发现,其实JVM本身就是一个计算机体系结构,很多原理和我们平时的硬件、微机原理、 操作系统都有十分相似的地方,所以学习JVM本身也是加深自我对计算机结构认识的一个很好的途径。

  一、JVM结构
3684507445142637698.jpg
  JVM主要由类加载器子系统、运行时数据区(内存空间)、执行引擎以及与本地方法接口等组成。其中运行时数据区又由方法区、堆、Java栈、PC寄存器、本地方法栈组成。JVM主要由类加载器子系统、运行时数据区(内存空间)、执行引擎以及与本地方法接口等组成。其中运行时数据区又由方法区、堆、Java栈、PC寄存器、本地方法栈组成。
  众所周知,Java语言具有跨平台的特性,这也是由JVM来实现的。更准确地说,是Sun利用JVM在不同平台上的实现帮我们把平台相关性的问题给 解决了,这就好比是HTML语言可以在不同厂商的浏览器上呈现元素(虽然某些浏览器在对W3C标准的支持上还有一些问题)。同时,Java语言支持通过 JNI(Java Native Interface)来实现本地方法的调用,但是需要注意到,如果你在Java程序用调用了本地方法,那么你的程序就很可能不再具有跨平台性,即本地方法 会破坏平台无关性。
  二、类加载器子系统(Class Loader)
  类加载器子系统负责加载编译好的.class字节码文件,并装入内存,使JVM可以实例化或以其它方式使用加载后的类。 JVM的类加载子系统支持在运行时的动态加载,动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类,动态加载的另一好处是可以通过命名空 间的分隔来实现类的隔离,增强了整个系统的安全性。
  1、ClassLoader的分类:
  a.启动类加载器(BootStrap Class Loader):负责加载rt.jar文件中所有的Java类,即Java的核心类都是由该ClassLoader加载。在Sun JDK中,这个类加载器是由C++实现的,并且在Java语言中无法获得它的引用。
  b.扩展类加载器(Extension Class Loader):负责加载一些扩展功能的jar包。
  c.系统类加载器(System Class Loader):负责加载启动参数中指定的Classpath中的jar包及目录,通常我们自己写的Java类也是由该ClassLoader加载。在Sun JDK中,系统类加载器的名字叫AppClassLoader。
  d.用户自定义类加载器(User Defined Class Loader):由用户自定义类的加载规则,可以手动控制加载过程中的步骤。
  2、ClassLoader的工作原理
  类加载分为装载、链接、初始化三步。
  a.装载
  通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM。当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。
  在内存中,ClassLoader实例和类的实例都位于堆中,它们的类信息都位于方法区。
  装载过程采用了一种被称为“双亲委派模型(Parent Delegation Model)” 的方式,当一个ClassLoader要加载类时,它会先请求它的双亲ClassLoader(其实这里只有两个ClassLoader,所以称为父 ClassLoader可能更容易理解)加载类,而它的双亲ClassLoader会继续把加载请求提交再上一级的ClassLoader,直到启动类加 载器。只有其双亲ClassLoader无法加载指定的类时,它才会自己加载类。
  双亲委派模型是JVM的第一道安全防线,它保证了类的安全加载,这里同时依赖了类加载器隔离的原理:不同类加载器加载的类之间是无法直接交互的,即 使是同一个类,被不同的ClassLoader加载,它们也无法感知到彼此的存在。这样即使有恶意的类冒充自己在核心包(例如java.lang)下,由 于它无法被启动类加载器加载,也造成不了危害。
  由此也可见,如果用户自定义了类加载器,那就必须自己保障类加载过程中的安全。
  b.链接
  链接的任务是把二进制的类型信息合并到JVM运行时状态中去。
  链接分为以下三步:
  a.验证:校验.class文件的正确性,确保该文件是符合规范定义的,并且适合当前JVM使用。
  b.准备:为类分配内存,同时初始化类中的静态变量赋值为默认值。
  c.解析(可选):主要是把类的常量池中的符号引用解析为直接引用,这一步可以在用到相应的引用时再解析。
  c.初始化
  初始化类中的静态变量,并执行类中的static代码、构造函数。
  JVM规范严格定义了何时需要对类进行初始化:
  a、通过new关键字、反射、clone、反序列化机制实例化对象时。
  b、调用类的静态方法时。
  c、使用类的静态字段或对其赋值时。
  d、通过反射调用类的方法时。
  e、初始化该类的子类时(初始化子类前其父类必须已经被初始化)。
  f、JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。
  三、Java栈(Java Stack)
  Java栈由栈帧组成,一个帧对应一个方法调用。调用方法时压入栈帧,方法返回时弹出栈帧并抛弃。Java栈的主要任务是 存储方法参数、局部变量、中间运算结果,并且提供部分其它模块工作需要的数据。前面已经提到Java栈是线程私有的,这就保证了线程安全性,使得程序员无 需考虑栈同步访问的问题,只有线程本身可以访问它自己的局部变量区。
  它分为三部分:局部变量区、操作数栈、帧数据区。
  1、局部变量区
  局部变量区是以字长为单位的数组,在这里,byte、short、char类型会被转换成int类型存储,除了long和 double类型占两个字长以外,其余类型都只占用一个字长。特别地,boolean类型在编译时会被转换成int或byte类型,boolean数组会 被当做byte类型数组来处理。局部变量区也会包含对象的引用,包括类引用、接口引用以及数组引用。
  局部变量区包含了方法参数和局部变量,此外,实例方法隐含第一个局部变量this,它指向调用该方法的对象引用。对于对象,局部变量区中永远只有指向堆的引用。
  2、操作数栈
  操作数栈也是以字长为单位的数组,但是正如其名,它只能进行入栈出栈的基本操作。在进行计算时,操作数被弹出栈,计算完毕后再入栈。
  3、帧数据区
  帧数据区的任务主要有:
  a.记录指向类的常量池的指针,以便于解析。
  b.帮助方法的正常返回,包括恢复调用该方法的栈帧,设置PC寄存器指向调用方法对应的下一条指令,把返回值压入调用栈帧的操作数栈中。
  c.记录异常表,发生异常时将控制权交由对应异常的catch子句,如果没有找到对应的catch子句,会恢复调用方法的栈帧并重新抛出异常。
  局部变量区和操作数栈的大小依照具体方法在编译时就已经确定。调用方法时会从方法区中找到对应类的类型信息,从中得到具体方法的局部变量区和操作数栈的大小,依此分配栈帧内存,压入Java栈。
  •   上一篇: 浅析Java虚拟机结构与机制 (下)
  •   下一篇: 虚拟化和虚拟机基本概念