• JVM 内存结构


    Java 虚拟机在运行 Java 程序 时,把它所管理的内存划分为若干个不同的数据区域,主要包括以下五个部分:程序计数器、Java 堆、Java 虚拟机栈、方法区和本地方法栈。

    JVM 内存结构

    程序计数器

    程序计数器是当前线程所执行的字节码的行号指示器,它会指出下一条将要执行的指令的地址,字节码解释器就是通过改变计数器的值来选取程序接下来执行的操作。

    程序计数器是线程私有的一小块内存,每条线程都要有一个独立的程序计数器,以使线程切换后恢复到正确的执行位置。

    • 如果线程正在执行 Java 方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
    • 如果执行 native 方法,则计数器为空

    它也是唯一一个不会出现 OutOfMemoryError 的内存区域。

    Java 虚拟机栈

    与程序计数器一样,Java 虚拟机栈也是线程私有的,在线程创建时 Java 栈会被创建,每个方法在在执行的同时都会创建一个栈帧,用于存放局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    一般所谓的“栈”,指的是虚拟机栈中局部变量表部分,其中存放了各种基本数据类型( 8 种),对象引用(reference 类型) 和 returnAddress 类型。局部变量表所需的空间在编译期就已经确定并完成分配,在方法运行期间不会被改变。

    Java 虚拟栈中可能出现两种异常:

    • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
    • OutOfMemoryError:虚拟机栈扩展时无法申请到足够的内存

    本地方法栈

    本地方法栈与 Java 虚拟机栈的作用类似,区别是 Java 虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈为虚拟机执行 Native 方法服务。有的虚拟机(例如 HotSpot 虚拟机)直接把本地方法栈和 Java 虚拟机栈合并在一起。

    本地方法栈也可能会抛出 StackOverflowError 和 OutOfMemoryError 异常。

    Java 堆

    Java 堆是是虚拟机中最主要的内存区域。它为线程共享,在虚拟机启动时创建,几乎所有的对象实例都存储在 Java 堆中。

    Java 堆也被称作 "GC" 堆。从内存回收角度看,可分为新生代和老年代。而新生代又可分为 Eden 区、From Survivor 区、To Survivor 区等。

    Java 堆的实现,既可以实现为固定的,也可以是扩展的。当前虚拟机都按照可扩展来实现,通过 -Xmx 和 -Xms 控制堆大小。

    如果堆中没有内存并且也无法再扩展时,会抛出 OutOfMemeoryError 异常。

    方法区

    方法区与 Java 堆一样,为线程共享。用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。也叫作 Non-Heap(非堆)。

    如果方法区无法满足内存分配需求,会抛出 OutOfMemoryError 异常。

    运行时常量池

    运行时常量池是方法区的一部分。Class 文件中的常量池用于编译期生成的各种字面量和符号引用,这部分内容在类加载后被存入运行时常量池。

    动态性是运行时常量池相对于 Class 文件常量池的一个重要特征,即不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中。

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

    直接内存

    直接内存并不由 JVM 管理,它是利用 Native 函数库在 Java 堆外申请分配的内存区域,可以避免在 Java 堆和 Native 堆中复制数据以提高性能。

    例如 NIO 中的 DirectByteBuffer 就可以作为这块内存的引用进行操作直接内存。

    永久代与元空间

    有时会看到方法区被称为永久代,其实两者有着本质的区别。方法区是 JVM 规范中的定义,而永久代是 JVM 规范的一种实现,并且只有在 HotSpot 虚拟机中如此,其他虚拟机中没有永久代的说法。

    在 JDK1.6 之前,HotSpot 虚拟机把 GC 分代收集扩展至方法区,或者说使用永久代实现方法区。不过永久代有 -XX:MaxPermSize 的上限,很容易遇到内存溢出问题。

    所以在 JDK1.7 中,将部分数据已经转移 Java Heap 或 Native Heap 中,例如:将原本放在永久代中的字符串池和类的静态变量移出到 Java Heap 中,将符号引用转移到 Native Heap 中。但永久代仍然存在,并没有移除。

    在 JDK1.8 中,取消了永久代,代替为元空间实现,它也是 JVM 规范中方法区的一种实现。不过它与永久代最大的不同是:元空间并不在虚拟机中,而是将元空间放到本地内存中。所以默认情况下,它只受本地内存的限制,可以通过 -XX:MetaspaceSize 参数设置初始空间大小,默认没有最大空间限制。

    常见的 OOM 及原因

    Java 中的 OOM 指的就是 java.lang.OutOfMemoryError 异常。主要有以下几种:

    java.lang.OutOfMemoryError:Java heap space

    Java 堆中主要用于存放各种对象实例。当堆中没有足够的空间分配给新对象时,或者说达到了堆空间设置的最大空间限制,则会抛出此异常。

    引起内存溢出的原因主要有:

    • 流量访问量大,超过设置的堆空间大小;
    • 内存泄露,不能被回收的对象消耗过多堆空间;

    java.lang.OutOfMemoryError:Permgen space

    在 JDK7 中,HotSpot 虚拟机使用永久代实现方法区,永久代较小,而且回收效率较低,很容易出现内存溢出。

    因此,JDK8 取消了永久代,使用元空间来实现方法区,存放在本地内存中。

    java.lang.OutOfMemoryError:Metaspace

    方法区主要存储类的元信息,HotSpot 元数据区。当元空间没有足够的空间分配给加载的类时,会抛出此异常。

    引起元数据区空间不足的原因主要有:

    • 加载的类太多,常见于 jsp 页面过多时;
    • 元空间被实现在堆外,主要受到进程本身的内存限制,一般很难出现溢出。
  • 相关阅读:
    无线传输终端 无线通信模块 全网通5G/4G
    字符设备驱动总结
    推荐一款基于业务行为驱动开发(BDD)测试框架:Cucumber!
    指针和数组试题解析(4)字符数组部分续集
    static
    python珍藏宝藏学习资料
    二十三种设计模式:解密职责链模式-购物优惠活动的设计艺术
    【电商数仓】数仓BI工具集成之Zabbix入门、部署、配置、启停、使用
    linux更换常用软件的默认缓存路径(.conda, .huggingface等)
    Lua网站开发之文件表单上传
  • 原文地址:https://blog.csdn.net/m0_61682705/article/details/132662884