一文带你详细了解JVM运行时内存

news2025/1/21 4:57:03

一文带你详细了解JVM运行时内存

  • 1. 程序计数器
  • 2. 虚拟机栈
  • 3. 本地方法栈
  • 4. 堆
    • 4.1 堆的总括
      • 4.1.1 概念
      • 4.1.2 特点
      • 4.1.3 设置堆内存大小
      • 4.1.4 堆的分类
    • 4.2 新生代和老年代
      • 4.2.1 对象存储
      • 4.2.2 配置新生代和老年代的堆中占比
    • 4.3 对象分配过程
    • 4.4 堆GC
  • 5.元空间
  • 6.方法区
    • 6.1 方法区的理解
    • 6.2 方法区结构
    • 6.3 方法区设置
  • 7.运行时常量池

在这里插入图片描述

在这里插入图片描述

1. 程序计数器

概念

程序计数器也叫作PC寄存器,是一块很小的内存区域,可以看做是当前线程执行的字节码的行号指示器。字节码的解释工作就是通过改变程序计数器里面的值来获得下一条需要执行字节码的指令。

特点
  • Pc寄存器表现为一块内存,功能是存放伪指令,确切的说是存放的将要执行指令的地址。

  • 当虚拟机正在执行的是一个native方法时,JVM的PC寄存器存储的值是undefined。

  • 程序计数器是线程私有的,它的生命周期和线程一样,每个线程只有一个。这也是为了保证多线程下,线程切换后能恢复到正确的执行位置,所以每个线程需要独立的程序计数器,相互隔离互不影响。

  • 此区域是唯一一个没有OOM情况的区域。

图例

在这里插入图片描述

2. 虚拟机栈

概念

JAVA虚拟机栈的生命周期和线程相同,他也是线程私有的,每一个线程有自己独立的虚拟机栈。他用来存储栈帧,程序运行时,每一个方法被调用执行时都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

图例演示

栈帧

栈帧是支持虚拟机方法调用和执行的数据结构。栈帧中存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法的调用和执行完成都对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

在这里插入图片描述

来个小总结

设置虚拟机栈的大小

-Xss为JVM启动时的每个线程分配的内存大小,也就是可以设置线程栈的大小。

-Xss1m # 单位为MB -Xss1024k #单位为KB -Xss1048576 #字节大小

在这里插入图片描述

局部变量表

局部变量表的作用是在方法执行过程中存放所有的局部变量。编译成字节码文件时就可以确定局部
量表的内容。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

实例方法中的序号为0的位置存放的是this,指的是当前调用方法的对象,运行时会在内存中存放实例对象的地址。

为了节省空间,局部变量表中的槽是可以复用的,- -旦某个局部变量不再生效,当前槽就可以再次被使用。
在这里插入图片描述
在这里插入图片描述

局部变量表是一组变量值存储空间,用于存放方法的参数和方法内定义的局部变量。

在这里插入图片描述

操作数栈

操作数栈是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或者对象实例的字段中复制常量或者变量写到操作数栈,再随着计算的进行会将栈中的元素出栈到局部变量表或者返回给方法调用者。

在这里插入图片描述

动态链接

java虚拟机中,每一个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的为了支持方法调用过程中的动态链接。 动态链接的作用:将符号引用转换为直接引用。

在这里插入图片描述

在这里插入图片描述

方法返回地址

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

在这里插入图片描述

在这里插入图片描述

3. 本地方法栈

概念

本地方法栈则是为虚拟机使用到的本地(Native) 方法服务,而虚拟机栈是为使用到的java方法服务。

关于native方法

native关键字修饰的Java方法是一个原生态方法,方法对应的实现Java作用范围达不到,而是在用其他编程语言(如C和C++)文件中实现。Java语言本身不能直接对操作系统底层进行访问和操作,但可以通过JNI接口调用其他编程语言来实现对操作系统底层的访问。 native方法在异地实现,类似抽象方法,不能有方法体,要以分号结束。例如:

本地方法栈特点

  • 本地方法栈加载nativef方法,是为了填补java不方便实现的场景产生的。

  • 虚拟机栈为为虚拟机执行java服务,而本地方法栈为了执行虚拟机所使用到的native服务

  • 本地方法栈也是线程私有的,和线程的生命周期是一致的,每个线程都有一个本地方法栈。

在这里插入图片描述

在这里插入图片描述

4. 堆

4.1 堆的总括

4.1.1 概念

Java堆(Java Heap) 是虚拟机所管理的内存中最大的一块。 Java堆是被所 有线程共享的一块内存区域, 在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例, Java 世界里“几乎”所有的对象实例都在这里分配内存。

在这里插入图片描述

4.1.2 特点

  • 堆是Java虚拟机所管理内存中最大的一块区域。

  • 堆是线程共享的。

  • 堆在虚拟机启动的时候创建。

  • 堆存在的目的就是存放对象实例。

  • 堆是垃圾回收管理的主要区域。因此堆又被称作为GC堆,JAVA堆还可以细分为新生代,老年代,永久代(jdk8以后就取消了),其中新生代又分为Eden空间、From survivor、To survivor。

  • 堆在计算机物理上存储是不连续的,但是逻辑上是连续的,它的大小可以调节(-Xmx,-Xms控制)。

  • 方法结束后,堆对象不会马上的移除,仅仅在垃圾回收的时候才会移除。

  • 如果堆中没有足够的内存完成对实例的分配,且堆的空间无法再扩展时,那么将会报出OOM异常。

4.1.3 设置堆内存大小

我们可以通过-Xms来设置最小堆内存,通过-Xmx设置最大堆内存。

以上是设置了:-Xms5m -Xmx20m

这里可以看出打印出来的Xmx值18m和设置的值20m之间是有差异的,total Memory和最大的内存之间也还是存在比较明显的差异,就是说JVM一般会尽量保持内存在一个尽可能底的层面,而非贪婪做法按照最大的内存来进行分配。

另外,当我们申请分配内存10m时,我们会发现free Memory和total Memory都上升了,可以看出JVM在内存分配时是动态分配的。

4.1.4 堆的分类

JAVA将虚拟机堆分为三个部分:

  • 新生代 (又分为伊甸园区,幸存者区s0和幸存者区s1)

  • 老年代

  • 永久代(JDK1.8后没有了,被本地内存的元空间取代了)

图例如下:

4.2 新生代和老年代

4.2.1 对象存储

  • 新生代存放刚创建的实例对象,内存比较小,垃圾回收比较频繁。新生代又分为Eden区,survivor To区S0和survivor From区S1,其中S0区和S1区并不是固定的from及to的区域,由对象转移的方向决定的,假设对象从S1转移到S0,那么S1便是survivor From,S0是survivor To。

  • 老年代主要存放一些生命周期比较长的对象,经过在新生代几次的回收依旧没有清除掉,那这部分实例便会转移到老年代。老年代的垃圾回收相对来讲没有那么频繁。

在这里插入图片描述
在这里插入图片描述

4.2.2 配置新生代和老年代的堆中占比

默认情况下-XX:NewRatio=2,表示新生代:老年代 = 1:2,新生代占整个堆空间的1/3

案例:假设我们将-XX:NewRatio修改为等于4,那么则表示新生代:老年代 = 1:4,那么新生代占整个堆空间的1/5

除了我们可以配置新生代和老年代的比例之外,我们还可以配置eden和S0和S1在新生代中的占比情况,默认情况下-XX:SurvivorRatio = 8,表示Eden:S0:S1=8:1:1,这表示Eden占整个新生代的8/10,而两个survivor区域分别占了1/10,另外,需要补充一点,由于JVM在运行时,每次都只会使用Eden区和一块survivor区进行服务,因此总是会有一个survivor区域是空闲着的,所以新生代的最高使用也只能达到9/10。

4.3 对象分配过程

  • new对象时首先会将对象放在eden区,该区大小有内存限制。

  • 当eden区的数据满了之后,程序还需要创建对象,会触发垃圾回收,将那些不再被引用的对象给销毁掉。

  • 剩余没被回收掉的对象会被转移到S0区,而程序新创建的对象又会继续写入Eden区。

  • 当再次发生垃圾回收时,如果S0中还存在未被销毁的对象,那么这部分剩余的对象会被转移到S1中。

  • 之后每次经历垃圾回收,存在S0或者S1中未被销毁的对象总会相互转移过去。

  • 当这种转移达到15次上限后,那么这部分对象将会被转移到老年区。当然这个阈值并不是固定15,可以通过调节参数 -XX:MaxTenuringThreshold=N来控制阈值。

  • 当养老区的内存也不足时,会触发GC进行养老区的垃圾回收。

  • 如果养老区进行了GC垃圾回收后还是没有办法保存新创建的对象,那么将会报OOM异常。

4.4 堆GC

Java中的堆是虚拟机中GC收集垃圾的主要区域。GC分为两种,一种是部分收集(Partial GC),一种是整堆收集(Full GC).

部分收集

  • 新生代收集(Minor GC/Yong GC):只是新生代的垃圾回收。

  • 老年代收集(Major GC/Old GC):只是老年代的垃圾回收。

  • 混合收集(Mixed) :收集整个新生代和老年的垃圾。(G1 GC会混合回收, region区域回收)

整堆收集(Full GC):收集整个java堆和方法区的垃圾收集器

年轻代GC触发条件

  • 当年轻代内存不足时,会触发Minor GC,这里的内存不足指的是Eden区的内存不足,Survivor区不会。

  • Minor GC 会暂停其他用户的线程,等到垃圾回收结束,用户的线程才恢复。

老年代GC触发条件

  • 老年代空间不足时,会尝试触发Minor Gc,如果空间还是不足,则会触发Major GC

  • 如果Major GC结束后,空间还是不足,会报OOM异常。

  • Major GC的速度比Minor GC慢10倍以上。

Full GC触发条件

  • 程序调用System.gc(),会触发Full GC,但不会立即去执行。

  • 老年代空间不足。

  • 方法区空间不足。

  • 通过Minor GC后仍能进入老年代的对象所占空间大于老年代剩余可用空间。

在这里插入图片描述

5.元空间

JDK1.8后为什么废除永久代,引入元空间
  • 在之前的永久代中,它是堆的一部分,主要是在存储类的元数据、静态变量、常量等,这些数据的大小也不太容易控制和计算,开发人员对永久代进行调优会有很多的难度。永久代会对GC带来不必要的复杂度,回收效率偏低。

  • 而用元空间替代永久代,这样的话可以很好的解决这个问题,因为元空间是放在本地内存上的,简而言之,只要你服务器内存还有,元空间基本就不会发生内存溢出等问题。

废除永久代的好处
  • 由于类的元数据分配在本地内存上,这样就说元空间的最大分配内存就是服务器系统剩余可用内存,不会遇到永久代时存在的内存溢出问题。

  • 将运行时常量池从永久代中分离出来,与类的元数据分开,提高了元数据的独立性。

  • 将元数据从永久代剥离出来放到元空间,可以提升对元数据的管理,同时也提升GC效率。

元空间相关参数
  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。如果没有使用该参数来设置类的元数据的大小,其最大可利用空间是整个系统内存的可用空间。JVM也可以增加本地内存空间来满足类元数据信息的存储。但是如果没有设置最大值,则可能存在bug导致Metaspace的空间在不停的扩展,会导致机器的内存不足;进而可能出现swap内存被耗尽;最终导致进程直接被系统直接kill掉。如果设置了该参数,当Metaspace剩余空间不足,会抛出:java.lang.OutOfMemoryError: Metaspace space。

  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集。

  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。

在这里插入图片描述

6.方法区

6.1 方法区的理解

概念:

元空间、永久代是方法区具体的落地实现。方法区看作是一块独立于Java堆的内存空间,它主要是用来存储所加载 的类信息的,方法区是线程共享的。

特点:

  • 方法区与堆一样是各个线程共享的内存区域。

  • 方法区在JVM启动的时候就会被创建,并且它实际的物理内存空间和Java堆一样可以是不连续的。

  • 方法区的大小跟堆一样,可以选择固定的大小或者动态变化。

  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机仍然会报OOM异常。

  • 关闭虚拟机就会释放方法区区域。

在这里插入图片描述

在这里插入图片描述

6.2 方法区结构

类加载器将Class文件加载到内存以后,将类的信息存储到方法区中。

方法区中存储的内容:

  • 类型信息(域信息、方法信息)

  • 运行时常量池

类型信息

  • 这个类型的完整有效名称(全名 = 包名.类名)

  • 这个类型直接父类的完整有效名(对于 interface或是java.lang. Object,都没有父类)

  • 这个类型的修饰符( public, abstract,final的某个子集)

  • 这个类型直接接口的一个有序列表

域信息

域信息,即为类的属性,成员变量 JVM必须在方法区中保存类所有的成员变量相关信息及声明顺序。 域的相关信息包括:域名称、域类型、域修饰符(pυblic、private、protected、static、final、volatile、transient的 某个子集)

方法信息

  1. 方法名称方法的返回类型(或void)

  2. 方法参数的数量和类型(按顺序)

  3. 方法的修饰符public、private、protected、static、final、synchronized、native,、abstract的一个子集

  4. 方法的字节码bytecodes、操作数栈、局部变量表及大小( abstract和native方法除外)

  5. 异常表( abstract和 native方法除外)。每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏 移地址、被捕获的异常类的常量池索引

在这里插入图片描述

6.3 方法区设置

方法区的大小不必是固定的,可以根据应用的需要动态调整

  • jdk7及之前

  • 通过-xx:Permsize来设置永久代初始分配空间。

  • -XX:MaxPermsize来设定永久代最大可分配空间。64位的机器默认是82M。当JVM加载的类信息容量超过了这个值,会报OOM异常:PermGen space。

  • jdk8及以后

  • 元数据区大小可以使用参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定。但是元数据区的 -XX:MaxMetaspaceSize默认是-1即没有限制,不设置可以使用系统剩余所有内存。

  • 如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError:Metaspace

在这里插入图片描述

7.运行时常量池

字节码文件中,内部包含了常量池。

方法区中,内部包含了运行时常量池。

常量池:存放了编译期间产生的各种字面量和符号引用。

运行时常量池:是常量池表在运行时的一种表现形式。

编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中。

​常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。

在这里插入图片描述

原文:详细了解JVM运行时内存 - 帝莘 - 博客园

在这里插入图片描述

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

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

相关文章

redis问题归纳

1.redis为什么这么快? (1)基于内存操作:redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以性能比较高 (2)数据结构简单:redis的数据结构是专门设计的&…

Unity2021及以上 启动或者禁用自动刷新

Unity 2021以以上启动自动刷新 Edit---> Preferences--> Asset Pipline --> Auto Refresh 禁用的结果 如果不启动自动刷新在Project面板选择Refresh是不会刷新已经修改后的脚本的。

MIB 6.S081 System calls(1)using gdb

难度:easy In many cases, print statements will be sufficient to debug your kernel, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful. To learn more about how to run GDB and the common iss…

存储过程与触发器

一、存储过程 1.1 概念 把需要重复执行的内容放在存储过程中,实现代码的复用。 create procedure 创建存储过程的关键字 my_proc1:存储过程的名字。 执行下例代码就是创建了一个存储过程 执行存储过程,就是把上图的插入语句重复执行,现…

蓝桥杯每日一题2023.11.19

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 首先想到的方法为dfs去寻找每一个数&#xff0c;但发现会有超时 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, cnt, a[N]; void dfs(int dep, int sum, int start) {if(dep 4){if(s…

linux文件IO

文件IO截断 截断对文件的偏移量没有影响。

Cross-View Transformers for Real-Time Map-View Semantic Segmentation 论文阅读

论文链接 Cross-View Transformers for Real-Time Map-View Semantic Segmentation 0. Abstract 提出了 Cross-View Transformers &#xff0c;一种基于注意力的高效模型&#xff0c;用于来自多个摄像机的地图视图语义分割使用相机感知的跨视图注意机制隐式学习从单个相机视…

git基本用法和操作

文章目录 创建版本库方式&#xff1a;Git常用操作命令&#xff1a;远程仓库相关命令分支(branch)操作相关命令版本(tag)操作相关命令子模块(submodule)相关操作命令忽略一些文件、文件夹不提交其他常用命令 创建版本库方式&#xff1a; 创建文件夹 在目录下 右键 Git Bush H…

Kafka性能测试初探

相信大家对Kafka不会陌生&#xff0c;但首先还是要简单介绍一下。 Kafka是一种高性能的分布式消息系统&#xff0c;由LinkedIn公司开发&#xff0c;用于处理海量的实时数据流。它采用了发布/订阅模式&#xff0c;可以将数据流分发到多个消费者端&#xff0c;同时提供了高可靠性…

Shell判断:流程控制—if(三)

一、调试脚本 1、调试脚本的其他方法&#xff1a; [rootlocalhost ~] # sh -n useradd.sh 仅调试脚本中的语法错误。 [rootlocalhost ~]# sh -vx useradd.sh 以调试的方式执行&#xff0c;查询整个执行过程。 2、示例&#xff1a; [rootlocalhost ~]# sh -n useradd.sh #调…

OpenCV快速入门:窗口交互

文章目录 前言一、鼠标操作1.1 鼠标操作简介1.2 鼠标事件类型&#xff08;event类型&#xff09;1.3 鼠标事件标志&#xff08;flags&#xff09;1.4 代码示例1.4.1 获取鼠标坐标位置1.4.2 监听鼠标滚轮事件1.4.3 在图像中显示鼠标坐标 二、键盘操作2.1 代码示例2.2 waitKey的等…

坐标系下的运动旋量转换

坐标系下的运动旋量转换 文章目录 坐标系下的运动旋量转换前言一、运动旋量物体运动旋量空间运动旋量 二、伴随变换矩阵三、坐标系下运动旋量的转换四、力旋量五、总结参考资料 前言 对于刚体而言&#xff0c;其角速度可以写为 ω ^ θ ˙ \hat {\omega} \dot \theta ω^θ˙&…

Linux|僵死进程

1.僵死进程产生的原因或者条件: 什么是僵死进程? 当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程. 简而言之,就是子进程先结束,并且父进程没有获取它的退出码; 那么僵死进程产生的原因或者条件就是:子进程先于父进程结束,并且父进程没有获取…

从一到无穷大 #19 TagTree,倒排索引入手是否是优化时序数据库查询的通用方案?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 文章主旨时序数据库查询的一般流程扫描维度聚合时间聚合管控语句 TagTree整体结构索引…

lv11 嵌入式开发 ARM指令集中(伪操作与混合编程) 7

目录 1 伪指令 2 伪操作 3 C和汇编的混合编程 4 ATPCS协议 1 伪指令 本身不是指令&#xff0c;编译器可以将其替换成若干条等效指令 空指令NOP 指令LDR R1, [R2] 将R2指向的内存空间中的数据读取到R1寄存器 伪指令LDR R1, 0x12345678 R1 0x12345678 LDR伪指令可以将任…

本地私域线上线下 线上和线下的小程序

私域商城是一种新型的零售模式&#xff0c;它将传统的线下实体店与线上渠道相结合&#xff0c;通过会员、营销、效率等方式&#xff0c;为消费者提供更加便利和高效的购物体验。私域商城的发展趋势表明&#xff0c;它将成为未来零售业的重要模式&#xff0c;引领零售业的创新和…

各类语言真实性能比较列表

这篇文章是我所做或将要做的所有真实世界性能比较的索引。如果你对想要看到的其他真实世界案例有建议&#xff0c;请在评论中添加。 用例 1 — JWT 验证 & MySQL 查询 该用例包括&#xff1a; 从授权头部获取 JWT验证 JWT 并从声明中获取电子邮件使用电子邮件执行 MySQL…

数据结构:红黑树的插入实现(C++)

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 一、红黑树二、红黑树的插入三、代码实现总结 一、红黑树 红黑树的概念&#xff1a; 红黑树是一颗二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&…

Android图片涂鸦,Kotlin(1)

Android图片涂鸦&#xff0c;Kotlin&#xff08;1&#xff09; import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path import android.graphics.PointF import android.…

改进YOLOv8:结合ODConv构成C2f_ODConv:即插即用的动态卷积/可轻量化

🗝️YOLOv8实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv8:我的专业笔记与技术总结   -- YOLOv8轻松上手, 适用技术小白,文章代码齐全,仅需 …