jvm运行时数据区

简单了解一下运行时数据区在jvm中的位置。对于运行时数据区

79b14de8376f49e3bffc18e756e1c57e_tplv-k3u1fbpfcp-watermark

  • 方法区、堆空间是属于线程共享的
  • 程序计数器、栈、本地方法栈是属于线程私有的,每个线程都有一份

程序计数器(Program Counter Register)

用于存储指向下一条jvm指令的地址,由执行引擎读取下一条指令。

  • 它是一块很小的内存空间,几乎可以忽略不记,也是运行速度最快的存储区域。
  • 每个线程都有一份,线程私有。
  • 如果当前是native方法,则是未指定值(undefined)

虚拟机栈

1731ed4e23bc4d0b8871d2cb5441a560_tplv-k3u1fbpfcp-watermark

  • java虚拟机栈,每个线程创建的时候都会创建一个虚拟机栈,其内部保存了一个个栈针,每个栈针对应一次java方法调用。
  • 随线程的创建而创建,随线程的消亡而消亡
  • jvm直接队java栈的操作只有两个,就是“压栈”和“出栈”,每个方法执行,伴随着进栈,执行结束出栈
  • 在一个活动的线程中,在一个时间点上,只有一个活动的栈针,这个活动栈针是栈顶的栈针,叫做当前栈针,该栈针对应的方法叫当前方法,定义该方法的类叫当前类
  • 不存在垃圾回收问题

栈针内部结构

4bec251347874e5989125c7360e5ac64_tplv-k3u1fbpfcp-watermark

局部变量表

定义为一个数组,主要用于存储方法参数定义内的局部变量。包括:基本数据类型,对象引用,以及returnAddress

image-20221231230930484

  • 局部变量表的基本存储单元是Slot(变量槽),占32为。32位以下的类型占一个变量槽(包括returnAddress),64位类型占两个变量槽

  • byte、short、char、boolean在存储前别转换为int

  • long 和 double 则占用两个slot

  • 如果当前方法是一个构造方法或则实例方法,那么this会被存储到index为0的slot中

  • returnAddress类型:值是指向 Java 虚拟机指令操作码的指针

    • 局部变量表所需的容量大小在编译期间就已经确定下来了。并保存在方法的Code属性的maximum local variables数据项中。大小一旦确定下来,不会被改变
    • 局部变量表的slot是可以重复利用的,如果一个局部变量过了作用域以后,声明新的局部变量可以复用过期slot

操作数栈

每一个独立的栈针除了包含局部变量表以外,还包含一个后进先出的操作数栈,可以被称为表示式栈。在方法执行过程中,会根据字节码指令,向栈中写入数据或提取数据。

image-20221231234340850

  • 主要用于存放计算的中间结果,同时作为计算过程中变量临时存储空间
    • 有些字节码指令会从局部变量表中读取值,然后压到栈里
    • 有些则会从栈中取出数据进行计算,然后把结果压入栈
  • 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈的操作数栈中
  • 操作数栈和局部变量表一样,在编译期间就已经定义好,保存在方法的Code属性的max_stack的值

动态链接

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

方法返回地址

存放调用该方法的pc寄存器的值。方法执行结束,栈针出栈以后。程序计数器需要知道该执行的下一条指令是什么。

  • 正常退出,调用者的pc计数器值作为返回地址,即调用该方法的指令的下一条指令地址
  • 异常退出,返回地址通过异常表来确认

本地方法栈

本地方法栈主要用于管理本地方法的调用,即native修饰方法

当某个线程调用一个本地方法时,它就会进入一个全新的不受虚拟机限制的环境。它和虚拟机拥有同样的权限。

  • 本地方法可以通过本地方法接口来访问虚拟机内部的云运行时数据区
  • 甚至可以直接使用本地处理器中的寄存器
  • 直接从本地内存的堆中分配任意数量的内存

一个java实例只有一个堆,且在jvm启动时被创建,堆内存可以动态变化。

在《java虚拟机规范》这样描述:所有的对象实例以及数组都应当在运行时分配在堆上。所以堆是用来存放对象的。不过现在可以通过逃逸分析,把一些对象分配到栈中。减少垃圾回收。

image-20230101012421367

  • 堆在物理上可以不连续,但是在逻辑上是连续的
  • 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向堆中的对象或数组
  • 方法结束以后,堆中的对象不会马上被移除,仅仅在垃圾回收的时候才会被移除
  • 线程共享,存在线程安全问题

TLAB

主要作用是为了避免多个线程操作同一个地址,加锁导致速度变慢。

  • 在伊甸园区进行继续划分,jvm为每个线程分配了一个私有的缓存区域
  • 因为其大小仅占eden空间1%,所以不是所有的对象都能在TLAB中成功分配,但是jvm确实是将TLAB作为内存分配的首选
  • 如果对象在TLAB中分配失败,则在eden中年分配内存

方法区

方法区主要存储:类型信息,运行时常量池,静态变量,JIT代码缓存,域信息,方法信息

类型信息:全类名、直接父类的全类名、修饰符、直接接口列表

域(Field)信息:域名、域类型、域修饰符

方法信息:方法名称、方法放回值、方法参数数量和类型、方法修饰符、方法字节码、异常表

常量池vs运行时常量池

常量池在类的字节码文件中,当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池

  • 常量池:存放各种字面量和对类型、域和方法的符号引用

  • 运行时常量池是直接引用

  • 运行时常量池相对于常量池有动态性

Hotspot中方法区的演进

  1. jdk1.6及以前:有永久代,静态变量存放在永久代中

  2. jdk1.7:有永久代,但是逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中

    • jdk7将StringTable放在堆空间中,因为永久代回收效率低,在full gc才会触发。导致StringTable回收效率低。而开发的时候有大量字符串被创建,导致永久代内存不足
  3. jdk1.8及以后:无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆中

image-20230101161630014

image-20230101161809848

image-20230101161912330