JVM运行时数据区

Java和C++的区别,体现在自动内存分配垃圾收集技术

JVM在执行Java程序时,会将它管理的内存分为若干个不同的数据区域。

这些区域有各自的作用范围以及生命周期:

  • 线程私有的区域,随着用户线程的启动和结束而建立和销毁。
  • 线程共享的区域,随着虚拟机进程的启动而一直存在,JVM结束后销毁。

1、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,可以看做当前线程执行的字节码的行号指示器。是线程私有的。

JVM的字节码解释器工作时,就是通过改变这个计数器的值,来选取下一条需要执行的字节码指令。它是程序控制流的指示器。

1、为什么PC是线程私有的

首先,这个东西必须存在,因为肯定要记录下一条执行的指令是什么。

如果把这个东西做成线程共享的,那它的含义就是,当前正在执行的这个线程,下一条要执行的指令的地址

那么如果要发生线程上下文切换,新的线程必须告诉JVM,它的下一条指令的地址,同时,被切换走的那个线程也要保存它的下一条指令的地址

因此,为了线程切换后能恢复到上次执行的位置,就必须有线程私有的程序计数器。

2、PC的值

  • 如果线程执行的是一个Java方法,PC记录的是正在执行的虚拟机字节码指令的地址。
  • 如果线程执行的是一个Native方法,PC的值为空

PC这块内存区域,是唯一一个没有规定任何 OutOfMemoryError 情况的区域,永远不会发生内存溢出。

3、PC的作用

它的效果就是保存了下一条要执行的字节码指令地址,但是有两个作用:

  • 在本线程内:JVM的字节码解释器通过程序计数器来读取指令,所以PC可以进行程序的流程控制,比如顺序、循环、分支
  • 在多线程的情况下:程序计数器用于记录每个线程执行的位置,从而当线程被切换回来的时候能够得知该运行哪一条指令

2、虚拟机栈

虚拟机栈(VM Stack)也是线程私有的。

虚拟机栈描述的是Java方法执行的线程内存模型:

  • 每个方法被执行时,JVM都会同步创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 每个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表

通常说的,JVM中的“栈内存”,指的就是此处的虚拟机栈,或专指虚拟机栈中局部变量表部分。

局部变量表存放了编译期可知的局部变量的内容,包括基本类型和对象引用、returnAddress类型(指向一条字节码指令的地址)

局部变量表所需的内存空间在编译期间完成分配。局部变量表的基本存储单位是“局部变量槽(Slot)”。

进入一个方法时,这个方法需要多大的局部变量空间是完全确定的(槽数确定),在方法运行期间不会改变局部变量表的大小。

这个内存区域存在两类异常:

  • StackOverflowError:线程请求的栈深度大于虚拟机允许的深度

  • OutOfMemoryError:线程申请栈空间失败,内存不够。

    (HotSpot不支持栈容量动态扩展,只要线程申请栈空间成功就不会在运行过程中发生OOM)

3、本地方法栈

作用和虚拟机栈类似,不过虚拟机栈是为Java方法服务的,而本地方法栈是为Native方法服务的。

这部分没有强制规定,HotSpot虚拟机直接把虚拟机栈和本地方法栈合二为一了

4、堆

堆(Heap)是JVM管理内存中最大的一块,被所有线程共享,在虚拟机启动时创建。

堆的唯一目的就是存放对象实例,大多数对象实例都存在这里

堆是垃圾收集器管理的内存区域,所以也被叫做“GC堆”(垃圾堆)。

堆占用的内存空间是可以扩展的,用这两个参数设定:

-Xmx // 设置最大内存
-Xms // 设置最小内存

如果堆的内存不足(堆上没有空间进行实例分配,且无法往大扩展),就会抛出 OutOfMemoryError 异常。

1、堆内存是怎么细分的

堆中可以细分为新生代和老年代,其中新生代又分为Eden区、From Survivor区、To Survivor区,默认比例是8:1:1,可以调整。

5、方法区

方法区(Method Area)也是线程共享的。

它用于存储已经被虚拟机加载的类型信息、常量、类的静态变量、JIT 即时编译器编译后的代码缓存等数据

方法区属于堆的一个逻辑部分,但要和堆区分开理解。

1、方法区的具体实现

并未规定具体实现方式,可以由虚拟机自行实现。

在JDK 8以前,HotSpot选择使用“永久代”实现方法区,这样可以让垃圾回收器也管理这部分内存,不用专门为这个空间写一个垃圾回收策略。但这样也有弊端,使得更容易发生内存溢出,因为永久代有内存上限

在JDK 6时,已经放弃使用永久代,逐步改为使用“本地内存”来实现方法区。

在JDK 7,已经把原本放在永久代的字符串常量池、静态变量等移出

到了JDK 8,完全废弃了永久代的概念,把内容全部移到了“元空间”中。

垃圾回收行为在方法区比较少见。但如果方法区内存不足,也会抛出 OutOfMemoryError 异常。

比如频繁使用动态代理创建出一堆类型,就会占据方法区内存

2、方法区和永久代的关系

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。

永久代就是HtoSpot对方法区的一种实现,其他虚拟机中是没有永久代的。

3、为什么要舍弃永久代,使用元空间

  • 永久代有内存上限,由JVM管理,无法进行调整,容易发生内存溢出。元空间在直接内存中,直接内存是受本机可用内存的影响,相对更不容易内存溢出
  • 元空间是方法区的实现,里面存放了类的元数据。元空间更大,那么能加载的类就更多了
  • 在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有⼀个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代

6、运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分

Class文件中有“常量池表(Constant Pool Table)”,用于存放编译器生成的各种字面量与符号引用,在类加载完成后,这部分内容会存放在方法区的运行时常量池中。

运行时常量池具备动态性。Java语言并不要求常量一定只有编译期才能产生,即并不是只有Class文件中常量池表的内容能进入方法区的运行时常量池,运行期间也可以将新的常量放入常量池,比如String类的intern()方法。

运行时常量池受到方法区内存的限制,如果无法申请到足够的内存,也会抛出 OutOfMemoryError 异常。

存放的位置

  • 在JDK1.7前,运行时常量池+字符串常量池是存放在方法区的永久代中
  • 在JDK1.7中,字符串常量池从方法区移到堆中,运行时常量池还保留在方法区中
  • 在JDK1.8中,使用元空间实现方法区,此时字符串常量池依然保留在堆中,运行时常量池依然保留在方法区中,但此时的方法区处于元空间

7、直接内存

JDK1.8之后,方法区放在了元空间中,元空间就在直接内存中。

直接内存(Direct Memory)不是JVM运行时数据区的一部分,但也被频繁使用,也可能导致OOM。

在JDK 1.4中加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式。

它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆内的DirectByteBuffer对象作为这块内存的引用进行操作。这样就能提高性能,避免了在Java堆和Native堆中来回复制数据。

直接内存受到主机总内存(物理内存、分页文件)的限制,如果设置得比实际内存更大,在动态扩展时就会发生内存不足,从而到最后OOM。

8、总结

JVM将内存主要分为了两大部分:。其中堆是线程共享的,而栈是线程私有的。

  • 堆:用于存储对象实例
  • 栈:服务于每个线程方法的执行。
    • 每个方法执行时,JVM会同步创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息。
    • 一个方法的执行过程,对应着一个栈帧的入栈和出栈过程。

栈分成了两部分:

  • 虚拟机栈:面向Java方法
  • 本地方法栈:面向Native方法

要记录下一条执行的指令位置,并且控制线程的切换,需要设计一个线程私有的程序计数器

  • 程序计数器:
    • 负责存放当前线程的字节码执行位置。字节码解释器根据PC的值来选取字节码指令去执行。
    • 有两个作用:进行程序流程控制、保存上次执行到的字节码指令,线程切换回来后接着执行,所以它是线程私有的

HotSpot虚拟机在堆上额外开辟了一块内存,作为方法区

  • 方法区:
    • JDK1.8之前在堆上的永久代中,JDK1.8之后在直接内存的元空间中。
    • 用于存储已经被类加载的类、常量、类的静态变量、JIT 即时编译器的代码缓存

为了对常量进行优化,在方法区中开辟了运行时常量池

  • 运行时常量池:是方法区的一部分,存放编译器生成的各种字面量与符号引用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值