JVM是编译后的Java程序(.class文件)和硬件系统之间的接口。
JVM架构如下:
由上图可见,JVM的输入时.class文件。大致分为三部分:
- 类加载器 Classloader Subsystem
- 运行时数据区 Runtime Data Area
- 执行引擎 Execution Engine
除此之外,还有本地方法接口、本地方法库,也就是调用的不同的平台的库函数,体现了跨平台性。
Classloader把.class文件加载到Runtime Data Area中,然后由Execution Engine来执行Java字节码
Classloader
类加载器是动态加载的,即在运行时遇到这个类时才进行加载,而不是在编译时进行,这样更加合理。
双亲委派模型(Parent Delegation Model):
类加载器具有层次结构 ,可以看做父子关系。
- Bootstrap classloader:当运行JVM时就被创建,负责加载核心类库。是所有类加载器的父类,由c/c++实现。
- Extension classloader:加载除了基本Java API之外的一些扩展类
- System classloader:加载应用程序类,也就是用户自定义的类
- User-defined classloader:用户也可以自己开发类加载器
可以看到,类加载器自上到下具有父子关系,最上方是根。
双亲委派模型:
类加载过程遵循该模型规定。除了根累加器外,其余类加载器都应该有自己的父类加载器。每个类加载器都有自己的命名空间。工作过程如下:
- 当前类加载器首先在自己加载的类缓存中查询,如果已经加载则直接返回。
- 如果没有,则去上层父类加载器中加载,直到根加载器。
- 如果所有上层类加载器都没有加载,则由当前类加载器加载,并放入自己的缓存。
需要注意的是,查找只能是由下至上的,父类加载器不能查询下层子类加载器。
可以看见,这种机制提供了安全性和层级性。越基础的类的加载越在靠近根的地方被加载,用户自定义的类在加载时必须不与上层的基础类矛盾才能加载。层次结构也方便了类的划分组织。
从中可以体现Java的一个原则——类是最小逻辑组织单元。一切的底层工作都以类为单位展开。
Runtime Data Area
运行时数据区是操作系统分配的内存区域。划分如下:
可以看到,有些部分为线程私有,有的则是类中所有线程共享的。
- 程序计数寄存器(PC register):线程私有,启动时创建。保存当前执行的JVM指令的地址。可以表明当前程序的下一条指令到哪去取。
- JVM栈(JVM Stack):线程私有,启动时创建。对栈帧(Stack Frame)push/pop。JVM描述的是Java方法执行的过程:每个方法被执行的时候都会创建一个栈帧,来存储方法中的信息包括局部变量数组(存储包括方法参数的全部局部变量)、操作数栈(每个方法都在操作数栈和局部变量数组之间交换数据,操作数栈之内的操作数不仅可以来自局部变量数组,也可以来自常量池中的引用)、对运行时常量池(见下文)的引用等。每一个方法的调用直到执行完成就对应着一个栈帧在JVM栈中入栈到出栈的过程。
- 本地方法栈(Native method stack):线程私有,启动时创建。为被调用的非Java实现的本地方法提供栈,即通过JNI(Java Native Interface Java本地接口)调用的c/c++代码,这部分接口在源代码中以native关键字描述。根据语言,创建b的栈。不同
- 方法区(Method Area):线程共享。保存被JVM加载的类和接口的运行时常量池,成员变量,方法的信息,静态变量等。即类级别的信息。
- 运行时常量池(Runtime constant pool):线程共享。包含在方法区中。其中包含所有方法、变量的引用,即在内存中的实际地址(JVM栈中的某个栈帧的具体位置)。
- 堆(Heap):保存实例或者对象,是垃圾回收(GC)的主要目标。所有通过new关键字创建的变量都存储在堆中。
需要注意的是,Java内存模型中是没有寄存器的概念的,它以栈为主体进行内存管理。以索引来代替实际的内存地址。索引根据具体指令可能指向操作数栈或者常量池。
Execution Engine
通过类加载器加载,被分配到JVM的运行时数据区的字节码被执行引擎执行。它以指令为单位取Java字节码,类似于CPU。字节码由一个字节的的操作码和附加的操作数组成。
Java字节码还不是JVM能够直接执行的语言。字节码通过以下方式转化:
- 解释器:读一条指令,解释,执行。重复这个过程。不保存解释后的结果。
- 即时(Just-In-Time)编译器:把整段字节码先翻译成机器码,供执行引擎直接执行。执行起来很快因为翻译结果保存在了缓存中,但是编译所花的时间比逐条解释要慢。
JIT编译器采用检查策略:如果一个方法执行频率超过阈值,则被编译成本地机器码。对于那些执行次数很少的代码,由解释器直接解释执行。
不同的JIT采用不同策略
执行引擎中的垃圾收集器(Garbage Collector)单独作为一个模块介绍。
参考文献、博客: