前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM内存区域

JVM内存区域

原创
作者头像
用户4439777
修改2020-08-03 10:38:47
9320
修改2020-08-03 10:38:47
举报
文章被收录于专栏:用户4439777的专栏
简介

JVM虚拟机,也就是虚拟的计算机,它有自己虚拟的CPU、虚拟的内存等等,当然还有大名鼎鼎的垃圾回收器。第一篇我们来讲解一下JVM的虚拟内存。

Java运行时内存区域

Java虚拟机在启动时会根据JVM参数向操作系统申请内存,并将申请到的内存划分为不同的区域。这些区域的作用各不相同,有的区域在JVM启动时就已初始化并一直存在,有的区域则依赖于用户线程的启动和结束而建立和销毁。JVM的内存区域包含以下几个运行时数据区(图摘自深入理解JAVA虚拟机第三版)。

方法区

方法区(Method Area)是共享的内存区域,它用于保存JVM加载过的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。在jdk1.8以前的HotSpot中,方法区是通过永久代(Perm)实现的。永久代通过-XX:PermSize和-XX:MaxPermSize指定,默认情况下-XX:MaxPermSize为64MB。一个大的永久区可以保存更多的类信息,如果永久区的内存被占满可能会发生内存溢出。值得注意的是,如果大量使用了动态代理,可能会在运行时产生大量的类而造成OOM。可以看到,用永久代实现的方法区不是一个很好的设计,由于运行时的情况很难预估,而永久代又必须指定一个MaxPermSize,由此操作的OOM时有发生。

在jdk1.6之后,HotSpot就已经有逐步放弃永久代转而通过本地内存来实现的计划了。到jdk1.7时的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出到了堆中。到了jdk1.8已经用元数据区MetaSpace代替了永久代。MetaSpace如果不指定内存大小,理论上可以使用计算机内存的最大值。

和方法区一样,堆(Heap)也是共享的内存。方法区共享的是类信息和各种类对象,而堆里存放的是对象的实例和数组。堆是JVM中最大的一块内存空间,也是最容易发生OOM的内存空间。我们常说的垃圾回收器就是用来管理堆的,而垃圾回收器的算法又与JVM的响应时间和吞吐量密切相关,可以说堆是JVM中最重要的一块内存区。根据垃圾回收器的算法不同,堆也有不同的结构。关于堆的结构我们放在垃圾回收器的时候再探讨。

虚拟机栈

虚拟机栈是线程私有的,它的生命周期和线程相同。虚拟机栈里是一个或多个栈帧,线程每执行一个方法JVM都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和返回值地址。

局部变量表存放了编译时期可知的各种Java虚拟机基本数据类型、对象引用、和returnAddress类型。这些数据类型在局部变量表中的存储空间以局部变量槽来表示,其中64位长度的long和double类型的数据会占用两个槽,其余的数据类型只占一个。局部表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。注意这里所说的大小是指槽的个数,具体每一个槽占用给多少字节由各自的虚拟机实现来自行决定。

操作数栈主要用于保存计算过程的中间结果。当遇见计算时,JVM会把参与计算的局部变量压入操作数栈,执行计算操作时再从栈中取出进行计算。可能有人在这里有所疑问,为什么不直接进行计算而需要一个操作数栈呢?其实直接计算只适用于最简单的操作,一旦计算很复杂就需要操作数栈来复制记忆计算顺序。如果对此还有疑问可以搜搜后缀表达式实现计算器的博文。

动态链接指向运行时中常量池中该栈帧所属方法的应用,该引用的目的是支持动态代理。

返回地址是标记本方法结束,也就是本栈帧结束后要跳转到哪个栈帧的哪一行继续执行。

关于虚拟机栈有两个常见的异常,一个是StackOverflowError(如果线程请求的栈深度大于虚拟机允许的深度),一个是OutOfMemoryError(如果栈扩展时无法申请到足够的内存)。

本地方法栈

本地方法栈和虚拟机栈的功能和结构是相近的,其区别是本地方法栈是为虚拟机使用到的本地方法(Native)方法服务。

《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失 败时分别抛出StackOverflowError和OutOfMemoryError异常。

程序计数器

JVM的程序计数器和操作系统的计数器很类似,可以看作是当前线程所执行的字节码的行号指示器。程序计数器只占很小的一块空间,而且不会出现扩容的情况,是JVM里唯一不会OOM的内存区域。

运行时常量池

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

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,也就是说除了Class文件中常量池以外,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。

运行时在方法区中也受到方法区内存的限制,当无法申请到内存时会抛出OutOfMemoryError异常。

直接内存

直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中 定义的内存区域。但是这部分内存也被频繁的使用,而且也会导致OOM异常。

jdk 1.4中新加入NIO类,引入了一种基于通道(channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些情景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而引起OutOfMemoryError异常。

栈上分配

这里的栈指的就是虚拟机栈,分配指的是分配对象的实例。本来对象实例应该在堆中,受到垃圾回收器的管理,当大量的实例分配到堆上时可以引发OOM问题的产生。Java虚拟机提供了一种栈上分配的技术来解决这个问题,栈上分配的对象会随着方法的结束而自动释放。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • Java运行时内存区域
    • 方法区
        • 虚拟机栈
          • 本地方法栈
            • 程序计数器
              • 运行时常量池
                • 直接内存
                  • 栈上分配
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档