JVM运行时数据区

运行时数据区

运行时数据区包含PC寄存器,栈,堆内存,方法区,本地方法栈。

PC寄存器(程序计数器)

介绍:

1.PC寄存器是一块很小的内存空间,是运行速度最快的存储区域,因为只存储了指向下一条指令的地址

2.在JVM规范中,每个线程有它自己的PC寄存器,是线程私有的,生命周期与线程的生命周期保持一致

3.任何时间一个线程都之后又一个方法在执行,也就是当前方法,程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,如果是在执行native方法,则是未指定值(undefined)

4.程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖PC寄存器来完成

5.字节码解释器工作时就是通过改变这个计时器的值来选取下一条需要执行的字节码指令

6.唯一一个不会出现OOM异常的区域

作用:

PC寄存器用来存储指向下一条指令的地址,也就是即将要执行的指令代码的地址,由执行引擎读取下一条指令

为什么使用PC寄存器记录当前线程的执行地址?

因为CPU需要不停的切换各个线程,在来回切换线程的时候需要依靠PC寄存器来记录执行的命令,这样切换回来就能知道从哪继续开始。

PC寄存器为什么被设定为线程私有?

因为如果不是线程私有的话,CPU在线程之间来回切换的时候记录字节码指令地址会被覆盖,导致切换回来的时候程序会出现问题。所以要设置为私有的,每个PC寄存器独占一份,这样就不会产生冲突覆盖的问题。虚拟机栈

虚拟机栈:

概念:

栈是管运行的单位,内部保存的是一个个的栈帧,对应着一个个java方法的调用,生命周期和线程的生命周期一致,主管java程序的运行

优点:指令集小,跨平台,编译器容易实现

缺点: 性能下降,实现同样的功能需要更多的实现

特点: 访问速度仅次于PC寄存器,栈不存在垃圾回收问题,栈只有进栈和出栈俩个任务,会出现OOM,是线程私有的,执行一个方法被创建的时候都会创建一个栈帧

执行原理:

JVM对栈的操作只有俩个, 进栈出栈, 遵循先进后出后进先出的原则,在一条线程上,只会有一个活动的栈帧,只有当前正在执行的当前栈有效,这个栈帧称为当前方法,定义这个方法的是当前类。

执行引擎的所有字节码指令只针对当前栈帧进行操作。

如果该方法还调用了其他方法,对应的栈帧创建出来,放在栈顶,成为新的当前栈帧。

栈帧内部存储:

栈帧中存储了: 局部变量表,操作数栈,动态链接,方法返回地址,其他信息。

局部变量表:

概念:

定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括8种基本数据类型,对象引用,以及returnAddress类型。是线程私有的。

局部变量表所需的容量大小是在编译器确定下来的,并保存在方法的Code属性的Maximum local variables数据项中。在方法运行期间是不会改变局部变量表容量的大小。

当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

Slot:

局部变量表的存储单元是Slot(变量槽),从0开始存储

如果栈帧是实例方法,则会存储this

在局部变量表中,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。

byte,short,char,boolean 在存储前被转换为int, boolean 0表示false,非0表示true。

具体演示:

在这里插入图片描述

slot的重复利用:

栈帧中的局部变量表中的槽位是可以重复利用的,如果一个局部变量超出了作用域,那在其后面申明新的局部变量就有可能会复用过期局部变量的槽位,来达到节省资源目的。

操作数栈:

操作数栈是基于数组实现的

在方法执行过程中,往栈帧中写入数据或者读取数据

是用来操作方法当中变量的:复制,求和,交换等操作

操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时存储空间。

操作数栈是Jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的

操作数栈的大小在编译期间就定义好了,运行期间不可改变

如果被调用的方法带有返回值的话,其返回值会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令

动态链接:

概念:

指向运行时常量池的方法引用

每一个栈帧内部包含一个指向运行时常量池中栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用,保存在class文件的常量池里,比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

方法返回地址:

概念:

本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层栈帧的局部变量表,操作数栈,将返回值压如调用者栈帧的操作数栈,设置pc寄存器值等,让调用者栈帧继续执行下去。

存放调用该方法的PC寄存器的值

一个方法的结束,有两种方式:

1.正常执行完成

2.出现未处理的异常,非正常退出

无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正确退出时,调用者的PC寄存器的值作为返回地址,即调用该方法的指令的下一条指令的地址,而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

正常完成出口和异常完成出口的区别:

通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

抛出异常时的异常处理存储在异常处理表中。

字节码指令:

ireturn: boolean,byte,char,short,int

lreturn: long

freturn: float

dreturn: double

areturn: 引用类型

return: void

方法绑定

在JVM中,将符号引用转换为调用方法的直接引用于方法的绑定机制相关。跟多态挂钩

静态链接:

当一个字节码文件被装在进JVM内部时,如果被调用的目标方法在编译期间可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称为静态链接。

动态链接:

如果被调用的方法在编译期间无法被确定下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此被称为动态链接。

绑定机制:

绑定是一个字段,方法或者类在符号引用被替换为直接引用的过程,这仅仅只发生一次。分为早期绑定和晚期绑定,分别对应静态链接和动态链接。

早期绑定:

被调用的目标方法如果在编译期间可知,且运行期间保持不变时,即可将这个方法与所属的类型进行绑定,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。

晚期绑定:

被调用的方法在编译期间无法被确定下来,只能够在程序运行期间根据实际的类型绑定相关的方法,这种绑定方式称为晚期绑定。

方法的调用指令区分:

非虚方法:

非虚方法对应的是早期绑定和静态链接,在编译期间就可知,称为非虚方法。

静态方法,私有方法,final修饰的方法,实例构造器,父类方法都是非虚方法。

虚方法:

虚方法对应的是晚期绑定和动态链接,在编译期间不可知,在运行期间才可知,称为虚方法

调用指令:

普通调用指令:

1.invokestatic: 调用静态方法,解析阶段确定唯一方法版本

2.invokespecial: 调用方法,私有及父类方法,解析阶段确定唯一方法版本。

3.invokevirtual: 调用所有虚方法

4.invokeinterface: 调用接口方法

动态调用指令:

5.invokedynamic: 动态解析出想㔿调用的方法,然后执行

注意:

final修饰的方法也是invokevirtual,但是final修饰的是非虚方法

前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持用户确定方法版本,其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法

虚拟机堆内存:

概念:

  • 堆是线程共享的,会出现GC垃圾回收,会出现OOM。

  • 一个JVM实例只存在于一个堆,堆在JVM程序启动的时候就初始化了内存大小,是JVM内存区域最大的一块空间。

  • 堆内存大小是可以调整的。

  • 堆可以处于物理上不连续的空间,但在逻辑上堆应该是连续的。

  • 几乎所有的对象都保存在堆中

  • 在方法出栈后,方法中保存的引用被销毁后,指向堆当中的对象不会被及时回收,而是等着堆空间快满的时候,GC来进行统一回收。这样做的好处是节省性能。

堆内存的划分:

概念:

  • 在JDK1.7时:新生代,老年代,永久代
  • 在JDK1.8时:新生代,老年代,元空间
  • 堆内存的大小是跟新生代和老年代有关的,跟元空间没有关系。新生代和老年代的内存比例是1:2
  • 新生代中伊甸园区和幸存者区的比例是8:1:1,期间幸存者区只会有一块被使用
  • 几乎所有的对象被创建出来都会被存入新生代,如果新生代存不下则直接放入老年代,如果老年代放不下则爆OOM异常。
新生代
  • 新生代又分为:伊甸园区,幸存者0区,幸存者1区。
  • 幸存者0区和幸存者1区只会有一个用来存放对象
老年代

老年代是用来存储存活时间较长,基本不会被销毁的对象。

对象分配过程

在往堆当中存放对象的时候,首先会存放到伊甸园区,如果伊甸园区放不下,则会进行一次minor GC,GC完毕后,如果还放不下则会存放老年代,如果老年代放不下,则触发major GC,GC完毕后,如果老年代也放不下,则触发OOM。如果伊甸园区满了,则触发minor GC,存活的对象则会存放幸存者0区或者幸存者1区,如果堆空间又满了,则触发minor GC。存活的对象放入空的幸存者区,先前的幸存者区存活的对象会存入空的幸存者区,并触发年龄计数器计数,直至阈值到达15则进入老年代,如果老年代满了则触发major GC。如果老年代满了则爆OOM异常。

新生代minor GC

minor GC只回收伊甸园区的垃圾,但是每次minor GC清理都清理新生代的垃圾。

minor GC触发频率很高,回收效率快。

会引发STW,阻塞其他用户线程。

老年代major GC

在触发major GC前,会尝试触发一次minor GC,之后如果空间还不足则触发major GC。

major GC的速度一般会比minor GC慢10倍以上,STW时间更长。

如果major GC后,内存还不足,则爆OOM异常。

TLAB

堆空间常用的jvm参数

-XX:+PrintFlagsInitial: 查看所有的参默认初始值
-XX:+PrintFlagsFinal: 查看所有的最终值,会存在修改值
-Xms:初始堆空间内存(默认为物理内存的1/64)
-Xmx:最大堆内存空间大小(默认为物理内存的1/4)
-Xmn:设置新生代的大小(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构的占比
-XX:SurvivorRation:设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails: 输出详细的GC处理日志
打印gc简要信息:①: -XX+PrintGC ②: -verbose:gc
-XX:HandlePromotionFailure: 是否设置空间分配担保

方法区:

概念:

方法区在逻辑上是属于堆的区域,而在物理上不属于堆。所以方法区是独立于的一块内存。

方法区在JVM启动的时候被创建,方法区的大小决定了系统可以保存多少个类,方法区是线程共享的,会触发OOM,有垃圾回收。

方法区存储信息

加载类的类型信息(全限定名,父类,修饰符,类的接口)

常量

静态变量

即时编译器后的代码缓存

方法区参数设置

方法区设置内存的大小最小值应设置大一些

-XX:MetaspaceSize 
 最小值 在64位Windows下,默认是21M
一旦超过21M FullGC将会触发并回收没用的类,然后初始位被重置,新的初始位的值取决于GC后释放了多少空间,如果释放的空间不足,哪么不超过最大值时,适当提高该值,如果释放空间过多,则适当降低该值。
-XX:MAxMetaspaceSize 
值是-1,没有限制,限制是本地内存。

运行时常量池

  • 运行时常量池是方法区的一部分

  • 常量表是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中

  • 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池

  • JVM为每个已加载的类型都维护一个常量池。池中的数据项像数组项一样,是通过索引访问

  • 运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池周静的符号地址了,这里换为真实地址。

    ​ 运行时常量池在,相当于Class文件常来给女孩的另一重要特种是: 具备动态性

  • 运行时常量池类似于传统编程语言中的符号表,但是它锁包含的数据却比符号表要更加丰富一些。

  • 当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛OOM异常。

常量池

常量池是用来存放类的信息。

java文件编译生成后字节码文件,而原本的数据放入常量池当中,字节码文件需要的数据则从常量池当中拿。相应的字节码对应常量池当中的数据,也就是引用。

也就是说常量池中保存的是类信息的引用,字节码需要数据则引用常量池即可。

为什么要有常量池?

在加载很多类的时候,有些类中的信息是同样的,这样必然会加载一些重复的信息,消耗不该消耗的资源。而使用常量池即可只需要保存这些信息的一份,字节码需要的数据只需要引用即可。这样大大减少了不必要的资源,提高性能。

方法区的演进

JDK1.6有永久代,静态变量,字符串常量池存放在永久代中

JDK1.7有永久代,但已经逐步去除永久代,因为静态变量,字符串常量池保存在堆中

JDK1.8无永久代,改为元空间,但静态变量,字符串常量池还保存在堆中

永久代为什么要被元空间替换?

因为永久代使用的是JVM内存,元空间使用的是本地内存。

因为永久代的内存大小不好调整以及对永久代的调优是比较困难的所以换成了元空间。

方法区的垃圾回收

Java虚拟机规范 对方法区的约束是非常宽松的,可以回收也可以不回收。一般来说这个区域回收效果是比较头疼的地方,因为涉及到了类回收,其他回收都挺好。但是对方法区这块区域又不能不回收。所以这是一块令人比较烦的区域。

主要回收常量池中废弃的常量和不再使用的类型

回收类的条件:

1.该类的所有实例都已经被回收,该类的子类也要被回收。

2.加载该类的类加载器被回收

3.该类对应的java.lang.Class对象在任何地方都没有被引用,无法在任何地方通过反射调用该类的方法。

以上3个条件都同时满足之后并不是直接回收,而是允许该类回收。

回收常量的条件:

只要没有引用指向常量即可回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值