看过周志明的<深入了解Java虚拟机>,网上零零散散看到过相关的帖子,现在再回忆JVM,印象也是很模糊. 根据B站上记忆大师的推荐的方法,如果记住一个东西,首先需要理解它,最好类别,然后图形化它,这样记忆才会深刻. 所以今天又来画图了 —— 其实我是个画家.
这次我们要画的内容是 —– JVM.
前面本想总结下synchronized
的,但是呢synchronized涉及到Object 对象头, 对象头又设计到对象的初始化, 涉及到JVM , 涉及到 类加载. 所以准备从头到尾的来总结一遍. 类加载的回头补上,今天先从JVM开始画.
开头的图摘自 官网Developer Guides
Java 代码是怎么被JVM虚拟机所执行的.
我们的Xxx.java
文件,经过javac
编码成 Xxx.class
Xxx.class
是JVM 所能解析的 二进制文件, JVM将Xxx.class
根据执行机器上的JDK环境将文件内容处理成JVM指令.
我们可以通过javap
指令,也将Xxx.class
转成JVM指令.
JVM 最初就是执行转换后的JVM指令来执行我们所编写的java代码.
JVM 运行时数据区结构
类加载系统
负责将类加载到内存区,本着Java中万物皆对象,负责加载类的相关类也是JAVA对象, 比如 ClassLoader
,类加载系统不仅仅限于类加载器,底层是基于C去实现加载的.
堆区
堆区:用于存放对象实例,我们new
出来的对象一般都存储在堆上.(但也有特殊情况,有时候会存在栈上).
默认情况下:
新生代 : 老年代 = 1 : 2
eden : s0 : s1 = 8 : 1 : 1
但是这些都是可以通过JVM参数调整的.
堆区是线程共享的.
元数据区(运行时常量池)
用于存储运行时常量池,类信息,常量静态变量.
元数据区是线程共享的.
程序计数器
程序计数器:记录程序在内存中的指针位置,可以理解为当前线程执行的字节码的行号指示器.
字节码执行引擎通过改变计数器这个值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等功能都需要依赖计数器来完成.
字节码执行引擎
字节码执行引擎:负责执行加载到元数据区的字节码文件 和 修改程序计数器.
栈区/栈帧
栈区/栈帧: 线程运行时开辟出来的一快内存空间,有称栈帧.是线程独享的一块空间. 栈帧内部又可以分为4块:
- 局部变量存储空间
- 操作数栈
- 动态链接
- 程序出口
局部变量
JVM 把方法入栈之后,会将所有的变量,放在一个数组中,执行赋值的时候是先将值压入 操作数栈中,最终出栈赋值给 局部变量,根据其索引值决定赋值给谁.
在add()
方法中,有一个数组存储着3个变量: a, b, c.
下面我们用代码验证
1 |
|
执行完 javap -c
的结果是
1 | Compiled from "Test.java" |
这里我们只看 add()
方法,就拿0: iconst_1
来说
0: iconst_1
: 前面的0 : 我们就理解其为程序计数器.
0: iconst_1
: 后面的iconst_1 : 是将int类型1 压入 操作数栈.
下面我们在来看1: istore_0
1: istore_0
: 后面的istore_0 : 是将int类型值存入局部变量0 中
1: istore_0
: 后面的_0
: 就是数组中的索引值.
下面我们改下程序
1 |
|
执行 javap -c
之后
1 | public static int add(); |
0: bipush 10
: 像操作数栈中入栈 int类型值 10
2: istore_0
: 将int类型的值出栈,存入局部变量0 中.
操作数栈
操作数栈 是一个栈结构.
这里我们以如下代码为主:
1 | public static int add(); |
我们来看下 a + b
是什么流程
1 | 6: iload_0 // 加载局部变量0 到 操作数栈 |
动态链接
动态链接是值对象实例的非静态方法的指针.
程序出口
程序出口:记录程序退出的位置.