JVM之栈和堆运行时内存深度剖析

news2024/11/29 22:39:58

运行时内存篇

程序计数器

也是线程私有的,不共享,因为cpu时间片轮换的缘故,所以需要记录上次未执行完的线程执行到那条字节码指令了,所以每个线程需要记录当前执行的命令的内存指针,以方便线程再次得到执行的时候按照正确的顺序执行

JVM之栈(虚拟机栈)

基础知识

会gc吗

不会进行gc,因为线程执行结束后整个栈就被回收掉了,不会进入到gc的阶段

可能抛出来的异常

jvm虚拟机规范允许Java的栈大小是动态的或者固定不变的

  • 如果是采用固定大小的jvm,如果线程要求分配的栈空间超过了设置的最大容量,会抛出StackOverflowError异常
  • 如果是动态扩展的,在jvm发现已经没有了可用的内存了,会抛出去OutOfMemoryError异常

如何设置大小

-Xss size (即:-XX:ThreadStackSize)设置栈的大小(一般默认为512k-1024k,取决于操作系统),现在基本都是默认为1024k

栈帧

方法与栈帧之间的关系

  • 在这个线程上正在执行的每个方法都有各自对应一个栈帧(stack frame)
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧,只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧,与当前栈帧对应的方法就是当前方法,定义这个方法的类就是当前类
    栈帧方法示意图

栈的FILO原则

JVM直接对Java栈的操作只有两个(遵循着先进后出的原则)

  • 每个方法执行,伴随着进栈
  • 执行结束后的出栈工作

栈帧总体的结构

每个栈帧中存储着:

  • 局部变量表(Local Variables)
  • 操作数栈(Operand Stack 也称为表达式栈)
  • 动态链接(Dynamic Linking 或指向运行时常量池的方法引用)
  • 方法返回地址(Return Address 或方法正常退出或者异常退出的定义)
  • 一些附加信息
    栈帧的内部结构

局部变量表

  • 局部变量表也被称之为局部变量数组或本地变量表
  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各种基本数据类型(8种),对象引用,以及returnAddress类型
  • 局部变量表所需要的容量大小是在编译期间确定下来的,并保存在方法的Code属性的maximum local variables数据项中,在方法的运行期间是不会改变局部变量表的大小的
  • 方法嵌套调用的次数由栈的大小决定.一般来说,栈越大,方法嵌套调用次数越多.对一个函数而言,局部变量表越膨胀,他的栈帧就越大,会占用更多的栈空间,导致其嵌套调用次数就会减少
  • 局部变量表中的变量只在当前方法调用中有效,在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随着销毁.

非静态的方法会默认有一个this指针指代当前对象 他会占用局部变量表数组的第一个位置
double和long类型会占用局部变量表的两个slot
slot也是会被复用的
局部变量是不会被对象初始化赋值的

与GC Roots的关系

局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或者简介引用的对象都不会被回收

操作数栈

  • 我们说Java虚拟机栈的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈
  • 每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的(LIFO)的操作数栈,也可以称之为表达式栈
  • 操作数栈是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的
  • 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值
  • 栈中的任何一个元素都可以是任意的Java数据类型
  • 32bit的类型占用一个栈单位深度
  • 64bit的类型占用两个栈单位深度
  • 操作数栈在方法的执行过程中,根据字节码指令,并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)操作,往栈中写入数据或者提取数据来完成一次数据访问
  • 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,使用他们后再把结构压入栈,比如执行复制,交换和求和等操作
  • 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并用return指令返回出去(所以说return是从操作数栈中返回出去的)
栈顶缓存技术

基于栈式架构的虚拟机所使用的的零地址指令更加的紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这同时也意味着需要更多的指令分派次数和内存的读写此时.
由于操作数是存储在内存中的,因此频繁的执行内存读写操作必然会影响cpu的执行速度,为了解决这个问题,Hotspot虚拟机的设计者提出了栈顶缓存(Tos)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此来降低对内存的读写次数,提升执行引擎的执行效率

动态链接

指向运行时常量池的方法引用

  • 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用.包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接,比如说:invokedynamic指令
  • 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里.比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用.
方法的调用
  • 静态链接:当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译器可知,切运行期保持不变,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接.
  • 动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期间将调用方法的符号引用转换为直接饮用,由于这种引用具有动态性,因此称之为动态链接

对应的绑定机制为:早期绑定和晚期绑定.绑定是一个字段,方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次.

方法的调用指令
  • invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态,这也是Java语言中最常见的方法分派方式
  • invokeinterface指令用于调用接口方法,他会在运行时搜索由特定对象所实现的这个接口方法,并找出合适的方法进行调用
  • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造器),私有方法和父类方法,这些方法都是静态类型绑定的,不会在调用时进行动态派发
  • invokestatic指令用于调用命名类中的类方法(static方法),这是静态绑定的
  • invokedynamic调用动态绑定的方法,这个是JDK1.7后新加入的指令,用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法.前面四条调用指令分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的(他是为了动态语言设计的,不过在Java8以后的lambda也会生成一些这个字节码出来,是需要有一个引导器用方法句柄去确认类型的)

方法返回地址

  • 存放调用该方法的pc寄存器的值
  • 一个方法的结束有两种方式
  • 正常执行完成退出
  • 出现未处理的异常,非正常退出
  • 无论通过哪种方式退出,在方法退出后都返回到该方法调用的位置.方法正常退出时,调用者的pc计数器的值作为返回地址,即调用方法的指令下一条指令的地址.而通过异常退出的,返回地址要通过异常表来确定,栈帧中一般不会保存这部分信息

本地方法栈

  • Java 虚拟机栈用于管理Java 方法的调用,而本地方法栈用于管理本地方法的调用。
  • 本地方法是使用C/c++语言实现的。
  • 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

权限很高,可以随意操作jvm中的东西

JVM之堆

核心概述

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域,也是GC的重点区域。
  • Java堆区在jvm启动的时候即被创建,其空间大小也就确定了,是jvm管理的最大的一块内存空间
  • 堆内存的大小是可以调节的
  • 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
  • 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除
  • Java虚拟机规范描述是所有的对象实例以及数组都应该分配在堆上,不过现在也出来了新的栈上分配的概念(引用对象全在堆上也并不那么绝对了,但是栈上分配也不是直接把对象放栈上了 只是一个标量)

堆的内存结构

结构图示

jdk7堆内存模型
jdk8堆内存模型

新生代与老年代

  • 存储在JVM中的Java对象可以被划分为两类:
  • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都十分迅速
  • 另外一类是对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致
  • Java堆区进一步细分的话,可以划分为年轻代(youngGen)和老年代(oldGen)
  • 其中年轻代又可以划分为Eden空间,Survivor0空间和Survivor1空间(有时也叫作form区和to区)
  • 几乎所有的Java对象都是在Eden区被new出来的(大对象的可能会直接进入老年代)
  • 绝大部分的Java对象的销毁都在新生代进行了

TLAB概念

  • 从内存模型而不是垃圾回收的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,他包含在Eden空间内
  • 默认就是开启的(这样在每个线程新建引用对象需要在堆上分配空间时不需要加锁,提升了效率)
  • 在程序中,开发人员可以通过选项“-XX:UseTLAB”设置是否开启TLAB空间
  • 默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,当然我们可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。
TLAB的补充说明
  • 尽管不是所有的对象实例都能够在TLAB中成功分配内存,但是JVM确实是将TLAB作为内存分配的首选
  • 在程序中,开发人员可以通过选项 -XX +/-UseTLAB 设置是否开启TLAB空间
  • 默认情况下TLAB的空间非常小,仅占用整个Eden区空间的1%,当然我们可以通过选项 -XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小.
  • 一旦对象在TLAB空间分配内存失败,JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存

分代思想

因为在Java的世界中,大部分的对象的都是临时对象,随着方法的执行完毕就应该被垃圾回收掉,如果不分代的话会导致整个堆上的频繁垃圾回收,会很影响程序的执行效率,所以通过分代把这些需要频繁回收掉的和稳定的长期使用对象分割开,是非常聪明的做法.

堆对象分配

总结的金句

  • 针对幸存者s0,s1区的总结:复制之后有交换,谁空谁是to
  • 关于垃圾回收
  • 频繁在新生区收集
  • 很少在老年代收集
  • 几乎不在永久代(元空间)收集

分配策略

  • 优先分配到Eden
  • 大对象直接分配到老年代
  • 尽量避免程序中出现过多的大对象
  • 长期存活的对象分配到老年代
  • 空间分配担保
  • 动态对象年龄判断
  • 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入到老年代,无须等到MaxTenuringThreshold中要求的年龄
空间分配担保

-XX:HandlePromotionFailure
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间:

  • 如果大于,则此次Minor GC是安全的
  • 如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。
  • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

在JDK 6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,观察OpenJDK中的源码变化,虽然源码中还定义了HandlePromotionFailure参数,但是在代码中已经不会再使用它。JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC.

分配过程示意图

分配过程示意图

堆的参数设置

重要设置指令
  • -Xms:初始内存 (默认为物理内存的1/64)
  • -Xmx:最大内存(默认为物理内存的1/4)
  • -XX:NewRatio:配置新生代与老年代在堆结构的占比。赋的值即为老年代的占比,剩下的1给新生代,默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3,-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
  • -Xmn:设置新生代的大小。(初始值及最大值)通常用默认即可。
  • -XX:SurvivorRatio:设置Eden区与form和to区的大小比例
  • 在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1
  • 开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8
  • windows下显示设置才会是8:1:1,否则居然是6:1:1
  • -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄。超过此值,仍未被回收的话,则进入老年代,默认值为15
  • -XX:+PrintGCDetails:输出详细的GC处理日志
注意事项
  • 一旦堆区中的内存大小超过"-Xmx"所指定的最大内存时,将会抛出OutOfMemoryError异常
  • 通常会将-Xms -Xmx两个参数设置为相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能
  • 堆默认最大值的计算方式:如果物理内存小于192m,那么堆最大值为物理内存的一半,如果物理内存大于等于1g,那么堆最大值为物理内存的1/4
  • 堆默认最小值计算方式:最少不得少于8m,如果物理内存大于等于1g,那么默认值为物理内存的1/64.最小堆内存在jvm启动的时候就会被初始化

栈与堆总结

栈和堆的区别

  • 角度一 栈不会进行垃圾回收.而堆是垃圾回收的重点区域,并且栈抛出的内存异常与堆不同
  • 角度二: 栈的执行效率非常高,而堆是存储空间非常大
  • 角度三: 栈是属于栈结构,里面由一组组栈帧构成.正好符合方法的执行顺序 堆物理上是可以不连续的,属于一大块的内存空间
  • 角度四 :栈管运行 堆管存储,基本类型是能编译期确定好大小的,是可预期的,并且为了执行效率更高,所以直接存储在栈上

方法区

  • jdk1.6及之前:有永久代(permanent generation)
  • jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中
  • jdk1.8及之后: 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池仍在堆
  • -XX:MetaspaceSize
  • -XX:MaxMetaspaceSize:默认值是-1,即不限制

StringTable

  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
  • 字符串常量池中是不会存储相同内容的字符串的

变化

  • Java 6及以前,字符串常量池存放在永久代。
  • Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
  • Java 8 中,字符串常量仍然在堆。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/589783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pinpoint安装部署(相关博客合集)

pinpoint安装部署 说明一、PinPoint介绍及工作原理1.1 确定部署的组件及服务 二、相关组件版本兼容情况2.1 确定版本 三、部署3.1 HBASE3.2 agent 说明 本博客写在搭建PinPoint之前,主要是用来记录查阅的相关博客资料,等到动手搭建完再更新实际部署操作…

医学可视化应用简介

VolView VolView是一款为临床专业人员开发的开源放射学查看器。使用VolView,可以通过交互式电影体积渲染对数据有更深入的视觉理解,并轻松地以3D形式可视化DICOM数据。由于VolView在浏览器中运行,不需要安装软件,数据也可以安全地…

SOLIDWORKS技巧培训-绘制零件滚花的两种方法

最近常有朋友咨询SolidWorks零件如何做滚花的效果,下面给大家整理了绘制零件滚花的一个培训教程: 作为机械设计师,滚花应该都不陌生,真要说起来,滚花绘制其实也不算难,跟着我们一步一步来,应该…

DATAX数据同步工具

1:DATAX概览 ​ DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 1.1:DataX3.0框架设计 DataX本身作为离线数据同步框架&am…

NR RLC(四) AM mode

欢迎关注同名微信公众号“modem协议笔记”。 这篇是RLC AM mode的相关内容,RLC的ARQ是一种重传机制,其实NR三个不同的协议层MAC(HARQ)、 RLC(ARQ)和PDCP都有重传功能。为什么三层协议都需要具备重传功能?早期也有思考过这个问题,…

Sui与KuCoin Labs联合推出夏季黑客松,奖池高达28.5万美金!

Sui与KuCoin Labs联合推出的夏季黑客松于2023年5月29日正式开启,本次黑客松将为参赛者提供与Sui基金会团队、KuCoin Labs和其他黑客松合作伙伴及支持者进行技术交流和项目演示的机会。比赛涵盖四个主要赛道,总奖池高达28.5万美金,最终比赛结果…

【目标检测】yolov5模型详解

文章目录 一、Yolov5网络结构1.1 Input1.2 Backbone1.2.1 Conv模块1.2.2 C3模块1.2.3 SPPF模块 1.3 Neck1.4 Head1.4.1 head1.4.2 目标框回归1.4.3 目标的建立1.4.4 NMS(Non-Maximum Suppression) 二、损失函数2.1 分类损失2.2 置信度损失2.3 定位损失 L…

在Linux系统实现服务器端和客户端的套接字通信

目录 一.创建一个socket文件夹用来存放编写的服务器端和客户端程序 二.编写服务器端代码 三.编写客户端代码 四.编译c语言程序 五.断开连接 六.可能涉及到的一些没接触过的知识点 一.创建一个socket文件夹用来存放编写的服务器端和客户端程序 (我系统里的文件…

Revit楼板问题:楼板连接处以及楼板开洞,一键开洞

在我们做楼梯时,楼梯与楼板处的连接处理不是那么符合实际,会出现一些问题,如下图,这样的连接会导致楼梯配筋时钢筋外露。 我们来学习如何调节楼板与楼板连接处的高度,选中楼梯,点击“编辑楼梯”在所需要更改…

STM32堆栈方面知识点

最近弄json,发现经常的堆溢出,然后找问题。因为对STM32堆栈问题没有深刻认识,就花时间好好研究下了堆栈并且做了验证 1.栈地址区间确定 首先找到启动文件,我的启动文件在startup_stm32f40xx.s,一般的启动文件也都在s…

Linux之命令搜索

目录 Linux之命令搜索 Whereis命令 定义 基本信息 举例 which命令 定义 与whereis命令的区别 基本信息 举例 locate 命令 定义 优点 缺点 基本信息 案例 Linux之命令搜索 Whereis命令 定义 whereis --- 搜索系统命令的命令(像绕口令一样&#xff09…

Revit中如何为曲面墙体开洞口,一键开洞?

一、Revit中如何为曲面墙体开洞口 直线墙体开洞可以直接通过编辑轮廓来绘制洞口形状即可,或者可以通过选中墙体选择“墙-洞口”命令来开洞口。 但是曲面墙体开洞并没有那么简单,曲面墙体不能够编辑轮廓,并且选中曲面墙体“墙-洞口”命令只能够…

C++const函数的运用:深度解析const函数的魅力

C 深度解析const函数的魅力 1. C const函数的基本概念(Basic Concepts of const Functions in C)1.1 const函数的定义与特性(Definition and Characteristics of const Functions)1.2 const函数的使用场景(Usage Scena…

docker磁盘空间爆满 通过overlay2 目录名查找对应容器

docker磁盘空间爆满 通过overlay2 目录名查找对应容器 1、docker文件系统占用分析2、使用shell脚本通过overlay2 目录名查找对应容器 1、docker文件系统占用分析 du -sh /var/lib/docker/*看到占用磁盘空间最大的目录是docker存储根目录下的overlay2目录 再查看docker存储根目…

Android智能指针SP WP

1.概述 Android的C部分代码中有大量的sp/wp存在,意思是strong pointer和weak pointer,看字面意思就是指针相关的东西。C是通过new和delete进行内存的分配和释放的,但是有时候开发者会忘记使用delete导致内存泄露,所以Android中就…

设计师们都在用的AI作图神器,你还不抓紧入手一款

人工智能在机器和计算机控制的机器人中模拟人类智能过程。这允许计算机系统执行繁重的任务,帮助人类专注于更重要的事情。因此,多年来,工作场所对 AI 集成的需求不断增加。 同样,人工智能正迅速成为设计行业的一部分。在平面设计…

MyBatis与Spring的整合

学习目标: 掌握Spring与MyBatis的集成 掌握使用SqlSessionTemplate实现整合 掌握使用MapperFactoryBean实现整合 掌握Spring的事务切面实现声明式事务处理 掌握使用注解实现声明式事务处理 学习内容: 1.整合思路梳理 思路梳理 以上流程可以全部移…

RHCE 作业二

1. 第一步:配置服务端server 1>安装chrony [rootserver ~]# yum install chrony -y 2>编辑配置文件,修改为阿里的时间服务地址 [rootserver ~]# vim /etc/chrony.conf 3> 重启服务 [rootserver ~]# systemctl restart chronyd 4>测试 5>…

证件照制作教程:如何使用在线工具制作高质量的证件照

证件照制作:让你的形象更加完美 介绍 证件照是人们在日常生活、工作和学习中必不可少的一项证明身份的重要文件。而证件照的质量好坏不仅会直接影响到证件审核的效率,还会影响到自己形象的好坏。为了让自己的形象更加完美,我们需要制作高质…

公网使用SSH远程连接安卓手机Termux - Android手机服务器

文章目录 1.安装ssh2.安装cpolar内网穿透3.远程ssh连接配置4.公网远程连接5.固定远程连接地址 转载自cpolar极点云的文章:公网SSH远程连接Termux – 电脑使用安卓Termux 「无需公网IP」 使用安卓机跑东西的时候,屏幕太小,有时候操作不习惯。不…