• JVM基础03_运行时数据区


    JVM运行时内存布局

    在这里插入图片描述
    其中堆和元数据区(方法区)是多线程共享的,程序计数器、本地方法栈以及虚拟机栈是线程私有的。

    程序计数器(PC寄存器)

    PC寄存器的作用

    用来存储指向下一条指令的地址,也就是即将执行的指令代码。
    同时由于CPU需要不断切换各个线程,经过切换过后,每个线程都要记录当前线程执行到的位置,这个位置是通过PC寄存器进行存储的,这也是PC寄存器必须要线程私有的原因。
    字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

    PC寄存器的特点

    它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    虚拟机栈

    栈是运行是的单位,而堆是存储的单位。------即栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在那里。

    虚拟机栈的作用

    主管Java程序的运行,它保存着方法的局部变量、部分结果,并参与方法的调用和返回。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。它是线程私有的,生命周期与线程一致。

    栈帧

    虚拟机栈中的数据都是以栈帧的格式存在的。在这个线程上正在执行的每个方法都各自对应一个栈桢。栈帧是一个内存区块,是一个数据集,维系着方法执行过程的各种数据信息。
    在一条活动线程中,在某一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧相对应的方法就是当前方法,定义这个方法的类就是当前类。如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶端,成为新的当前帧。
    在这里插入图片描述
    如果当前方法调用了其他方法,方法返回之际,当前栈帧回传会此方法的执行结果给另一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧成为当前栈帧。
    Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。

    栈帧的组成

    • 局部变量表
    • 操作数栈
    • 动态链接(指向运行时常量池的方法引用)
    • 方法返回信息
    • 一些附加信息

    局部变量表

    定义为一个数字数组,主要存储方法参数和定义在方法体内的局部变量。局部变量表的大小是在编译器确定下来的,在方法运行期间是不会改变的。
    局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈桢的销毁,局部变量表也会随之销毁。
    局部变量表中存放编译器可知的八种基本数据类型,引用类型,returnAddress类型的变量。局部变量表中,最基本的存储单位是Slot(变量槽)。32位以内的类型(包括returnAddress)占一个slot,64位的类型(double和long)占用两个slot。
    在这里插入图片描述

    Slot

    JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
    如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会被存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
    栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了期作用域,那么在其作用域之后生命的新的局部变量就可能会复用过期局部变量的槽位,从而起到节省资源的目的。

    静态变量与局部变量的对比

    • 成员变量:在使用前都经历过默认初始化赋值
      • 类变量:linking的prepare阶段:给类变量默认复制。inital阶段:给类变量显式赋值即静态代码块赋值。
      • 实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值。
    • 局部变量:在使用前必须要进行显式的赋值

    补充

    局部变量表中的变量式GC阶段重要的GCRoot。

    本地方法栈(native method stack)

    本地方法(native method)

    本地方法就是一个Java调用非Java代码的接口。它并不是由Java语言实现的,JVM中的方法是由C语言实现的。

    本地方法的作用

    与Java环境外交互
    当我们需要和操作系统进行交互或者是和某些硬件交换信息时,本地方法提供了一个简洁的接口,我们无需去了解Java应用以外的细节就可实现我们想要达到的效果。

    本地方法栈

    本地方法栈用来管理本地方法的调用,是线程私有的。它的具体做法是本地方法栈中登记本地方法,在执行引擎执行时加载本地方法库。
    在这里插入图片描述

    关于堆的概述

    一个JVM实例只存在一个堆内存,堆是Java内存管理的核心区域。堆区在JVM启动时被创建,其空间大小此时确定,它是JVM管理的最大的一块内存空间。
    所有的线程共享Java堆,在这里还可以划分线程私有的缓存区(TLAB)。
    所有的对象实例都在堆中分配内存。在方法结束之后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
    堆,是垃圾回收的重点区域。
    在这里插入图片描述
    以以下代码为例:

    Person person=new Person();
    
    • 1

    关于Person类的相关信息存放在方法区内,person引用变量被存放在虚拟机栈中,被创建出来的Person实例被存放在堆中。
    在这里插入图片描述

    堆的内存划分

    JDK7之前堆被分为:新生代、老年代、永久代(新生代又分为Eden、S0、S1区)
    JDK8之后堆被分为:新生代、老年代、元空间
    在这里插入图片描述
    在这里插入图片描述
    在jdk8之后的堆中,新生代和老年代大小比例一般为1:2。而在新生代中,Eden区和S0、S1区的大小比例为8:1:1。
    在这里插入图片描述
    所有的Java对象都是在Eden区被new出来的。绝大部分的对象销毁都是在新生代发生的。
    在这里插入图片描述

    MInor GC和Major GC和Full GC

    • 部分收集:
      • 新生代收集(Minor GC/Young GC):只是新生代的垃圾回收
      • 老年代收集(Major GC/Old GC):只是老年代的垃圾收集
      • 混合收集(Mixed GC):收集整个新生代和部分老年代的垃圾收集
    • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集

    MInor GC触发机制

    当年轻代中的Eden区空间不足时,会触发Minor GC。需要注意的是S0和S1满不会触发。 Minor GC会暂停用户线程,等GC结束之后,用户线程恢复运行。

    老年代GC(Major GC)触发机制

    指发生在老年代的GC,使不需要的对象从老年代消失。
    若老年代空间不足,会先尝试触发Minor GC。若空间还不足,则会触发Major GC。若Major GC后,内存仍然不足,则会产生OOM。

    Full GC触发机制

    • 调用System.gc()时,系统建议执行Full GC,但是不必然执行。
    • 老年代空间不足
    • 方法区空间不足
    • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
    • 由Eden区、S0区向S1区进行复制时,对象大小大于可用内存,则将该对象转存老年代,且老年代可用内存小于该对象大小。

    Java堆分代的作用

    优化GC性能。新生代中存放使新创建的对象,而大部分对象都是会在新生代中被回收利用。而老年代存放的是经历多次GC仍然存活的对象。对不同特点的对象采用不同的处理方法可极大的提升内存的利用率和性能。

    内存分配策略

    如果对象在出生后并经历一次Minor GC后仍然存活,就会被转移到Survivor区中,年龄被设置为1。在Survivor区中每经过一次Minor GC,年龄就增长一岁。当年龄到达一定程度时,就会被晋升到老年代中。
    特殊情况:大对象直接分配到老年代中。目的是为了避免在新生代中被多次复制影响内存性能。

    TLAB

    堆区是线程共享区域,JVM为每个线程在Eden区中分配一个私有缓存区域。
    使用TLAB可以避免一系列非线程安全问题,同时还能提升内存分配的吞吐量。
    在这里插入图片描述
    注意;一旦对象在TLAB空间分配内存失败,JVM可以通过加锁机制确保数据操作的原子性,从而直接在Eden空间内分配内存。
    在这里插入图片描述

    方法区

    方法区与堆一样,都是线程共享的内存区域。
    在JDK7以前,习惯把方法区叫做永久代。JDK8之后,使用元空间取代了永久代,并且将元空间放在本地内存之中。
    在这里插入图片描述

    方法区功能

    它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存和运行时常量池等。

    类型信息

    对于每个加载的类型(类,接口,枚举,注解),JVM必须在方法区存储以下类型信息:

    • 类型的完整有效名称
    • 直接父类的完整有效名称
    • 类的修饰符
    • 类型直接接口的有序列表

    静态变量

    随着类的加载而加载,被类的所有实例共享。
    需要注意的是被static final修饰的变量在编译时就会被分配。

    运行时常量池与常量池

    方法区内包含运行时常量池;字节码文件中,内部包含常量池。常量池表中包括各种对类型、方法的符号引用。
    由于字节码需要数据支持,而这些数据往往比较大,无法直接存到字节码里。则通过将数据存放到常量池里,字节码中存放指向常量池的引用。

    常量池中有什么

    • 数量值
    • 字符串值
    • 类引用
    • 字段引用
    • 方法引用
      常量池,可以看作是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等类型。

    运行时常量池

    常量池表中的内容会在类加载后存放到方法区的运行时常量池中。运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址,而是转换为真实地址。

    方法区的改变

    JDK1.6之前,有永久代,静态变量存放在永久代中
    JDK1.7,由永久代,但是已经逐步”去永久代“,字符串常量池、静态变量移除,保存在堆中
    JDK1.8及以后,无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中

    方法区的垃圾回收

    对常量的回收策略:常量池中的常量没有被任何地方所引用
    对类型的回收策略比如满足如下三个条件:

    • 该类的所有实例已经被回收
    • 该类的类加载器已经被回收
    • 该类的类对象没有在任何地方被引用,无法通过反射访问该类的方法
      满足以上三个条件可以进行回收,但并非一定回收。
  • 相关阅读:
    所有 Windows 用户请注意:该高危零日漏洞已被利用 7 周
    PTE-精听学习(一)
    解读以下产生m个k位数的验证码的代码
    元宇宙012 | 世界人工智能大会之元宇宙论坛:技术篇
    端午节节日PPT模板
    位运算的一些经典题目
    B2B还是F2B2C,谁是品牌商的未来?
    二、python Django路由与请求(request的参数)
    数据库原理(关系型数据库基本理论)——(
    【机器学习】让大模型变得更聪明
  • 原文地址:https://blog.csdn.net/weixin_43962203/article/details/126325260