JVM运行时数据区详解

JVM运行时数据区详解

一 运行时数据区

包括方法区,堆区,栈区,PC寄存器和本地方法栈
在这里插入图片描述
大体介绍
在这里插入图片描述

二 PC寄存器

  1. 介绍
    (1)它是一块很小的内存空间,也是运行速度最快的存储区域
    (2)每个线程都有它自己的程序计数器,是线程私有的,生命周期和线程的生命周期一致
    (3)任何时间一个线程只有一个方法执行,也就是当前方法。程序计数器会储存当前线程正在执行的Java方法的JVM指令地址
    (4)JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟
    (5)它是程序控制流的指示器,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成
    (6)字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
    (7)作用
    PC寄存器用来存储指向下一条指令的地址,即是将要执行的指令代码,有执行引擎读取下一条指令
    在这里插入图片描述
    (8)案例
    在这里插入图片描述
    (9)面试问题
    ① 使用PC寄存器存储字节码指令地址有什么用?
    答:CPU会不停的切换各个线程,通过PC寄存器记录的地址值可以知道从哪里继续开始执行。
    ② PC寄存器为什么会被设定为线程私有?
    答:为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程都分配一个PC寄存器。

三 虚拟机栈

  1. 基本内容
    (1)每个线程在创建时都会创建一个虚拟机栈,内部保存一个栈帧,对于一次次的方法调用
    (2)是线程私有的
    (3)生命周期和线程一致
    (4)主管Java程序的运行,保存方法的局部变量(8种基本数据类型、对象引用地址)、局部结果并参与方法的调用和返回
  2. 栈的特点
    (1)栈是一种快速有效的分批额储存方式,访问速度仅次于程序计数器
    (2)JVM对Java栈的操作只有两个:方法执行压栈,执行结束出栈
    (3)对于栈来说不存在垃圾回收问题
  3. 栈中可能出现的异常
    (1)Java栈的大小是动态或是固定不变的
    若是固定不变的,若线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,会抛出StackOverflowError异常
    若动态扩展,没有足够的内存会出现OutOfMemoryError异常
    (2)可以通过使用参数 -Xss选项设置线程的最大栈空间
    在这里插入图片描述在这里插入图片描述
  4. 栈运行原理
    (1)JVM对Java栈的操作只有俩个,就是对栈帧的压栈和出栈,遵循后进先出的原则
    (2)在一条活动线程中,一个时间点上,只有一个活动的栈帧,这个栈帧被称为当前栈帧
    (3)执行引擎运行的所有字节码指令只针对当前栈帧进行操作
    (4)方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前栈
    (5)不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧中引用另一个线程的栈帧
    (6)如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧
    (7)Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令,一种是抛出异常,不管使用哪种方式,都会导致栈帧被弹出
  5. 栈帧内部结构
    每个栈帧中存储着局部变量表,操作数栈,动态链接,方法返回值和一些附加信息
    在这里插入图片描述
    (1)局部变量表
    定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,包括基本数据类型、对象引用和返回值类型
    ② 由于局部变量表建立在线程的栈上,是线程的私有数据,不存在数据安全问题
    ③ 局部变量表所需的容量大小在编译期确定下来,在方法运行期不会改变大小在这里插入图片描述
    方法嵌套调用的次数由栈的大小决定,栈越大方法嵌套调用次数越多,而参数和局部变量越多,栈帧就越大
    ⑤ 局部变量表中的变量只有在当前方法调用中有效,当方法调用结束后,随着方法栈帧的销毁,局部变量表也会销毁
    (2)局部变量表中变量槽solt的理解
    ① JVM会为局部变量表的中每一个Slot分配一个访问索引,通过索引可以访问局部变量表中的局部变量值
    ② 当一个实例方法被调用时,它的方法参数和局部变量会按照顺序被复制到局部变量表中的每一个slot上
    如果需要访问局部变量表中一个64bit的局部变量时,只需要使用前一个索引即可,比如long和double类型变量
    当前栈帧是有构造函数或实例方法创建的,那么该对象引用this将放在index为0的slot处
    在这里插入图片描述
    栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域后的新的局部变量就有可能复用过期局部变量的槽位在这里插入图片描述
    (3)操作数栈
    ① 操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
    ② 操作数栈就是JVM执行引擎的一个工作区,当方法开始执行时,这时候操作数栈为空
    ③ 操作数栈有一个明确的栈深度用于存储数值,该数值在编译期就确定了,为max_stack值
    ④ 操作数栈并非采用访问索引的方式进行数据访问的,是通过压栈和出栈玩成一次数据的访问
    ⑤ 如果被调用的方法带有返回值,其返回值将会被压入当期栈帧的操作数栈中,并更新下一条需要执行的字节码指令
    操作数栈中元素的数据类型必须和字节码指令的序列匹配,32bit和64bit分别占一个栈和两个栈单位深度
    ⑦ Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈就是指操作数栈
    ⑧ 案例代码追踪
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    (4)动态链接
    ① 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用
    ② 动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
    在这里插入图片描述
    (5)方法调用
    在JVM中,将符号引用转换为调用方法的直接引的过程与方法的绑定机制有关
    ① 绑定
    绑定是一个字段、方法或类再符号引用转换为直接引用的过程,只发生一次
    ② 静态链接
    目标方法在编译期可知且运行期保持不变,这种情况下降调用方法的符号引用转换为直接引用的过程叫做静态链接,对应的方法的绑定机制为早期绑定,这样的方称为非虚方法。
    ③ 动态链接
    被调用的方法在编译期无法确认下来,只能在程序运行期将调用方法的符号引用转换为直接引用的过程叫做动态链接,对应的方法的绑定机制为晚期绑定,这样的方法称为虚方法
    ④ 虚方法和非虚方法指令
    非虚方法指令:invokestatic,invokespecial
    虚方法指令:其余的(final修饰的除外)
    (6)方法返回地址
    方法正常退出时,调用者的PC计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址,若异常退出,返回地址通过异常表来确定,栈帧中一般不会保存这部分信息
    在这里插入图片描述
    ② 正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值

四 本地方法栈

(1)本地方法栈用于管理本地方法的调用
(2)本地方法栈也是线程私有的
(3)允许被实现成固定或者是可动态扩展的内存大小
(4)本地方法是使用C语言实现的
(5)具体做法是本地方法栈中登记本地方法,在执行引擎执行时加载本地方法
(6)本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区
(7)当某个线程调用一个本地方法时,他就不在受虚拟机控制,它和虚拟机拥有同样的权限

五 堆空间概述

  1. 堆的核心概述
    (1)所有的对象实例以及数组都应当在运行时分配在堆上,几乎所有的对象实例都在这里分配内存。
    (2)**数组和对象可能永远不会存储在栈上,**因为栈帧中保存引用,引用指向对象或者数组在堆中的位置
    (3)在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除
    (4)堆是GC垃圾收集器执行垃圾回收的重点区域
  2. 堆空间内部结构
    堆空间分为年轻代,老年代,永久代
    在这里插入图片描述
    JDK7和JDK8的区别
    在这里插入图片描述
  3. 设置查看堆内存的大小
    (1)设置堆内存大小
    **-Xms:**设置堆空间(年轻代+老年代)的初始内存大小
    **-Xmx:**设置堆空间(年轻代+老年代)的最大内存大小,开发中一般将二者设置成相同的值
    (2)默认堆空间的大小
    初始内存大小:物理内存大小 / 64
    最大内存大小:物理内存大小 / 4
    (3)查看设置的参数
    方式一:在cmd窗口 使用命令:jps 查看进程,在用 jstat -gc 进程ID查看
    方式二:在idea工具设置参数 :-XX:+PrintGCDetails

    在这里插入图片描述
  4. 新生代与老年代
    (1)设置新生代与老年代的比例,默认值为2,-XX:NewRatio数值
    (2)设置新生代中eden去与survivor区的比例,-XX:SurvivorRatio数值
    (3)对象分配过程
    说明:
    ① 当new对象先放到Eden区
    ② 当Eden区空间填满,程序创建对象,JVM的GC将对Eden区进行垃圾回收(Minor GC),销毁Eden区的不被其他对象所引用的对象,在加载新的对象到Eden区中
    ③ 然后将Eden区的剩余对象移动到S0区
    ④ 如果再次触发垃圾回收,此时将上次幸存的没有被回收的S0区的放在S1幸存区中
    ⑤ 如果再次触发垃圾回收,此时会重新放回到S0区,接着再去S1区
    ⑥ 当达到默认次数15次后,则到到老年区
    可以设置次数:**-XX:maxTenuringThreshold=**进行设置
    ⑦ 当老年区内存不足时,再次触发Major GC,进行老年区的内存清理
    ⑧ 若老年区执行了GC后发现依然无法进行对象的保存,就会产生OOM异常
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    总结:
    ① 针对幸存者S0,S1区,复制之后又交换,谁空谁是to
    ② 垃圾回收频繁在新生区收集,很少在老年区收集,几乎不再永久区/元空间收集
  5. 内存分配策略
    (1)优先分配到Eden区
    (2)大对象直接分配到老年代
    (3)长期存活的对象分配到老年代
    (4)动态对象年龄判断,如果Survivor区中相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年代
    (5)空间分配担保
    使用 -XX:HandlePromotionFailure进行分配空间
  6. 对象分配过程-TLAB
    (1)从内存模型而不是垃圾回收的角度,对Eden区进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内
    在这里插入图片描述
    (2)尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选,可以通过选项 -XX:UseTLAB设置开启TLAB空间
    (3)TLAB空间内存非常小,仅占整个Eden空间的1%,可以通过 -XX:TLABWasteTargetPercent设置它的大小
    (4)一旦对象在TLAB空间分配内存失败时,JVM就会通过使用加锁机制确保数据操作的原子性,从而在Eden区中分配空间
    在这里插入图片描述
  7. 堆空间常用的jvm参数:
    (1) -XX:+PrintFlagsInitial : 查看所有的参数的默认初始值
    (2)-XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)
    具体查看某个参数的指令: jps:查看当前运行中的进程
    jinfo -flag 参数名如SurvivorRatio 进程id
    (3)-Xms:初始堆空间内存 (默认为物理内存的1/64)
    (4) -Xmx:最大堆空间内存(默认为物理内存的1/4)
  • -Xmn:设置新生代的大小。(初始值及最大值)
    (5) -XX:NewRatio:配置新生代与老年代在堆结构的占比
    (6) -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例
    (7) -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄
    (8)-XX:+PrintGCDetails:输出详细的GC处理日志
    (9) 打印gc简要信息:① -XX:+PrintGC ② -verbose:gc
    (10) -XX:HandlePromotionFailure:是否设置空间分配担保
  1. 代码优化
    (1)栈上分配:将堆分配转化为栈分配,根据逃逸分析发现一个对象并没有逃逸出方法,就可能优化成栈上分配,在线程结束后,栈空间被回收,局部变量对象也会被回收,无需进行垃圾回收了
    ① 常见的逃逸出方法的场景:给成员变量赋值、方法返回值和实例引用传递
    ② 看new的对象实体是否有可能在方法外被调用,被调用了则发生了逃逸
    ③ 逃逸分析参数设置:-XX:+DoEscapeAnalysis,开启逃逸分析,默认打开
    (2)同步省略:如果一个对象被发现只能从一个线程访问,那么对该对象的操作可以不考虑同步。
    在动态编译同步快的时候,JIT编译器可以借助逃逸分析判断同步块所使用的锁对象是否只能被一个线程访问而没有被发布到其他线程,如果只能就会取消同步,这个过程叫做同步省略,也叫锁消除
    (3)分离对象或标量替换
    ① 有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分或全部可以不存储在内存,而是存储在CPU寄存器中
    标量是指一个无法在分解成更小的数据的数据,还可以分解的数据叫做聚合量
    ③ 若经过逃逸分析,发现一个对象不会被外界访问的话,经过JIT优化,就会把这个对象拆解成若干个成员变量来代替,这个过程就是标量替换
    ④ 标量替换参数设置:-XX:+EliminateAllocations,开启标量替换,默认打开,允许将对象打散分配在栈上
    (4)代码优化参数设置总结
    在这里插入图片描述

六 方法区

  1. 方法区概述
    (1)堆、栈和方法区的交互关系
    在这里插入图片描述
    在这里插入图片描述
    (2)对方法区的基本理解
    ① 方法区和堆一样是各个线程共享的内存区域
    ② 方法区在JVM启动的时候创建,它实际的物理内存空间和堆区一样可以是不连续的,关闭JVM就会释放方法区的内存空间
    ③ 方法区可以和堆一样可以选择固定大小和动态扩展
    ④ 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,会出现内存溢出OOM的异常
    (3)设置方法区大小的参数
    ① 元数据区大小可以使用参数 -XX:MetaspaceSize和-XX:MaxMetaspaceSize进行指定
    ② 64位Windows下,默认值XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize值是-1,没有限制
    ③ 一般初始化的高水位线设置为一个相对较高的值,避免频繁的调用Full GC
    (3)方法区的内部结构
    方法区存储内容有被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存
    在这里插入图片描述
    ① 类型信息包括:类的完整名称、父类的完整名称、类型的修饰符、类型直接接口的有序列表
    ② 域信息包括:域名称、域类型、域修饰符
    ③ 方法信息:方法名称,返回类型,参数数量和类型,修饰符,方法的字节码、操作栈、局部变量表及大小,异常表包括异常处理的开始位置结束位置、程序计数器的偏移地址、被捕获的异常类的常量池索引
    ⑤ 字节码文件处理包括类的版本信息、字段、方法以及接口等描述信息外,还包括常量池表,常量池表包括字面量和对类型、域和方法的符号引用
    在这里插入图片描述
    ⑥ 运行时常量池
    常量池表是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
    JVM为每一个已加载的类型都维护一个常量池,池中的数据项像数组项一样,是通过索引访问的
    运行时常量池相对于Class文件常量池的另一特征是具备动态性,运行时常量池包含多种不同的常量,包括编译器确定的数值字面量,运行期解析后获得的方法或字段引用,此时存储的是真实地址
    (4)方法区的演进细节

    ① 为什么字符串常量池要调整堆空间?
    因为永久代的回收效率很低,在Full GC的时候才会触发,而Full GC只有在老年代的空间不足、永久代不足时才会触发,这导致字符串常量池的回收效率不高,而放在堆里就能及时回收内存。
    ② 为什么Java8要把永久代替换成元空间?
    为永久代设置空间大小是很难确定的,对永久代进行调优也是很困难的,类的元数据被移到了一个与堆不相连的本地内存区域,这个区域就是元空间
    (5)方法区的垃圾回收
    ① 《Java虚拟机规范》对方法区的约束非常宽松,可以不要求虚拟机再方法区中实现垃圾回收,一般来说这个区域的回收效果不好,尤其是类型的卸载,但是有时候回收又是必要的,它主要回收常量池中废弃的常量和不在使用的类型,Hotspot虚拟机对常量池的回收策略是只要常量池中的常量没有被任何地方引用,就可以回收
    ② 判断一个类型是否可以被回收需要该类的所有实例被回收,加载该类的类加载器被回收,该类对应的java.lang.Class对象没有在任何地方呗引用,无法在任何地方通过反射访问该类的方法所以条件比较苛刻,效果不好
    ③ 在大量使用反射、动态代理、CGLib等字节码框架等需要Java 虚拟机具备类型卸载的能力,保证不会对方法区造成过大的内存压力

七 对象的实例化内存布局与访问定位

  1. 对象的实例化
    在这里插入图片描述
  2. 创建对象的六个步骤
    在这里插入图片描述
  3. 对象的内存布局
    在这里插入图片描述
    整体图示:
    在这里插入图片描述
  4. 对象访问定位

在这里插入图片描述
(1)句柄访问
在这里插入图片描述
(2)直接指针引用访问(Hotspot使用)
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JVM内存模型是Java虚拟机在运行时所使用的内存分配和管理方式。它包括了运行时数据,也就是JVM在内存中划分的不同域,用来存储程序的数据和指令。 JVM运行时数据主要包括以下几个部分: 1. (Heap):用于存储对象实例和数组。是线程共享的域,所有线程共同使用来创建和访问对象。 2. 方法(Method Area):用于存储已加载的类信息、常量、静态变量和编译后的代码等。方法也是线程共享的域,它在内存中占用一块连续的空间。 3. 虚拟机(VM Stack):每个线程在创建时都会分配一个虚拟机,用来存储局部变量和方法调用信息。虚拟机是线程私有的,每个线程都有自己独立的虚拟机。 4. 本地方法(Native Method Stack):与虚拟机类似,用于存储本地方法调用的相关信息。 5. 程序计数器(Program Counter Register):用于存储当前线程执行的字节码指令的地址。 这些不同的运行时数据JVM内存模型中起着不同的作用,可以提供给程序运行所需的各种资源和环境。例如,用于存储对象实例,方法用于存储类信息和静态变量,虚拟机用于存储方法的局部变量和方法调用信息等。 总的来说,JVM内存模型和运行时数据是Java虚拟机在运行时所使用的内存管理和分配方式。它们的不同域有不同的作用,用来存储程序的数据和指令。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [运行时数据JVM内存模型](https://blog.csdn.net/weixin_45659364/article/details/124027073)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [JVM:Java内存模型与运行时数据域](https://blog.csdn.net/m0_71777195/article/details/131655107)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java内存模型与JVM运行时数据详解](https://download.csdn.net/download/weixin_38648037/12745990)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值