JVM:内存自动管理机制(内存结构)

一、目标

        了解 JVM 内存结构是如何划分的,以及每个区域的具体内容。

二、前言

        我们知道 Java 程序会由 .java 文件编译成 .class 文件,由 JVM 加载到内存形成类型模板,供程序运行期创建相应的对象使用,每个对象的作用、生命周期可能各不相同,如果加载到内存描述类型信息的 .class,以及创建的对象在内存中没有任何划分,当我们在使用这些数据时,结果可想而知,我今天要穿的夹克在哪里呢?我哪件陈旧、破大洞的裤子好像还没丢掉?房间都堆成垃圾堆了… 找了哪么久,好辛苦呀!
在这里插入图片描述
        下面,我们来看看 Java 对于内存如何划分,划分了哪些区域,每个区域有什么作用。

三、Java 内存结构

        Java 虚拟机在执行 Java 程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,这些数据区域可分为两个部分:

  • 线程共享的,会随着虚拟机启动而创建,随着虚拟机退出而销毁;

  • 线程私有的,与线程一一对应,随着线程开始和结束而创建、销毁;

        在《Java虚拟机规范(Java SE 8)》中描述了 JVM 运行时内存区域结构如下:
在这里插入图片描述

线程共享的数据区

        线程共享的数据区具体包括 方法区 两个区域:

1)、堆

        堆的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

        由于它的唯一目的就是存放对象实例,因此它也是垃圾收集器管理的主要区域,故也称为“GC堆”。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以堆中还可细分为新生代和老年代,再细致一点还可分为Eden、From Survivor、To Survivor空间等。关于GC的介绍,请移步JVM:垃圾回收机制

下图给出堆的结构图:
在这里插入图片描述
        注意,堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。而且,它可以是固定大小的,也可以是可扩展的(通过-Xmx和-Xms控制),如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

2)、方法区

        方法区与堆一样,也是线程共享,并且不需要连续的内存区域,它用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。

        根据虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

        (1)、运行时常量池

        Java 常量池实际上分为两种形态:静态常量池 和 运行时常量池

  • 静态常量池 ,即 *.class文件 中的常量池。class文件 中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,它们占用class文件绝大部分空间。

  • 运行时常量池 ,则是 JVM 在完成类装载操作后,将 class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

        运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放编译期生成的各种 字面量符号引用其中,字面量比较接近 Java 语言层次的常量概念,如文本字符串、被声明为 final 的常量值等;而符号引用则属于编译原理方面的概念,包括以下三类常量:类和接口的全限定名、字段的名称和描述符 和 方法的名称和描述符。

        因为运行时常量池(Runtime Constant Pool)是方法区的一部分,那么当常量池无法再申请到内存时也会抛出 OutOfMemoryError 异常。

线程私有的数据区

        线程私有的数据区 包括 程序计数器虚拟机栈本地方法栈 三个区域:

1)、程序计数器

        我们知道,线程是CPU调度的基本单位。在多线程情况下,当线程数超过CPU数量或CPU内核数量时,线程之间就要根据 时间片轮询抢夺CPU时间资源。也就是说,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。

        因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码指令地址。

        因此,程序计数器是线程私有的一块较小的内存空间,其可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的字节码指令的地址;如果正在执行的是 Native 方法,则计数器的值为空。

        程序计数器是唯一一个没有规定任何 OutOfMemoryError 的区域。

2)、虚拟机栈

        虚拟机栈描述的是 Java 方法执行的内存模型,是线程私有的。每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,而且每个方法从调用直至执行完成的过程,对应者一个栈帧在虚拟机栈中入栈到出栈的过程。

        其中,局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)和对象句柄,它们可以是方法参数,也可以是方法内的局部变量。

        虚拟机栈有两种异常状况:

        a. 我们知道,一个线程拥有一个自己的栈,这个栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小),如果线程请求的栈深大于虚拟机所允许的深度,就抛出StackOverflowError异常;

        b. 栈的大小可以是固定的,也可以是动态扩展的,如果虚拟机可以动态扩展,但扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;

下图为栈帧结构图:
在这里插入图片描述

3)、本地方法栈

        本地方法栈与虚拟机栈非常类似,区别在于虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机执行Native方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。


引用

《深入理解Java虚拟机:JVM高级特性与最佳实践》
Java虚拟机规范(Java SE8)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值