编辑

【1.1】JVM运行时数据区

2019-12-11 509 2 ● 深入理解JVM Hoji

一、JVM运行时数据区

JVM运行时数据区也叫JVM运行时内存结构,JVM在执行Java程序时中会把它管理的内存划分为若干个不同的数据区域,主要包括:虚拟机栈、本地方法栈、PC寄存器、方法区、堆区,这些数据区域中大致可划分为:线程独享、线程共享 --->

线程共享数据区:方法区、堆区。随着虚拟机启动而创建,随着虚拟机退出而销毁,且为为进程的所有子线程共享
线程独享数据区:虚拟机栈、本地方法栈、PC寄存器。这些数据区与线程一 一对应,与线程对应的数据区域会随着线程开始和结束而创建和销毁。线程独占一份

The following figure shows the runtime data area in jvm in the case of(在·····的情况下) two threads:

Several memory areas are described in detail below --->

(1)线程私有数据区

程序寄存器

程序计数器是一块较小的内存空间,可看作当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需依赖该计数器完成。此内存区域是JVM规范中没有规定任何OutOfMemoryError情况的唯一区域。

由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响。

在任意时刻,一条 JVM 线程只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法(Current Method)。

PC寄存器中存储当前method经过JVM汇编后字节码指令的地址

  • method非native方法:PC 寄存器就保存 JVM 正在执行的字节码指令的地址
  • method是native方法,PC 寄存器的值为空(undefined)

Java虚拟机栈

Java虚拟机栈中的元素叫做栈帧(Stack Frame),线程在调用java方法时,会为每个方法创建一个栈帧,来存储局部变量表、操作栈、动态链接、方法出口等信息

每个方法被调用和完成的过程,都对应着一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程相同。

The following figure shows the internal structure of the stack frame:

**栈帧(Stack Frame)**是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。

Stack Frame随着方法调用而创建,随着方法结束而销毁【无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束】,方法返回后,当前栈帧就随之被丢弃,前一栈帧就重新成为当前栈帧。

Stack Frame的存储空间分配在 JVM 中,每一个栈帧都有自己的局部变量表(Local Variables)、操作数栈(OperandStack)和指向当前方法所属的类的运行时常量池的引用

(1)局部变量表

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,JVM使用局部变量表完成参数值到参数变量列表的传递过程。局部变量列表的变量数量在编译时期就已经确定【即在Java程序被编译成Class文件时,在方法的Code属性的max_locals数据项中确定该方法所需要分配的最大局部变量表的容量】

因此,栈帧容量的大小仅取决于 JVM 的实现和方法调用时可被分配的内存。只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈帧(Current Frame),这个栈帧对应的方法就被称为是当前方法(Current Method),定义这个方法的类就称作当前类 (Current Class)。

局部变量表的容量以变量槽(Slot)为最小单位,32位虚拟机中一个Slot可以存放一个32位以内的数据类型(boolean、byte、char、short、int、float、reference 和 returnAddress八种)

returnAddress 类型是为字节码指令 jsr、jsr_w 和 ret 服务的,它指向一条字节码指令的地址

(2)操作数栈

和局部变量表一样,操作数栈的容量也是在编译期(将源码编译成class文件)确定,其深度属性存储在方法的 Code 属性供栈帧使用。但与前者不同的是,它不是通过索引来访问,而是通过标准的栈操作——压栈和出栈来访问变量。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。

JVM的底层字节码指令集是基于类型的,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,JVM会建立一个供记录出栈、入栈的操作用的操作数栈

JVM栈运行原理:栈中的数据都是以栈帧的格式存在,栈帧是一个内存区块,是一个有关方法和运行期数据的数据集。

当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中, A方法又调用了B方法,于是产生栈帧F2也被压入栈, B方法又调用了C方法,于是产生栈帧F3也被压入栈……
方法依次执行完毕后,后进的先被弹出......F3栈帧 ---> F2栈帧 ---> F1栈帧。

(3)动态链接

虚拟机运行的时候,运行时常量池会保存大量的符号引用,这些符号引用可以看成每个方法的间接引用。如果代表栈帧A的方法想调用代表栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,但是因为符号引用并不是直接指向代表B方法的内存位置,所以在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用才可以访问到真正的方法。

如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析,如果是在运行期间转换为直接引用,那么这种转换就成为动态链接

每一个栈帧内容都有一个指向运行期常量池的引用来支持当前方法实现动态链接(Dynamic Linking)

(4)方法出口

若方法正常返回,当前栈帧承担着恢复调用者状态的职责,调用者状态包括调用者的局部变量表,操作数栈和被正确添加过来表示执行了该方法调用指令的程序计算器(PC)等。使得使得调用者调用的方法返回,且将返回值推入调用者的操作数栈后继续正常执行。

若方法在执行期中,某些指令导致JVM抛出了没有方法处理的exception,或者在执行期遇到了Athrow字节码指令显示抛出该方法没有捕获的异常,那么方法异常调用完毕,方法的返回值返回给调用者。

本地方法栈

JVM实现者在实现JVM时,用传统的栈(C Stack)来支持本地方法的执行的一个容器。【本地方法指用非Java语言实现的方法】

当JVM使用其他语言实现指令集解析器时,会使用到本地方法栈

本地方法栈会在线程创建的时候按线程分配,用来存储线程调用本地方法中的局部变量表,操作栈等信息.

咋一看,本地方法栈与java虚拟机栈很相似,只不过服务的对象不一样,sun hotspot 虚拟机把TA们合二为一,本地方法栈区也会抛出StackOverFlowError和OutOfMemeoryError异常

(2)线程共享数据区

Heap 随 JVM 启动而被创建,Heap 是垃圾收集器管理的主要区域,因此很多时候也被称为GC堆。 Heap 内部存储了被自动内存管理系统所管理的各种对象,类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行

Java Heap的容量可以是固定大小,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。堆内存分为三部分 ---> 为什么叫伊甸区?据说,伊甸是犹太教、基督教圣经故事中人类始祖居住的乐园,因此这里自然也就比较热闹,每一个"类"出生于此也说得过去!

新生区:是类的诞生、成长、消亡的区域,一个类在这里产生、应用、最后被垃圾回收器收集,结束生命。

新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个:0区 (Survivor 0 space)和1区 (Survivor 1 space)

1、当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园进行垃圾回收(Minor GC),将伊甸园中的剩余对象移动到幸存0区。

2、若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1去也满了呢?再移动到养老区。

3、若养老区也满了,那么这个时候将产生Major GC (FullGCC),进行养老区的内存清理。
4、若养老区执行Full GC 之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

(1) JVM的堆内存设置不够,可以通过参数-Xms、-Xmx来调整
(2) 代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

==养老区==:用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。

永久区:永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据不会被垃圾回收器回收, JVM 关闭才会释放此区域所占用的内存。

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。 原因有二:

(1) 程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。 (2) 大量动态反射生成的类不断被加载,最终导致Perm区被占满。

永久代区域的演变: (1) Jdk1.6及之前 ----> 常量池分配在永久代 ;

(2) Jdk1.7 ----> 有,但已经逐步“去永久代” ;

(3) Jdk1.8及之后 ----> 无 (java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8);

方法区

Method Area 指可供各条线程共享的运行时的内存区域,它**存储每一个类的结构信息,如:类的结构信息、字段信息、方法信息、其他信息

除了常量以外的所有类(静态)变量 一个指向ClassLoader的指针 一个指向Class对象的指针 常量池

运行时常量池存放编译期生成的各种字面量和符号引用 (包括实际的常量(string,integer和floating,point常量)和对类型,域和方法的符号引用),池中的数据项象数组项一样,是通过索引访问的。但Java语言并不要求常量只有在编译器才能产生。比如在运行期String.intern会把新的常量放入池中。

方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集。

方法区的容量可以是固定大小的,也可随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。


持续更新... The following figure shows the internal structure of the Method Area:

喜欢的话可以关注一下呢

Hoji的CSDN(在这里随意任性地创作,一首诗、一幅画、一篇文章.... 在这里你可以get到但不仅限算法、Spring, SpringBoot等后端技术、MySql,Oracle数据库探索....,其中更不乏各种框架教程、学习方法..)

CSDN博客地址:https://blog.csdn.net/qq_43539599

个人博客:https://www.hoji.site

简书: https://www.jianshu.com/u/2d58d2f776a3

掘金:https://juejin.im/user/5d16f05f6fb9a07ee85c3eb6

上一篇

【1.0】JVM体系结构

下一篇

【1.0】JVM体系结构

倘若小文于你有益,欢迎
  • 如果您的提问博主没能及时回复,通过分享文章获得援助,何尝不是一种查缺补漏的好做法
  • 版权声明:本文为博主原创文章,遵循CC 4.0 BY版权协议
  • 文章转载:请在文末添加原文章地址,这也是尊重他人劳动成果的一点体现,谢谢您的配合!
  • 评论信息 (注:评论收到回复后,会以邮箱的方式提醒您;您的邮箱不会显示到页面中)

    验证码信息 看不清?点击图片进行切换!
    精彩随处可见 更多精彩内容
    作者: 浏览 61 评论 1 赞 1 2020-01-15
    作者: 浏览 62 评论 1 赞 1 2019-11-29
    作者: 浏览 83 评论 1 赞 1 2019-11-29
    作者: 浏览 54 评论 1 赞 1 2019-11-29
    作者: 浏览 52 评论 1 赞 1 2019-11-29
    目录





    制造,不断超越

    Hi,这是您第 次访问页面,上一次是
    (本站与百度相同,用Cookie统计站点的访问次数,与首页显示的总访问量完全不同)

    Copyright © 2019 - 2020 Created by Hoji Pan

    粤ICP备19138496号