Java 程序是如何运行起来的
0 Java 简介
首先简单介绍一下 Java 的历史与 Java 的核心机制。
Java 的历史
Java 最初产生于 SUN 公司的 “Green” 小组,该小组由 James Gosling 领导,最初的名字叫 Oak,在发现该名称已被注册之后,改名为 java。从 1996 年 1 月到现在,Java 经历了不断的演变。1999 年 6 月,Java 体系被分为三个方向:J2ME(微型版,移动端)、J2SE(桌面版,桌面端) 和 J2EE(企业版,服务端)。如今 JavaSE 的长期支持版(LTS)已经发展到了 11 版。
Java 的核心机制
Java 的两个基本的核心机制分别是 Java 虚拟机(JVM)和 Java 的垃圾回收器(GC):
-
JVM 可以理解为一个可运行 Java 字节码的虚拟计算机系统。正是 JVM 的存在使得 Java 语言具有了跨平台的特性,因为所有的 Java 原码都会被首先编译成 Java 字节码文件,从而交给 JVM 处理,而不直接与具体的操作系统通信。所以,对于不同的平台,有对应的不同的 JVM 来实现代码与平台的分离,从而实现了所谓的“一次编译,到处执行”。
-
Java 中的 GC 负责自动回收 JVM 中不再使用的内存空间。GC 的垃圾回收工作不需要由外部干预,从而消除了程序员回收无用内存的责任(在C/C++ 中这个工作是需要程序员负责考虑的)。GC 是 JVM 提供的一种系统线程跟踪存储空间的分配情况,当 JVM 处于空闲时,它会检查并释放掉那些可以被释放的存储空间。GC 是无法被精准控制和干预的,在 Java 程序运行的过程中它是自主启动并运行的。
1 JDK、JRE和JVM之间的关系
-
JDK:Java Development Kit,Java 开发工具包 - 开发人员进行 Java 软件开发测试的一套工具
-
JRE:Java Runtime Environment,Java 运行时环境 - Java 软件成品运行所依赖的环境
-
JVM:Java Virtual Machine,Java 虚拟机 - Java 语言实现跨平台运行的一种软件
-
三者的关系是:JDK包含JRE,JRE包含JVM,像下图这样。
2 Java 编译与运行过程
-
java程序的运行包括两个非常重要的阶段:编译阶段 和 运行阶段。它们的大致过程如下图。
-
编译:
-
java 源文件通过 JDK 中的编译器 javac 通过的编译,编译操作方式如下:
javac -encoding 编码方式 -d 输出为字节码文件的路径 代码(相对)路径。如:
javac -encoding utf8 -d ../out/ ./src/*.java,它表示以utf-8格式编码./src/路径下的所有 Java 源文件,生成的字节码文件放到上一级目录的out/路径下。 -
编译阶段的主要任务是检查 java 源程序语法,符合 java 语法规则能正常生成 字节码文件 xxx.class。字节码文件是只有 JVM 才能理解的文件,通过 JVM 解释为不同操作系统的可执行二进制机器码。字节码文件是程序最终执行需要的文件,而不是 java 源文件。字节码文件可以拷贝到任何操作系统中运行,实现跨平台的效果。
-
一个 java 源文件可以生成多个字节码文件,每个字节码文件对应源文件中定义的一个类;
-
-
运行:
-
使用JDK自带的执行工具/命令(java.exe)运行程序:
java 包名.类名。如:
java com.somedomain.util.DataMonitor,包名实际上已目录结构的形式存在,并且通常以反向域名的方式形成多级目录结构,比如这里对应的目录结构为./com/somedomain/util/,最后跟类名DataMonitor,它必须是在指定包内(目录结构下)存在的可运行字节码文件(类中包含作为程序入口的public static void main()方法)。 -
运行阶段过程:
java.exe 启动 JVM,JVM启动类加载器(ClassLoader),类加载器在硬盘上搜索对应的字节码文件,将其装载到 JVM 当中,JVM 将字节码文件解释成二进制数据,操作系统执行二进制和底层硬件平台进行交互,完成相应程序效果。
-
3 程序运行过程的内存分析
-
JVM 内存空间主要分为三块(还有其他空间):方法区内存、堆内存、栈内存
-
方法片段属于字节码文件的一部分,字节码文件在类加载时,将其放到了方法区中。所以,JVM中的三块主要内存空间中最先有数据的是方法区内存,其中存放了代码片段。代码片段在方法区内存中只有一份,可以被重复使用。
-
方法只有在被调用时才会被动态地分配所属空间。每次调用都需要为该方法在栈内存中分配独立的运行空间,此时发生压栈动作。方法执行结束后,对应的内存空间会全部释放,此时发生弹栈动作。下面是示例代码和方法执行过程的内存变化图。
public class UserTest {
public static void main(String[] args) {
int i = 10;
add(i); // 传递i中的字面值10
System.out.println("value of i in main() is " + i); // 10
}
public static void add(int i) {
i++;
System.out.println("value of i in add() is " + i); // 11
}
}
传递字面值
- 运行阶段在方法体中声明的局部变量,在栈内存中为其分配空间。而局部变量的参数传递实际上是传递的参数地址中所存的内容,这个内容可以是字面值,也可以是对象地址 。
-
字面值传递中,接收方参数(等号左边)的值被修改,而传递方参数(等号右边)的值不受影响。
-
对象地址的传递中,由于两个参数保存的内容指向同一对象地址,因此两方的操作指向同一片内存空间。下面是相应示例代码和方法执行过程的内存变化图。
-
public class UserTest {
public static void main(String[] args) {
User u = new User("Tomas", 56);
System.out.println(u.name+"'s age before add() is "+u.age); // 1
add(u); // 传递u的内存地址
System.out.println(u.name+"'s age after add() is "+u.age); // 10
}
public static void add(User u) {
u.age ++;
System.out.println(u.name+"'s age within add() is "+u.age);
}
}
class User {
String name;
int age;
public User(String n, int a) {
name = n;
age = a;
}
}
传递对象地址

简单介绍一下 Java 程序的运行流程和内存分析。
浙公网安备 33010602011771号