首页 > 系统相关 >JVM 主要组成部分与内存区域

JVM 主要组成部分与内存区域

时间:2024-12-28 09:56:10浏览次数:8  
标签:Java JVM 对象 局部变量 线程 内存 组成部分

一、JVM 主要组成部分:

JVM的主要包含两个组件和两个子系统,分别为:

(1)本地库接口(Native Interface):与native lib(本地方法库)交互,融合其他编程语言为Java所用,是与其它编程语言交互的接口

(2)运行时数据区(Runtime data area():即常说的JVM内存

(3)类加载子系统(Class loader):根据全限定类名装载class文件到运行时数据区的方法区中

(4)执行引擎子系统(Execution engine):也叫解释器,负责解释class指令,再提交给操作系统执行

 通过上面的图,我们可以大致知道Java代码是如何运行的:首先通过编译器将Java源代码转换成字节码,接着类加载系统把字节码加载到运行时数据区的方法区内,再使用执行引擎将字节码翻译成底层系统指令,最后交由 CPU 去执行,而这个过程中可能需要调用其他语言的本地库接口来实现整个程序的功能。

字节码只是 一套 JVM 指令集规范,并不能直接交给底层操作系统去执行,因此需要通过执行引擎将字节码翻译成底层系统指令

二、JVM 内存

JVM 在执行 Java 程序时,会将内存划分为若干个不同的数据区域,不同的区域用途不同,创建和销毁时间也不相同。但在 JDK1.8 版本之后对运行时数据区域做了些修改,下面我们分别来看看修改前后的内存区域是怎么样的。

1、JDK1.8之前的JVM内存区域:

2、JDK8之后的JVM内存区域

3、各区域的的作用:

1.虚拟机栈(或叫线程栈)

官方叫法虚拟机栈,更好理解的叫法可以叫线程栈,为什么叫线程栈,因为每个线程只要开始运行,JAVA虚拟机就会给线程挖一块内存出来当做栈,用来存放线程的局部变量等,而栈中的操作是由一个个的栈帧组成,

以上面这个类为例,运行时首先main()方法作为第一个栈帧入栈,在栈帧中调用了computer()方法,那么就会将computer()这个方法进行入栈,后面执行到其他方法依次类推。而一个方法执行完后那么就出栈销毁。

而一个栈帧中还有局部变量表、操作数栈、动态链接、方法返回地址。

局部变量表:用于存储方法参数和方法内部定义的局部变量。

操作数栈:操作数栈主要用于执行方法时进行数据操作和计算。

动态链接:用于在运行时解析方法调用。

方法返回地址:,用于保存方法调用结束后的返回地址。当方法执行完成时,程序需要跳转回方法调用点继续执行。

当我们编译好一个java类后,会生成对应的.class文件,即字节码文件。

可以通过jvm -p命令对该文件进行反汇编

可以看到java代码int a=1这行代买对应的jvm指令,iconst_1: 将int类型常量1压入操作数栈istore_1:将int类型值存入局部变量

之后执行到int c=a+b这行代码

那么对应的指令码部分就是如图所示。

iload_1:从局部变量1中装载int类型值。iload_2:从局部变量2中装载int类型值。在此处即表示将变量a和变量b的值压入操作数栈。如下图所示

然后执行iadd指令,i代表int类型,add代表加法操作。那么就会从操作数栈中栈顶弹出两个对应的数,执行加法操作后,将数值写入操作数栈。

bipush 10就是将10压入操作数栈。imul和iadd同理

i++ 和 ++i 的区别:

  1. i++:从局部变量表取出 i 并压入操作栈(load memory),然后对局部变量表中的 i 自增 1(add&store memory),将操作栈栈顶值取出使用,如此线程从操作栈读到的是自增之前的值。
  2. ++i:先对局部变量表的 i 自增 1(load memory&add&store memory),然后取出并压入操作栈(load memory),再将操作栈栈顶值取出使用,线程从操作栈读到的是自增之后的值。

之前之所以说 i++ 不是原子操作,即使使用 volatile 修饰也不是线程安全,就是因为,可能 i 被从局部变量表(内存)取出,压入操作栈(寄存器),操作栈中自增,使用栈顶值更新局部变量表(寄存器更新写入内存),其中分为 3 步,volatile 保证可见性,保证每次从局部变量表读取的都是最新的值,但可能这 3 步可能被另一个线程的 3 步打断,产生数据互相覆盖问题,从而导致 i 的值比预期的小。

 

4.Java堆

用于存储对象实例,是占用内存最大的区域,可划分为新生代和老年代,新生代又可细分为 Eden区(伊甸园区)、From Survivor区(S0)、To Survivor区(S1)。

在 HotSpot 虚拟机中,对象在堆内存布局分成三部分:对象头,实例数据,对齐填充。

① 对象头:包括两部分的信息:

运行时数据:存储对象自身的运行数据,如哈希码,GC代年龄,锁状态、线程持有的锁、偏向线程ID等。

类型指针:即对象指向它的类型数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个Java数组,那对象头中还必须有一块用于记录数组长度的数据。

② 实例数据:是对象真正存储的有效信息,是在程序代码中所定义的各种类型的字段内容,相同宽度的字段会被分配到一起。

③ 对齐填充:并不是必然存在的,仅起着占位符的作用。

 

 

当一个对象被new出来后,那么就放入了伊甸园区,当创建的对象越来越多,伊甸园区的内存不够时,那么就会触发垃圾回收GC(Garbage Collection),字节码执行引擎就会启动垃圾收集线程,执行Minor gc,当一个对象经过Minor gc后会被清除掉,或者是进入S0区和S1区,每经理一次GC对象头中的GC代年龄都会+1,超过15进入老年代,当老年代中内存不够时,就会触发Full GC,当年轻代和老年代中的内存都满了,且GC没有可回收的对象时,那么就会发生OOM(内存溢出)

5.元空间(方法区)

该区域被所有线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码(即class文件)等数据,同时,元空间中有一个运行时常量池用于存放静态编译产生的字面量和符号引用。该区域不需要连续的内存,并且可以动态扩展,动态扩展失败会抛出 OOM 异常,对该区域进行垃圾回收的主要目标是对常量池的回收和对类型的卸载,但是一般比较难实现。

如上图,常量testData,和User对象(也是存放的User对象的地址,指向堆中的User对象实例)

方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。JDK8 之前,Hotspot 中方法区的实现是永久代(Perm),JDK8 开始使用元空间(Metaspace),以前永久代的静态变量和常量池移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。

标签:Java,JVM,对象,局部变量,线程,内存,组成部分
From: https://blog.csdn.net/Tomkruse11/article/details/144782748

相关文章

  • [VUE]CALL_AND_RETRY_LAST分配失败-JavaScript堆内存不足 errno134
    使用vscode开发项目,由于项目较大,在运行npmrundev命令后,在一定的时间范围内,对vscode中的代码进行保存后,会自动编译运行,保存几次后就报错,需要重新运行npmrundev,很耗费时间)后报错报错:CALL_AND_RETRY_LASTAllocationfailed-JavaScriptheapoutofmemory(CALL_AND_RETRY_LAS......
  • JVM实战—3.JVM垃圾回收的算法和全流程
    大纲1.JVM内存中的对象何时会被垃圾回收2.JVM中的垃圾回收算法及各算法的优劣3.新生代和老年代的垃圾回收算法4.避免本应进入S区的对象直接升入老年代5.StoptheWorld问题分析6.JVM垃圾回收的原理核心流程7.问题汇总 1.JVM内存中的对象何时会被垃圾回收(1)什么时候会......
  • Java方法链调用以及在JVM和安卓DalvikVM下的区别
    目录方法链字节码与Smali下的编译结果总结方法链方法链(MethodChaining),也被称为命名参数法,是在面向对象的编程语言中调用的调用多个方法的通用语法。每一个方法返回一个对象,在一个单一的声明里,方法链省去了中间变量的需要。当需要构建一个对象或者设置其初始属性时,往往通过......
  • JVM实战—JVM内存设置与对象分配流转
    1.JVM内存划分的原理细节(1)背景引入接下来介绍JVM内存的分代模型:新生代、老年代、永久代。现在已知代码里创建的对象,都会进入到Java堆内存中。如下所示,main()方法会周期性执行loadReplicasFromDisk()方法来加载副本数据。publicclassKafka{publicstaticvoidm......
  • 2025年CXL强势启航:开启内存扩展新时代
    ComputeExpressLink(CXL)是一项旨在提高数据中心和高性能计算环境中CPU、内存及加速器之间通信效率的技术。尽管自2019年以来就已经存在并开始被一些产品使用,但直到2025年,CXL才有望从一个非常小众的技术转变为常规技术,广泛应用于现代服务器中。CXL相关扩展阅读:CXL与近......
  • JVM内存布局与 JNA 调用本地方法原理详解
    JVM内存布局详解程序计数器(PC)这个是当前线程正在执行的字节码行号指示器,类似于实际的PC,根据这里面的内存数据来确定程序接下来执行的指令.在JAVA中,每个线程都有一个,相互隔离,线程之间的切换就是基于程序计数器.如果执行的是方法,这里记录的是虚拟机字节码指令的......
  • GaussDB内存过载分析
    问题现象数据库进程内存占比较高长时间占比较高观察监控平台内存占用的变化曲线,无论当前数据库是否有业务在运行,数据库进程内存占总机器内存的比例长时间处于较高状态,且不下降。执行作业期间占比较高数据库进程在没有业务执行时,内存使用持续处于较低的状态,当有业务执行时,内......
  • Linux磁盘阈值及内存阈值检测脚本
    #!/bin/bash#设置阈值,例如磁盘使用率超过80%,内存使用率超过90%DISK_THRESHOLD=90MEMORY_THRESHOLD=99#获取磁盘使用百分比(这里以根目录为例)DISK_USAGE=$(df/--output=pcent|grep-o'[0-9]\+')#获取内存使用百分比MEMORY_USAGE=$(free|grepMem|awk'{print......
  • JVM实战—2.JVM内存设置与对象分配流转
    大纲1.JVM内存划分的原理细节2.对象在JVM内存中如何分配如何流转3.部署线上系统时如何设置JVM内存大小4.如何设置JVM堆内存大小5.如何设置JVM栈内存与永久代大小6.问题汇总 1.JVM内存划分的原理细节(1)背景引入(2)大部分对象的存活周期都是极短的(3)少数对象是长期存......
  • 4. JVM 运行时内存
    Java堆从GC的角度还可以细分为:新生代(Eden区、FromSurvivor(S0)区和ToSurvivor区(S1))老年代1.新生代a)Eden区Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。b......