JVM整体结构

文档

https://docs.oracle.com/javase/specs/jvms/se17/html/index.html

Java跨平台特性

Java跨平台特性主要是JVM的作用,只要在各种操作系统中适配了JVM,再将编译后的Java程序适配JVM即可,JVM屏蔽了上层具体的机器代码与指令,又为下层提供服务,使得class不需要适配操作系统,只需要适配JVM即可,如下图所示。
image

JVM的内存结构

image

关于栈的介绍官方文档如下图所示
image
每个线程在初始化时都会创建一个栈区域,在进行方法调用时会进行压栈,在栈内部会存储方法执行时需要使用到的局部变量。如果栈满还调方法时会抛出StackOverflow栈溢出。栈存储结构如下图所示。
image
栈的基本单位为栈帧,官方文档如下图所示
image
在方法调用时会自动创建一块栈帧控件,每个方法都会对应一个栈帧,在栈帧中会存储当前方法中用到的局部变量等。

使用Javap命令可以看到.class文件在JVM中运行的类似汇编语言的指令
image
这些指令都可以在指令手册中查到,比如new是创建实例
image
这几行代码在JVM中是这样执行的
image
image

首先会将10压入到操作数栈中并保存到变量中,20也一样,将20压入到操作数栈中并保存到变量中,这就是前两条赋值语句的作用。
然后该执行第三条语句了,先执行括号中的c+g,看汇编中的6到8行的语句就是实现这个功能的,将局部变量中保存的值拿出并进行相加,相加后的结果继续压入到操作数栈中,接着需要执行*10操作了,执行*10操作前先将10压入到操作数栈中,之后再将之前压入的c + g的结果进行出栈操作并相乘并保存到操作数栈中,运算后的结果会调用istore_1从操作数栈中出栈运算结果保存到变量1中也是代码中的变量c。
再然后会调用invokestatic静态方法将运算结果转换为Integer类型进行返回。
总上所述可以得知局部变量表中存储的是方法中定义的局部变量,这个表类似数组,并不是栈类型。
操作数栈的数据结构为先进后出的栈格式,可以将运算结果或待会需要使用的变量的值存储到操作数栈中,一般用于存储计算过程中的中间值。
动态链接中存储Java编译后的常量池与指令集的地址,动态链接是为了实现动态链接操作,当遇到方法调用时,根据动态链接中存储的地址获取到常量池与指令集并加载到内存中,这样就实现了动态链接操作。
方法出口存储的是执行完方法后,被调用方法的下一行地址,比如在main方法中执行完hello之后的下一条println的地址
image

局部变量表

image
存储在方法中用到的局部变量信息

操作数栈

image
用于存储执行执行过程中的操作数信息。
举个例子,我们要执行int c = a + b这个指令。
执行javap可以看到c = a + b这个指令的执行过程
image

  1. 首先会在局部变量中初始化a的值1和b的值2,两条iconst指令就行为了完成这个操作的,目前操作数栈是空的。
  2. 然后会将将a和b这两个要用的操作数压到操作数栈中,istore就是完成这个操作的,这时候操作数栈会有两个元素分别是1和2。
  3. 要执行iadd前需要从栈区弹出两个操作数,iload两条指令就是从操作数栈中弹出两个操作数,之后执行加法操作。

动态链接

image
把符号引用替换为直接引用。
在编写代码时,执行object.toString()方法后,将代码编译成.class文件后,class文件中只定义了因为程序未运行起来,所以只定义了object.toString()这个符号引用,当程序运行起来后,知道object.toStirng方法会加载到内存的指定区域中就会将这个符号引用替换未内存地址的直接引用然后去执行。

方法调用完成

image
顾名思义就是方法调用完成后将调用结果返回到被调用方中。

程序计数器

image
每个线程都有一个程序计数器
程序计数器用指向要指向的下一条指令的地址,如图所示,在运行时数据区和线程的空间区域都有程序计数器,线程中的程序计数器用于指向线程中下一条指令要执行的地址。当进行线程切换时线程会根据程序计数器保留上下文,以免线程切换回来找不到继续执行的位置。每执行完一条指令,执行引擎会自动修改程序计数器中。

本地方法栈

本地方法栈用于管理本地方法的调用。native方法,例如下图的这种方法。
image

方法区

方法区存储常量以及类模板和静态变量。
建议设置方法区的内存大小,如果不设置方法区的内存大小的话,当方法区的空间满了会进行重GC,重GC之后空间之后会使用内存进行扩容,这个扩容是无限大的,很容易出现内存耗尽。
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,达到该值就会触发
建议将MetaspaceSize和MaxMetaspaceSize设置成一样的值
设置太小的话会频繁就行重GC,性能会下降。

变量实例后的对象存储在堆空间中
堆分为伊甸园区 s0 和 s1被称为年轻代,然后老年代
新new的对象会进入伊甸园区,伊甸园区满了会进行一次轻GC,会根据对象的对象的引用来判断对象是否是垃圾,如果未被GC router上的对象引用则表示是垃圾对象,可以被回收。
Eden区了轻GC清理后会将剩余对象转移至s0/s1区中,每清理一次剩余对象的年龄会+1,当加到15时会将对象送入到老年代中。
当伊甸园区与s0区满了的话会进行轻gc,清理后的结果会移动到s1中,年轻代GC清理时就是将对象不停的轻gc然后移动到另一个对象中。
当老年代与年轻代都满了会抛出oom错误也就是内存溢出。
image

stop the world

当进行gc时,会触发stop the world,在发stop the world的过程中会将所用的用户线程暂停,所有代码执行暂停,应用程序暂停响应任何请求,然后再去gc。
如果没有stw机制的话,用户线程和gc同时在运行,gc找到了一个垃圾对象进行清除,这时候用户线程突然用到这个已经被清除的对象,这样会导致程序异常。

posted @ 2022-07-17 21:38  RainbowMagic  阅读(55)  评论(0)    收藏  举报