背景
最近正在学习JVM相关的课程,觉得这块非常有意思,因此就想写出来和大家共勉。
JVM整体架构图解
以上就是JVM的整体架构,接下来说一下他们之间是如何协作的
代码执行过程
编译好的class文件被类装载系统装载加载到方法区----开始执行字节码指令-----程序计数器记录当前指令位置----虚拟机栈------堆内存
各组件作用解释
- 字节码执行引擎:执行编译后的代码指令
- 栈:线程私有,由栈帧组成,一个线程栈可以有多个栈帧(eg:方法里面调用方法)
- 本地方法栈:用于存放native本地方法
- 方法区:jdk1.8以后这块区域又称元空间,用于存放:静态变量、常量、类信息
代码运行过程详解
- 首先,你的JVM进程会启动,就会先加载类到内存里。然后有一个main线程,开始执行你的类中的main()方法
- main线程是关联了一个程序计数器的,那么他执行到哪一行指令,就会记录在这个组件里
- 接着,就是main线程在执行main()方法的时候,会在main线程关联的Java虚拟机栈里,压入一个main()方法的栈帧(栈帧详情请继续看)
- 接着会发现需要创建一个Test类的实例对象,此时会加载Test类到内存里来
- 然后会创建一个Test的对象实例分配在Java堆内存里,并且在main()方法的栈帧里的局部变量表引入一个“test”变量,让他引用:Test对象在Java堆内存中的地址
- 接着,main线程开始执行Test对象中的方法,会依次把自己执行到的方法对应的栈帧压入自己的Java虚拟机栈
- 执行完方法之后再把方法对应的栈帧从Java虚拟机栈里出栈(出栈后栈里面的就会被回收[此回收并非gc回收])
栈(线程)图解
- 局部变量表:存储基础类型的变量,可以类似它是张数据库表
- 操作数栈:Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈
- 静态解析:一个方法要调用其他方法的时候,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用。这就叫做静态解析
- 动态连接:Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(eg:new一个类后,调用类中的方法)
- 程序计数器:记录所执行到的行号(指针位置),执行完一行代码,字节码执行引擎修改程序计数器
栈详解
public static void main(String[] args) {
System.out.println(calc());
}
public static int calc(){
int a = 1;
int b = 2;
return (a + b) * 10;
}
代码执行,首先执行:
int a = 1;把这个放入局部变量表。同理b也是如此。
(a + b) * 10:a+b:从操作数栈拿出a和b然后执行加法,然后把结果压入操作数栈。乘10同上
JVM内存参数设置
-XX:MaxMetaspaceSize: 元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小
XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,达到该值就会触发 full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超 过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值
XX:PermSize代表永久代的初始容量
注意:full gc是非常昂贵的操作