JAVA程序的运行原理:
1. 源文件(.java源代码)通过编译器编译成字节码文件class。
2. 通过JVM中的解释器将字节码文件生成对应的可执行文件,运行。
- 将编译后的程序加载到方法区,存储类信息。
4. 运行时,JVM创建线程来执行代码,在虚拟机栈和程序计数器分配独占的空间。根据方法区里的指令码,在虚拟机栈对线程进行操作,程序计数器保存线程代码执行到哪个位置。
“一处编写,处处运行”:编译后,不依赖于平台环境,在各种操作系统均可运行。
Class字节码文件:是一个二进制文件,包含了JAVA程序执行的字节码,包含的信息有版本、访问标志、常量池、当前类、超级类、接口、字段、方法、属性等,中间没有任何分隔符,文件开头有一个特殊标志,用16进制表示为0xcafebabe。
Class文件反编译后,可通过“JVM指令码表”查看指令,这些指令运行时都保存在方法区。
从指令码中看出,没有定义构造函数时,会有隐式的无参构造函数;方法参数保存在虚拟机栈里的本地变量表,每一个main方法都有一个String数组参数,保存在本地变量表的变量0。
JVM运行时数据区:包含线程共享部分和线程独占部分。
线程共享:线程共同访问的内存数据空间,随着JVM(虚拟机)或者GC(垃圾回收)而创建和销毁。包含方法区和堆内存。
1、方法区:JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据。在JVM规范中,这是一个逻辑区,根据不同的虚拟机有不同的具体实现,如oracle的HotSpot的方法区,在java7中放在永久代,java8中放在元数据空间,并通过GC机制对这个区域进行管理。目前有三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。
2、堆内存:存放对象的实例,在JVM启动时创建。可细分为老年代、新生代,垃圾回收器主要就是管理堆内存,如果满了就会出现OOM(OutOfMemoryError)。
线程独占:每个线程都会有自己独立的空间,随着线程的生命周期而创建和销毁。包含虚拟机栈、本地方法栈、程序计数器。
1、虚拟机栈:即虚拟机执行JAVA代码的栈,每个线程都会在这有一个私有空间。线程栈由多个栈帧(Stack Frame)组成,一个线程会执行一个或多个方法,一个方法对应一个栈帧。栈帧内容包括:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。栈内存默认最大是1M,超出则抛StackOverflowError。
2、本地方法栈:即虚拟机执行Native本地方法的栈,和虚拟机栈的主要区别是执行的方法不同。在虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现。HotSpot虚拟机中虚拟机栈和本地方法栈的实现方式是一样的,超出大小后也会抛出StackOverflowError。
3、程序计数器:记录当前线程执行字节码的位置,存储的是字节码指定地址,如果执行Native方法,则计数器值为空。每个线程在这都有一个私有空间,占用很少的内存空间。
CPU同一时间只会执行一条线程中的指令,JVM多线程会轮流切换并分配CPU执行时间,在线程切换后,需要通过程序计数器来回复正确的执行位置。
查看class文件内容
使用Demo.Java进行测试, 运行javac Demo.java
编译成class文件, 然后运行javap -v Demo.class > Demo.txt
查看class文件内容
Demo.Java
public class Demo{
public static void main(String[] args){
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
System.out.println(a + b);
}
}
Demo.txt
Classfile /E:/*/Demo.class
Last modified 2019-6-30; size 412 bytes
MD5 checksum efd785af33e58aa9fc9834110b74b87b
Compiled from "Demo.java"
public class Demo
minor version: 0 //次版本号
major version: 52 //主版本号 版本号规则: JDK5,6,7,8分别对应49,50,51,52
flags: ACC_PUBLIC, ACC_SUPER //访问标志
Constant pool: // 常量池 类信息包含的静态常量, 编译之后就能确认
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V
#4 = Class #19 // Demo
#5 = Class #20 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 main
#11 = Utf8 ([Ljava/lang/String;)V
#12 = Utf8 SourceFile
#13 = Utf8 Demo.java
#14 = NameAndType #6:#7 // "<init>":()V
#15 = Class #21 // java/lang/System
#16 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#17 = Class #24 // java/io/PrintStream
#18 = NameAndType #25:#26 // println:(I)V
#19 = Utf8 Demo
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = Utf8 java/io/PrintStream
#25 = Utf8 println
#26 = Utf8 (I)V
{
public Demo(); // 默认隐式无参的构造函数
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]); //程序的入口main方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC //访问控制
Code:
stack=3, locals=5, args_size=1 //方法栈栈帧中操作数栈的深度,本地变量数量,参数数量
0: sipush 500 //Jvm执行引擎执行这些源码编译过后的指令码, javap翻译出来的
3: istore_1 //是操作符, class 文件内存储的是指令码, 前面的数据是偏移量,
4: bipush 100 //Jvm根据这个去区分不同的指令. 详情参照'JVM指令码表'
6: istore_2
7: iload_1
8: iload_2
9: idiv
10: istore_3
11: bipush 50
13: istore 4
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_3
19: iload 4
21: iadd
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 7
line 6: 11
line 7: 15
line 8: 25
}
SourceFile: "Demo.java"