JVM区域划分以及那些区域可能产生OOM(out of memory)

通常情况下,我们可以把 JVM 的内存区域划分为以下几个部分,其中,有的区域是以线程作为单位,而有的区域则是整个 JVM 进程唯一的:

1.程序计数器

在 JVM规范中,每个线程都有自己的程序计数器,并且任何时间一个线程只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的java 方法的 JVM 指令的地址;但是,如果正在执行的是本地方法,则未指定值。

2.Java 虚拟机栈

虚拟机栈,早期也被称之为 Java 栈。每个线程在被创建时,都会创建一个虚拟机栈,其内部保存了一个个栈帧,对应着一次次 Java方法的调用。
在上面的计数器中,我们提到了任何一个时间点,一个线程只能执行一个方法,也就是当前方法,类似的,任何一个时间点,一个线程只会有一个活动栈帧,通常叫当前帧,方法所在的类为当前类。如果说该方法中又调用了其他方法,对应的会创建新的栈帧,这个栈帧成为了新的当前帧,一直到它返回结果或者执行结束为止。
JVM 直接对 Java 栈的操作只有两个,即对栈帧的压栈和出栈。
栈帧中存储着局部变量表,操作数栈,动态链接,方法正常退出或者异常退出的定义等。

3.堆

堆是 Java 内存管理的核心区域,用来放置 Java 实例对象。堆被所有的线程共享,在启动虚拟机时,我们可以通过指定 -Xmx来指定最大的堆空间等指标。

堆也是垃圾回收器重点照顾的对象,所以堆内空间还会被不同的垃圾回收器进一步细分,其中最有名的就是新生代,老年代的划分。

4.方法区

方法区也是被所有线程共享的一块内存区域,用于存储元数据,例如类的结构信息,以及对应的运行时常量池,字段,方法代码等。

在早期的 JVM 实现中,很多人习惯将方法区称为永久代,但是在 JDK 8 中已经将其删除,同时新增了元数据区。

5.运行时常量池

它是方法区的一部分。如果你有分析过反编译的类文件结构,你能看到版本号,字段,方法,父类,接口等各种信息,还有一项信息就是常量池。Java的常量池可以存放各种常量信息。

6.本地方法栈

它和虚拟机栈很类似,支持对本地方法的调用,同样也是每个线程创建一个。

下面上一张 JVM 内存区域划分的图:

在这里插入图片描述

哪些区域可能发生 OOM(Out of memory)? 内存溢出通俗的讲就是内存不够用了,并且 GC
通过垃圾回收也无法提供更多的内存。实际上除了程序计数器,其他区域都有可能发生 OOM, 简单总结如下:

  1. 堆内存不足是最常见的 OOM 原因之一,抛出错误信息 java.lang.OutOfMemoryError:Java heapspace,原因也不尽相同,可能是内存泄漏,也有可能是堆的大小设置不合理。
  2. 对于虚拟机栈和本地方法栈,导致 OOM 一般为对方法自身不断的递归调用,且没有结束点,导致不断的压栈操作。类似这种情况,JVM实际会抛出 StackOverFlowError , 但是如果 JVM 试图去拓展栈空间的时候,就会抛出 OOM.
  3. 对于老版的 JDK, 因为永久代大小是有限的,并且 JVM 对老年代的内存回收非常不积极,所以当我们添加新的对象,老年代发生 OOM的情况也非常常见。
  4. 随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的 OOM 有所改观,出现
    OOM,异常信息则变成了:“java.lang.OutOfMemoryError: Metaspace”。
    在这里插入图片描述
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值