本节讲涉及JVM虚拟机相关知识点,包括JVM的体系结构,Class的加载.GC等相关知识放在下节.
概念
首先来了解概念,什么是JVM,JVM与JDK,JRE是什么关系.
JRE ===> JRE是java运行环境,是java程序能跑起来的基础
JDK ===> JDK是java开发工具包,JDK中也有一套JRE,所以在jdk的安装路径底下会有jre
JVM ===> JVM即java虚拟机,是JRE的一部分.是虚构出来的计算机,它包括字节码指令集,寄存器,栈,堆和方法区等.
用简单点的图形表示三者关系就是这样
复杂点的话就是这样
类加载机制
类加载主要包括加载,验证,准备,解析,初始化几步
而加载器这里就要再说道说道了.
加载器
JDK提供三种加载器从上往下分别是
BootStrap ClassLoader 启动类加载器 ===>加载 jre/lib/*.jar 底下类
Extension ClassLoader 扩展类加载器 ===>加载 jre/lib/ext/*.jar 底下的类
Application ClassLoader 应用程序加载器 ===>加载 classpath底下的类
这三种加载器从上往下依次为下一级的父类
而在加载器的选择上,存在一种机制:双亲委托
双亲委托机制即当加载一个类时,首先判断是否已被加载,如果已经被加载则直接返回,如果未被加载,则直接将加载的任务提交给父类,父类同理,直到最顶层(BootStrap ClassLoader),当所以父类都没有加载的时候,才会调用当前加载类去加载.
双亲委派机制主要是为了防止类的重复加载
JVM结构
接下来说说jvm结构,先看一张简图
这是一张Class的加载图,虚拟机栈,本地方法栈,程序计数器为线程私有数据,一个线程对应一个.堆,方法区(元空间)为线程共享数据,所以线程都能访问.接下来对这些部分依次分析.
虚拟机栈
每个线程在运行时都会有一个栈与之对应,这点在之前讲锁的升级相关相关知识点时有涉及到.一个线程对应一个栈,而栈里面可能存在若干栈帧,具体有几个,取决于线程运行时调用的方法.比如
从图中可以看到,运行到该断点的时候,main没有执行完,被压在底部,showStack在顶部,这时在该执行线程的栈中就会分配两个栈帧分别对应这两个方法.而当运行到getError方法时则会抛出异常 java.lang.StackOverflowError.原因在于getError方法是一个无限递归,不停调用自己,这样也就意味着需要不停的创建栈帧,而当线程对应的栈的空间不足以容纳这么多栈帧时,就会抛出该Erorr.
而每个栈帧底下又有局部变量表,操作数栈,动态链接,方法返回地址
局部变量表:
变量值的存储空间,用于存放方法参数及方法内部的局部变量;局部变量表以变量槽为最小单位(Slot),32位虚拟机中一个Slot可以存放32位(4 字节)以内的数据类型( boolean、byte、char、short、int、float、reference和returnAddress八种,对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写.reference类型虚拟机规范没有明确说明它的长度,但一般来说,虚拟机实现至少都应当能从此引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据.Slot是可以重用的,当Slot中的变量超出了作用域,那么下一次分配Slot的时候,将会覆盖原来的数据.Slot对对象的引用会影响GC(要是被引用,将不会被回收). 系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值).也就是说不存在类变量那样的准备阶段
操作数栈:
操作数栈和局部变量表一样,在编译时期就已经确定了该方法所需要分配的局部变量表的最大容量.操作数栈的每一个元素可用是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型占用的栈容量为2.当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作(例如:在做算术运算的时候是通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行参数传递的).
动态链接:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中方法的符号引用为参数.这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态方法,私有方法等),这种转化称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接.
方法返回地址:
用来返回方法的被调用位置.
本地方法栈
登记native方法,在Execution Engine执行时加载本地方法库,java底层是并非是java,本地方法栈则是用来记录这些方法.
程序计数器
指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码).在后文中例子可以看到编码后的文件中执行语句前都存在数字编号,那些编号就是为程序计数器指路的路标,当程序计数器增长到某个数的时候才会知道自己该去执行哪条命令.
例子:
public class MyJvmShow {
public static void main(String[] args) {
MyJvmShow myJvmShow=new MyJvmShow();
System.out.println(myJvmShow.getNum());
}
public int getNum(){
int a=1;
int b=2;
int c=(a+b)*100;
return c;
}
}
编译后
Classfile /F:/zDemo/studyDemo/spring2Demo/target/classes/study/demo/myJvm/MyJvmShow.class
Last modified 2020-1-8; size 723 bytes
MD5 checksum 96d74beeaec9c0360014d938a3af141e
Compiled from "MyJvmShow.java"
public class study.demo.myJvm.MyJvmShow
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#28 // java/lang/Object."<init>":()V
#2 = Class #29 // study/demo/myJvm/MyJvmShow
#3 = Methodref #2.#28 // study/demo/myJvm/MyJvmShow."<init>":()V
#4 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #2.#32 // study/demo/myJvm/MyJvmShow.getNum:()I
#6 = Methodref #33.#34 // java/io/PrintStream.println:(I)V
#7 = Class #35 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lstudy/demo/myJvm/MyJvmShow;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 myJvmShow
#20 = Utf8 getNum
#21 = Utf8 ()I
#22 = Utf8 a
#23 = Utf8 I
#24 = Utf8 b
#25 = Utf8 c
#26 = Utf8 SourceFile
#27 = Utf8 MyJvmShow.java
#28 = NameAndType #8:#9 // "<init>":()V
#29 = Utf8 study/demo/myJvm/MyJvmShow
#30 = Class #36 // java/lang/System
#31 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
#32 = NameAndType #20:#21 // getNum:()I
#33 = Class #39 // java/io/PrintStream
#34 = NameAndType #40:#41 // println:(I)V
#35 = Utf8 java/lang/Object
#36 = Utf8 java/lang/System
#37 = Utf8 out
#38 = Utf8 Ljava/io/PrintStream;
#39 = Utf8 java/io/PrintStream
#40 = Utf8 println
#41 = Utf8 (I)V
{
public study.demo.myJvm.MyJvmShow();
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 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lstudy/demo/myJvm/MyJvmShow;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class study/demo/myJvm/MyJvmShow
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #5 // Method getNum:()I
15: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
18: return
LineNumberTable:
line 8: 0
line 9: 8
line 10: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
8 11 1 myJvmShow Lstudy/demo/myJvm/MyJvmShow;
public int getNum();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 100
9: imul
10: istore_3
11: iload_3
12: ireturn
LineNumberTable:
line 13: 0
line 14: 2
line 15: 4
line 16: 11
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lstudy/demo/myJvm/MyJvmShow;
2 11 1 a I
4 9 2 b I
11 2 3 c I
}
SourceFile: "MyJvmShow.java"
相关部分可自行对应参考.