对于任何一门语言, 要想达到精通的水平, 研究它的执行原理 (或者叫底层机制) 不失为一种良好的方式.
在本篇文章中, 将重点研究 java 源代码的执行原理, 即从程序员编写 JAVA 源代码, 到最终形成产品, 在整个过程中, 都经历了什么? 每一步又是怎么执行的? 执行原理又是什么?
一, 编写 java 源程序
java 源文件: 指存储 java 源码的文件.
先来看看如下代码:
- //MyTest 被 public 修饰, 故存储该 java 源码的文件名为 MyTest
- public class MyTest {
- public static void main(String[] args){
- System.out.println("Test Java execute process.");
- }
- }
- // 由于 MyTest 被 public 修饰了, 故 Class A 不能用 public 修饰
- class A{
- }
- // 由于 MyTest 被 public 修饰了, 故 Class B 不能用 public 修饰
- class B{
- }
1,java 源文件名就是该源文件中 public 类的名称
2, 一个 java 源文件可以包含多个类, 但只允许一个类为 public
二, 编译 java 源代码
当 java 源程序编码结束后, 就需要编译器编译.
安装好 jdk 后, 我们打开 jdk 目录, 有两个. exe 文件, 即 javac.exe(编译源代码, xxx.java 文件) 和 java.exe(执行字节码, xxx.class 文件).
如下图所示:
1, 切换到 MyTest.java 文件夹
2,javac.exe 编译 MyTest.java
编译后, 发现 e:\Blogs 目录多了以 class 为后缀的文件: A.class,B.class 和 MyTest.class
Tip: 当 javac.exe 编译 java 源代码时, java 源代码有几个类, 就会编译成一个对应的字节码文件(.class 文件)
其中, 字节码文件的文件名就是每个类的类名. 需要注意的是, 类即使不在源文件中定义, 但被源文件引用, 编译后, 也会编程相应的字节码文件.
如类 A 引用类 C, 但类 C 不定义在类 A 的源文件中, 编译后, 类 C 也被编译成对应的字节码文件 C.class
三, 执行 java 源文件
执行 java 源文件, 用 java.exe 执行即可
到现在, java 源程序基本执行结果, 并正确打印我们期望的结果, 那么, 如上的步骤, 我们可以总结如下:
如上总结, 已经抽象化了在 JVM 中的执行. 接下来, 我们将分析字节码文件 (.class 文件) 如何在虚拟机中一步一执行的.
四, JVM 如何执行字节码文件
1, 装载字节码文件
当 .java 源码被 javac.exe 编译器编译成 .class 字节码文件后, 接下来的工作就交给 JVM 处理.
JVM 首先通过类加载器(ClassLoader), 将 class 文件和相关 Java API 加载装入 JVM, 以供 JVM 后续处理.
在该阶段中, 涉及到如下一些基本概念和知识.
1)JDK,JRE 和 JVM 关系
JDK(Java Development Kit),Java 开发工具包, 主要用于开发, 在 JDK7 前, JDK 包括 JRE
JRE(Java Runtime Environment),Java 程序运行的核心环境, 包括 JVM 和一些核心库
JVM(Java Virtual Machine),VM 是一种用于计算设备的规范, 它是一个虚构出来的计算机, 是通过在实际的计算机上仿真模拟各种计算机功能来实现的, 是 JRE 核心模块.
2)JVM
JVM 是一种用于计算设备的规范, 它是一个虚构出来的计算机, 是通过在实际的计算机上仿真模拟各种计算机功能来实现的.
Java 虚拟机的主要任务是装载 class 文件, 并执行其中的字节码, 不同的 Java 虚拟机中, 执行引擎可能有不同的实现.
大致有如下几种引擎:
一次性解释字节码引擎
即时编译引擎
自适应优化器
关于虚拟机的实现方式, 采用软件方式, 硬件方式和软件硬件结合方式, 这个要根据具体厂商而定.
3)什么是 ClassLoader
虚拟机的主要任务是装载 class 文件并执行其中的字节码, 而 class 文件是由虚拟机的类加载器 (ClassLoader) 完成的, 在一个 Java 虚拟机中有可能存在多个类加载器.
任何 java 运用程序, 可能会使用两种类加载器, 即启动类加载器 (Bootstrap) 和用户自定义类加载器.
启动类加载器是 Java 虚拟机唯一实现的一部分, 它又可分为原始类装载器, 系统类装载器或默认类装载器. 它的主要作用是从操作系统的磁盘装载相应的类, 如 Java API 类等.
用户自定义装载类, 即按照用户自定义的方式来装载类.
2, 将字节码文件存储在 JVM 内存区
当 JAVA 虚拟机运行一个程序时, 它需要内存来存储许多东西.
比如如字节码, 程序创建的对象, 传递给方法的参数, 返回值, 局部变量以及运算的中间结果等, 这些相关信息被组织到 "运行时数据区".
根据厂商的不同, 在 Java 虚拟机中, 运行时数据区也有所不同. 有些运行时数据区由线程共享, 有些只能由某个特定线程共享.
运行时数据区大致可分几个区: 方法区, 堆区, 栈区, PC 寄存器区和本地方法栈区.
在该阶段中, 涉及到如下基本概念和知识.
1)方法区
方法区用来存储解析被加载的 class 文件的相关信息.
当虚拟装载一个 class 文件后, 它会从这个 class 文件包含的二进制数据中解析类型信息, 然后将该相关信息存储到方法区中.
2)堆
堆是用来存储相关引用类型的, 如 new 对象. 当程序运行时, 虚拟机会把所有该程序在运行时创建的对象都放到堆中.
3)PC 寄存器
PC 寄存器主要用来存储线程. 当新创建一个线程时, 该线程都将得到一个自己的 PC 寄存器 (程序计数器) 以及一个 java 栈.
Java 虚拟机没有寄存器, 其指令集使用 Java 栈来存储中间数据.
4)栈区
栈区主要用来存储值类型的, 如基本数据类型. 需要注意的是, String 为引用类型, 是存在堆中的.
Java 栈是由许多栈帧组成的, 一个栈帧包含一个 Java 方法调用的状态, 当线程调用一个方法时, 虚拟机压入一个新的栈帧到该线程的 Java 栈中, 当该方法返回时, 这个栈帧从 Java 栈中弹出.
3, 执行引擎与运行时数据区交互
运行时数据区为执行引擎提供了执行环境和相关数据, 执行引擎通过与运行时数据区交互, 从而获取执行时需要的相关信息, 存储执行的中间结果等
4, 执行引擎与本地方法接口
当要执行本地方法时, 执行引擎将调用本地方法接口来获取相关 OS 本地方法.
需要注意的是, 本地方法与操作系统强耦合的.
5,JVM 在具体操作系统上执行
JVM 通过调用本地接口来获取本地方法, 从而实现在具体的平台上执行. 比如在 Linux 系统上执行, 在 Windows 系统上执行和在 Unix 系统上执行.
来源: http://www.jianshu.com/p/d37db63514a9