JVM-运行时数据区

在这里插入图片描述

1. Program Counter Register(PC) 存放指令位置

因为CPU需要不停的切换各个线程,切换回来的时候,就得知道从哪开始继续执行,所以为了实现这个需求(需要记录下一次执行的指令地址)才有了PC来存放。但是,PC寄存器是线程私有的,为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。

虚拟机的运行,类似于这样的循环:

	while(not end){

	取PC中的位置,找到对应的位置指令;

	执行指令

	PC++

	}

2. JVM stacks(栈)

  • stacks里面存的是多个frame(栈帧),每个方法会对应一个栈帧
  • ⚠️注意: StackOverFlowError 表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。而 OutOfMemoryError 是指当线程申请栈时发现栈已经满了,而且内存也全都用光了

本地方法栈 当调用的是原生native方法的时候,需要寄存到本地方法栈当中
虚拟机栈 专门为调用jvm内部方法所提供的一个栈

但是在主流的Hotspot虚拟机中本地虚拟栈和虚拟机栈已经被融合成了一体,所以并没有过多的区别。

在这里插入图片描述

2-1 操作数栈

  • 在方法的执行过程中,各种字节码指令会往操作数栈中写入和提取内容(如上图),也就是出栈/入栈操作,操作数栈的深度都不会超过在code属性中的maxstacks数据项中设定的最大值,如果当前线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

2-2局部变量表

  • 记录变量
  • 局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,一个Slot可以存放一个32位以内的数据类型,每个槽都应该能存放一个boolean、byte、char、short,int,float、reference或returnAddress类型的数据。
  • 对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的引Slot空间。Java语言中明确的(reference类型则可能是32位也可能是64位),64位的数据类型只有long和double两种。
  • 为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。

2-3 动态链接

  • 将符号引用解析为直接引用

符号引用即用用字符串符号的形式来表示引用,其实被引用的类、方法或者变量还没有被加载到内存中。而直接引用则是有具体引用地址的指针,被引用的类、方法或者变量已经被加载到内存中。以变量举个例子:

//符号引用
String str = "abc";
System.out.println("str=" + str);
// 直接引用
System.out.println("str=" + "abc");

2-4 返回值地址

  • 就是当方法执行完成后,需要返回到调用方的地址,以保证程序的继续往下运行。

invoke(指令集)

  1. invokeStatic:调用静态方法
  2. invokeVirtual:调用虚方法,运行期动态查找的过程,多数调用方法,自带多态
  3. invokeInterface:抵用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的哪个对象的特定方法
  4. inovkeSpecial:调用构造方法,private方法,可以直接定位不需要多态的方法
  5. invokeDynamic:lambda表达式或者反射或者其他动态语言(scala,kotlin)或者GCLib

3. 堆(Heap)

在这里插入图片描述

所有的对象实例以及数组都应当在运行时分配在堆上

几乎所有的对象实例都在这里分配内存,但是少数情况下,堆可以额外开辟一个空间用于给线程存储一些属于它们专有的buffer。这种技术叫做TLAB,属于栈上分配技术。

数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。

垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:

  • Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区

    Young Generation Space : 新生区 Young/New
    又被划分为Eden区和Survivor区
    Tenure generation space: 养老区 Old/Tenure
    Permanent Space:永久区 Perm

  • Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间

    Young Generation Space:新生区 Young/New
    又被划分为Eden区和Survivor区
    Tenure generation space :养老区 Old/Tenure
    Meta Space :元空间 Meta

Java堆区用于存储Java对象实例,堆的大小在JVM启动时就已经设定好了,可以通过选项-Xmx-Xms来进行设置。

  • -Xms:用于表示堆区的起始内存,等价于-xx:InitialHeapSize
  • -Xmx:则用于表示堆区的最大内存,等价于-XX:MaxHeapSize

4. Method Area(逻辑概念)

  • 方法区是的一个逻辑部分,它与 Java 堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾回收。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。内存回收效率低,回收一遍内存之后可能只有少量信息无效
  • 1.8之间也可以称作PermSpace,在1.8之后称作MetaSpace。
  • 方法区的大小决定了系统可以保存多少个类如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:ava.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace

tips

  1. perm space < 1.8

    字符串常量位于PermSpace

    FGC不会清理

  2. meta space >= 1.8

    字符串常量位于堆

    会触发FGC清理

运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

5. 直接内存(Direct Memory)

在JDK4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 方法库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。相当于jvm可以直接访问的内核空间的内存(OS管理的内存),NIO提高效率0拷贝

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值