JVM内存分区笔记

JVM内存分区笔记,第1张

JVM内存分区笔记

JVM内存分区笔记
  • JVM内存分区
    • 线程共享和线程隔离
    • 程序计数器
    • 虚拟机栈(Java方法栈)
      • 栈帧
        • 局部变量表(Local Variable)
        • *** 作数栈(Operand Stack)
        • 动态连接(Dynamic linking)
        • 返回地址(Return Address)
    • 本地方法栈
    • 方法区
      • 方法区概念
      • 方法区-类型信息
      • 常量池(Constant Pool)
      • 运行时常量池

内存管理是JVM中的重要命题,当JVM对内存进行管理后降低了开发者的门槛,提高了程序的可维护性。
注:内存区域和内存模型并不是一个概念,内存模型传送门。

JVM内存分区

*** 作系统和JVM都进行了内存区域的划分,将连续的内存,抽象为不同作用的内存区域。Java的内存划分存在于更加上层的用户空间内的封装。JVM将内存划分为五个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区。

线程共享和线程隔离

线程共享:被所有线程共享的内存区域。包括 堆和方法区。
线程隔离:仅被当前线程独占的内存区域。包括 程序计数器、虚拟机栈、本地方法栈。
当编程时,需要判断当前读写的数据是存在于哪类内存的,若是存在共享内存区域,则需要考虑是否存在线程安全问题。若是线程隔离区域则不需考虑。

程序计数器

硬件层面:在硬件层面程序计数器是一种寄存器,用来存储指令地址,提供给处理器执行。
软件层面:在JVM软件层面程序计数器作用一样,用来存储字节码的指令地址,提供给执行引擎去取址执行。
这两种程序计数器分别存在于硬件和软件中,实现方式不一样,但设计思想类似。

虚拟机栈(Java方法栈)

程序执行的过程对应着方法的调用,而方法的调用对应着栈帧的入栈出栈 *** 作。

栈帧

栈帧中主要存在四种结构:局部变量表、 *** 作数栈、动态连接、返回地址。

局部变量表(Local Variable)

栈帧通过方法源码来生成的,当调用该方法时,传入方法的参数类型、 局部变量的类型在源码中都是已经确定的。既然数量与类型都可以确定那么需要占用的存储空间也能确定。那么该如何进行存储呢,在 局部变量表中通过4字节的slot来存储。

*** 作数栈(Operand Stack)

OS层面: *** 作数是计算机指令的一部分。
栈帧 *** 作数栈:JVM用来存储 *** 作数的栈,这个 *** 作数大部分是方法内的变量。 *** 作数栈一可以存储 *** 作数(变量以及中间结果),二能够方便指令顺序读取 *** 作数。虚拟机执行引擎在执行字节码指令的时候,会通过当前指令类型在 *** 作数栈中取出栈顶 *** 作数进行计算,然后将计算结果入栈,继续执行后续指令。

多个栈帧执行如下图:

动态连接(Dynamic linking)

OOP的主要特性是多态,而Java中的多态就是通过栈帧中的动态连接实现的。
概念:每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic linking)。

Java类加载过程步骤“连接”中,JVM会将class对象(存储在方法区的运行时常量池中)中的部分符号引用(静态解析),替换为直接引用。 有些方法由于多态的存在,JVM无法在类加载阶段确定被调用的具体类型,只能在运行时,真正产生调用时,根据实际的类型进行连接,即动态连接。

返回地址(Return Address)

当出现以下两种情况,当前方法就会返回:

  • 正常情况:方法正常执行完成,然后返回。若方法B正常返回就代表栈帧B执行完成,此时调用栈帧A,由于栈帧B是被栈帧A调用的,栈帧B在退出虚拟机时需要把返回信息共享给栈帧A的 *** 作数栈,如上图。同时需要修改程序计数器的值,让程序能够继续执行下去。
  • 异常情况:方法执行期间遇到异常情况,所以返回。若方法B异常返回,需要通过额外的异常处理器表来进行处理,其他和程序计数器相关的逻辑和正常情况类似。
public void funA(){
	funcB();
}
public void funB(){}
本地方法栈

本地方法:指的是非Java语言实现的函数,往往是由c/c++编写,和 *** 作系统相关性较强的底层函数(比如CAS)。
本地方法栈:就是为了支持本地方法的调用逻辑。

方法区 方法区概念

方法区是虚拟机规范中的抽象概念。其中规定了虚拟机必须有方法区这样的机构,但具体的实现可以灵活发挥。
具体实现:比如HotSpot虚拟机,在JDK8以前HotSpot的开发者将面向堆的分代设计复用到了方法区上,使用永久代来作为HotSpot上的方法区的实现,但是后来发现这种设计并不好,所以从JDK8开始借鉴了JRockit的设计思路,使用了元空间来代替永久代作为新的实现方式。

方法区是抽象概念,永久代/元空间是具体实现。

方法区-类型信息

类型信息:类加载的加载阶段,虚拟机会读取被编译后的class文件生成class对象,class对象存储了一些类型信息,这些信息就是存储在方法区内,这些信息有类的签名、属性、方法等。

常量池(Constant Pool)

作用:大部分类之间都存在相互调用关系,通过指针(即类加载中的符号引用)实现,在类A中存放类B的指针(符号引用)起到链接作用。
在虚拟机加载字节码文件的时候,首先加载的就是一些静态的符号引用,然后再类加载连接阶段(程序运行时),将符号引用转化为直接引用。符号引用本身是从字节码中加载进来的,那么字节码就是通过常量池(Constant Pool)来存储关系这些符号引用及其他静态引用。

常量池像一张链接表,而一些源码中定义的常量字面量则存储在运行时常量池中。

运行时常量池

运行时常量池存储两大类数据:

  • 编译期间产生的,主要是字节码中定义的静态信息。比如由字节码生成的class对象 (上文Constant Pool就包含在内)。由字节码生成的字面量,就是我们在编写代码时所定义的常量自变量。
  • 运行期间产生的,这部分比较灵活,虚拟机开发者可以将必要的信息都放进去。比如运行时会将一部分的符号引用转化为直接引用,那么这些直接引用就可以存储进来。比如字符串常量池。

堆,从JVM的规范上并没有进行严格意义上的分区,只是从不同角度去看,可以进行不同的逻辑划分。最主流最常见得是从垃圾回收的角度对堆内存进行划分。

学习视频链接:https://www.bilibili.com/video/BV1uQ4y1y7T2?spm_id_from=333.999.0.0

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5481660.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存