首页 > 其他分享 >JVM-part-运行时数据区

JVM-part-运行时数据区

时间:2024-05-17 17:01:38浏览次数:22  
标签:存储 常量 对象 part GC JVM 线程 方法 运行

运行时数据区组成部分:

  1. 程序计数器(PC寄存器 Program Counter Register)
  2. Java虚拟机栈(Java Virtual Machine Stacks)
  3. 本地方法栈(Native Method Stack)
  4. 堆(Heap)
  5. 方法区(Method Area)

其中存在线程共享和线程不共享的区域,如下:
线程共享:堆、栈
线程不共享:每一个线程都有的,程序计数器、本地方法栈

详细解释:

1、程序计数器:

他的作用是记录每一个线程当前正在执行的字节码的内存地址,便于在线程恢复的时候恢复执行**,避免了重复执行

2、Java虚拟机栈:

它是通过数组实现的一个栈结构(栈结构的特点是先进后出),每一个线程都存在自己的Java虚拟机栈,Java虚拟机栈中的基本元素为栈帧(Stack Frame),这里的栈帧其实就是我们调用的方法,调用一个方法,就会在Java虚拟机栈中压入一个栈帧,作为当前栈帧,也就是正在执行的方法;当一个方法结束的时候,就会将对应的栈帧进行弹出,将Java虚拟机栈中的下一个栈帧作为当前栈帧

  • 栈帧的组成:
    • 局部变量表(Local Variable Table):元素索引从0开始,它是栈帧的重要组成部分之一,同时对栈帧的大小也具有决定性的作用,通常来说,栈帧中的局部变量表越大,栈帧的大小就会越大,局部变量表的作用是存储调用方法中的局部变量和方法参数,需要注意的是局部变量表的大小在编译的时候就确定了,通过javap -v Xxx.class可以进行查看

    • 操作数栈(Operand Stack):也是栈帧的重要组成部分之一,底层通过数组实现,他的作用是存储当前方法过程中的操作数,举一个例子:例如


      通过jclasslib查看

可以通过-Xss进行指定Java虚拟机栈的大小

  • 方法返回地址(Return Address):它的作用是保存了被调方法执行完成之后需要执行的代码位置(这个位置当然在调用者中),恢复调用者的代码执行
  • 动态链接(指向运行时常量池的方法引用)(Dynamic Linking):他的作用是指向运行时常量池的方法引用,也就是将符号引用解析为直接引用,举一个例子,在此之前说明一下,在Java文件编译为Class文件的时候,会将所有的变量和方法引用转化为符号引用,存放到常量池中,比如,如果需要描述一个方法调用了另一个方法,就需要使用常量池中的表示指向方法的符号引用,动态链接就是解析这些符号引用,将其转化为方法的直接引用

说到动态链接的话,就会涉及到几个调用指令,如下:

  • 普通调用指令:
    • invokestatic:调用static方法,是确定的方法,在编译期就能确定,所以是非虚方法。
    • invokesopecial:调用方法(构造器)、私有方法和通过super调用的父类方法也是确定的,在编译期间就能确定,为非虚方法
    • invokevirtual:这里需要注意,除了通过final修饰的方法为非虚方法外,其他的都为虚方法(不知掉具体的方法),其具体实现也是在运行时确定的。
    • invokeinterface:调用接口,也是虚方法,其具体实现也是在运行时确定的。
  • 动态调用指令:
    • invokedynamic:在java7出来之后添加的新指令,它允许开发人员编写更加灵活的代码,通常伴随lamble表达式出现(类型通过变量来确定)与其他调用指令不同,invokedynamic 指令的调用目标在运行时才会确定,因此它是一种更加灵活和动态的调用方式。

    • 其他信息:通常包括当前方法的状态、异常处理信息、其他信息

3、本地方法栈:

它和Java虚拟机栈的结构差不多,但是调用的方法为native修饰的本地方法,通常是通过C或者C++编写的 (支持本地方法的调用)

4、堆:

堆是运行时数据区的重要组成部分,它是由年轻代、老年代组成(可能有的帖子中还存在元数据,但是我考虑到元数据是在方法区中的,所以就没有将它放进来哈)。

  • Young/New generation 年轻代:在年轻代中又由三部分组成:
    • Eden(伊甸园区):伊甸园区通常是新对象创建并存储的地方,因为据统计有80%的对象都存在朝生夕死的特征,能使垃圾回收变得更高效,注意哈,它能触发YGC(Young GC或者叫Minor GC)
    • Survivor0(幸存者1区):它是年轻代的组成部分之一,它主要是存储经过一次或者多次垃圾回收之后任然存在的对象,注意哈,Surivor幸存者区不能触发YGC
    • Survivor1(幸存者2区):同上

幸存者区,也叫 from、to,如何判断哪一个幸存者是from或者to,这个很容易,为空的一个就是to,不为空的就是from

当一个对象来的时候,存放的流程介绍(感觉没说明白,可看图参考):
首先当来一个对象的时候,如果伊甸园区是空的或者能够存放当前的对象的时候(伊甸园区的空闲大小比当前对象的大小大),就会将该对象存入伊甸园区,如果伊甸园区无法进行存储,或者已经满了,就会触发YGC(MinorGC)来进行垃圾回收,注意哈,不管是什么GC,都会将用户线程进行暂停(Stop The World,stw),因为MinorGC的效率很高,所以MinorGC对我们的性能往往没有什么影响,那么,在MinorGC中会做些什么呢,首先会通过可达性分析算法判断在Eden中哪些对象是垃圾,如果是垃圾就会直接进行清除,如果不是垃圾就会将其放入为空幸存者区(to),然后将另一个幸存者区的对象也放入to区,并且会在每一个对象的对象头的age属性中加1,当这个age等于15的时候(默认为15,可自定义)就会将这个对象从幸存者区晋升到老年代,当MinorGC完成之后,这时的Eden区和一个幸存者区都是空的,就又能存储新对象了


  • Tenure/Old generation 老年代:老年代中通常存放的是大对象(连续内存地址,如数组)或者存活时间较长的对象,当老年代满的时候,会先触发一次Minor GC,在执行Major GC(针对老年代的垃圾回收),这里要注意下,有的帖子会将这里的Major GC称为Full GC,尽管他们两个都会回收老年代的垃圾,但是,Full GC是针对与整个堆的清理和方法区的清理,而Major GC从效果上只会将年轻代和老年代进行清理,所以还是不同的哈,接着说,如果当老年代需要存储对象时,触发了Major GC之后任然不能存储的时候,就会出现OOM

这里会有几个问题哈:

1.是不是所有在老年区的对象的age都是15?

解析:不是,因为当需要存储一个大对象的时候,如果Eden不能存储会首先执行一次Minor GC,判断Eden是否能存储这个对象,如果不能,就会考虑直接将这个对象放入老年区中,也是需要判断下是否能够存储的哈,如果不能存储就会出现OOM错误,还有一种就是当Survivor区中的to满了,Eden区还需要向to区复制对象的时候,会先触发一次Minor GC,之后在判断是否能存储,如果不能存储就会考虑将to区的对象和Eden区中需要写入to区的对象写入到老年代,但是也是需要考虑是否能存的下哈

2.我们都知道在运行时数据区中的栈和方法区是线程共享的,一个JVM只有一个,那么是不是在堆中的所有数据都是线程共享的?

解析:在堆中的Eden中还存在一种内存区域叫做TLAB(Thread Local Allocation Buffer),每个线程在堆中都有一份,默认的大小是Eden内存的1%,也可以存储对象

3.为什么要将堆中的数据进行分代,不能直接为一块吗?

解析:我们都知道,大多数的对象都是朝生夕死的,并且在每一次GC操作都是会将用户线程先暂停(STW),在进行GC操作的,这个暂停时间大小从小到大为:Minor GC < Major GC < Full GC (范围越大,时间越长),如果我们将堆内存设置为一块的话,就会频繁的调用GC,导致用户线程暂停的时间越长,就影响效率了

4.是不是所有的对象都是分配在堆中的?

解释:不是的,这里有个技术叫做逃逸技术,简单解释下就是这个对象实体会不会被其他线程使用到,如果会,就说明这个对象会进行逃逸,反之为不会逃逸,对于不会逃逸的对象,我们可以实施一种叫做栈上存储的技术,就是将这个对象存储到栈中,我们直到在Java虚拟机栈中的栈帧是当执行方法执行完成之后就会出栈,当我们将对象存到栈中,就不再需要GC进行回收,从而提高了效率。这里说到的逃逸对象,我们有多种优化方式,包括刚说的栈上存储、标量替换、同步消除,这里就不一一说明了。

5、方法区

在JDK8之前,方法区也叫做永久代,在JDK8之后,方法区叫做元空间。

方法区在逻辑上属于堆的一部分,但是在HotSoprtJVM来说,方法区也叫做Non-Heap(非堆),并且在通过-X:Xms和-X:Xmx进行设置堆的大小的时候,并没有包含方法区的大小,所以方法区可以看作是一块独立于Java堆的内存空间。

方法区和堆一样,是线程共享的。在JVM创建的时候创建,可以自定义设置方法区的大小,方法区的大小决定了系统可以保存多少类,如果系统定义的类的总大小大于方法区的大小,就会导致方法区溢出,出现OutOfMemoryError:PermGen space或者OutOfMemoryError:Metaspace错误(取决于jdk的版本是1.7之前还是之后)

方法区的组成:

  • 类型信息:
    方法区中存储了每一个加载的类型(class,interface,enum,annatation)以下信息:
    • 这个类型的有效名称(包名+类名)
    • 这个类型的父类的有效名称(需要注意的是Object和interface没有父类)
    • 这个类型的修饰符(如public、final等)
    • 这个类型直接接口的一个有序列表
  • 域信息
    • JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
    • 域的相关信息包括:域名称、域类型、域修饰符(public、private等)
  • 方法信息
    方法区中存储了方法的以下信息:
    • 方法名称
    • 方法的返回值类型
    • 方法的参数数量及其类型(需要按照参数列表的顺序)
    • 方法的修饰符(如public、final、static等)
    • 方法的字节码
    • 异常表
  • JIT代码缓存:将即时编译器(JIT)编译之后的机器码进行保存,提高执行效率。
  • 静态变量:存放类的静态变量,这些变量在类加载的时候分配内存空间,在类卸载的时候将空间释放。
  • 运行时常量池:存储编译期间生成的各种字面量和符号引用

常量池和运行时常量池的区别:
1.常量池的存储位置在.class文件中,运行时常量池的存储位置在JVM的方法区中
2.常量池是在编译期间生成的,而运行时常量池是在类加载阶段生成的,基于编译期常量池创建
3.常量池的内容时静态的,在编译的时候就确定了,而运行时常量池的内容是动态的,允许在运行时添加新常量
4.常量池时用在.class文件在磁盘上的存储和传输,而运行时常量池的作用是在运行时动态连接和常量管理。

最后放几张图。
运行时数据区会错误的几个部分,即是否线程共享

Java运行时数据区的结构:

标签:存储,常量,对象,part,GC,JVM,线程,方法,运行
From: https://www.cnblogs.com/just1t/p/18151597

相关文章

  • 在Windows Server 2008 R2上运行.Net 8应用
    在WindowsServer2008R2上运行.Net8程序因为工作需要,要在客户的WindowsServer2008R2上运行一个WinForm程序。在网上搜了下之前也有人成功运行过.NetCore3、.Net6的Asp.NetCore服务,遂直接拿.Net8来写了。最后装了3个补丁包之后,也是成功运行。这篇笔记主要记录这3个补丁......
  • flutter 运行ios真机测试 提示 Command PhaseScriptExecution failed with a nonzero
    我这边引起CommandPhaseScriptExecutionfailedwithanonzeroexitcode的原因是我刚更换了推送证书,于是我打开钥匙串访问发现推送证书处于不受信任状态,于是把证书状态设置为信任状态并删除了旧的推送证书,设置完成后再去运行,就可以成功运行了。这是我这边的单一情况,......
  • 鸿蒙HarmonyOS实战-Stage模型(服务卡片介绍和运行机制)
    ......
  • Windows 中的通用 C 运行时更新
    在https://winlibs.com/介绍到Cruntime库时说:TraditionallytheMinGW-w64compilerusedMSVCRTasruntimelibrary,whichisavailableonallversionsofWindows.SinceWindows10UniversalCRuntime(UCRT)isavailableasanalternativetoMSVCRT.Universal......
  • bash脚本监控服务器SSH登录,每30分钟运行一次,发现登录发送到企业微信群
    //开始循环检测//loopCheck();//在每分钟的第30秒执行目标函数cron.schedule('358***',()=>{console.log('目标函数在8:35执行!');loopCheck_info();//在这里调用你想要定时执行的函数});cron.schedule('*/309-20***',()=>{con......
  • B. Mahmoud and Ehab and the bipartiteness
    原题链接题解观察一个二分图会发现同一组的节点不直接相连二分图能够建立的最多的边等于\(n*m\)code#include<bits/stdc++.h>usingnamespacestd;#definelllonglongvector<ll>G[100005];lldepth[100005]={0};llodd=0,even=0;voiddfs(llnow,llfa){i......
  • VScode 运行 jupyter 心得
    在服务器上,用vscode运行.ipynb文件是常用的手段,但是搞多了就会发现还是会有各种问题,在这里记录一下。os.environ的使用经常在这个运行一个程序前,我们需要加载一些环境变量,来设置比如代理转发(用于下载外面的东西),例:importosproxy_list=['HTTP_PROXY','HTTPS_PROXY',......
  • python打包在32位无法运行问题
    真不想吐槽现在的技术越高级越烂的一批尤其是开发工具win1064位python64位开发pyinsataller打包后不能在32位上运行别折腾重新安装python32位测试安装python3.12.232位竟然不能安装pandas(见鬼去吧)重新安装python3.8.10提示不能用在xp上,也可以接受了.再安装依赖包,没......
  • 自研WPF插件系统(沙箱运行及热插拔)
    前言插件化的需求主要源于对软件架构灵活性的追求,特别是在开发大型、复杂或需要不断更新的软件系统时,插件化可以提高软件系统的可扩展性、可定制性、隔离性、安全性、可维护性、模块化、易于升级和更新以及支持第三方开发等方面的能力,从而满足不断变化的业务需求和技术挑战。......
  • openGauss 业务运行时整数转换错
    业务运行时整数转换错问题现象在转换整数时报错如下。Invalidinputsyntaxforinteger:"13."原因分析部分数据类型不能转换成目标数据类型。处理办法逐步缩小SQL范围确定不能转换的数据类型。详情查看:https://opengauss.org详情查看:https://docs-opengauss.osinfra.c......