JVM内存区域与内存溢出异常

JDK1.7、1.8的JVM内存模型,哪些区域可能发生OOM(out of memory)内存溢出 JDK1.7、1.8的JVM内存模型,哪些区域可能发生OOM(out of memory)内存溢出! 阅读详情

序言:在java应用中,内存溢出是很常见的问题.只有了解内存区域的分布和各自负责的功能,我们才能在编写代码时注意编码质量,并且在发生溢出异常的时候能够根据异常提升快速找到溢出原因。

 

1:JVM虚拟机所管理的内存包括以下几个区域,如图所示:

 1.1 Java堆内存

对于我们应用环境来说,堆内存的占比是Jvm所管理内存是最大的。该内存是被所有线程所共享的,在虚拟机启动的时候被分配。

应用程序中大部分的对象的实例和数组都会在堆上被创建(涉及JIT,对象逃逸技术)。这一块也是垃圾回收器管理的主要区域(后续会在垃圾回收机制中描述)

1.2 方法区

方法区和java堆一样,也是被线程所共享的,该区域主要存放的是虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码(这是一种规范)。不论是jdk1.6-jdk1.7中的永久代还是jdk1.8的元空间其实都是方法区这个规范的具体实现(针对的是Sun HotSpot虚拟机来说)。下面是HotSpot对于方法区变更:

  1. 常量池从方法区移到堆中(jdk1.7开始)
  2. 取消了永久代,使用元空间(Metaspace)替代(1.8开始)
  3. 那么对应的虚拟机加载的类信息(class matadata)也从1.7的永久代中转移到了元空间中
  4. 永久代中的静态变量转移到堆内存中(jdk1.8开始)
  5. 永久代参数PermSize(设置方法区初始值大小)和 MaxPermSize(设置方法区最大内存大小) 替换为元空间参数MetaspaceSize(设置元空间初始值大小)和 MaxMetaspaceSiz(设置方法区最大内存大小)(jdk1.8开始)

为何oracle的sun团队从1.8开始舍弃永久代而启用元空间。原因在于:

  1. 每一个类被加载的时候都将自己类元数据加载到永久代中,而永久代的内存空间是有大小限制的(元空间它使用的是本地内,它的内存瓶颈只会是机器本身可分配的最大内存),一旦需要记载类过多,可能会存在内存溢出的风险;
  2. 类及方法的信息难以确定其大小,所以对于永久代的大小指定比较困难,太小容易出现永久代内存溢出,太大则容易导致老年代(小了)溢出。
  3. HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。永久代会为GC带来不必要的复杂度,并且回收效率偏低

1.3 Java虚拟机栈

虚拟机栈是被线程所私有的,它的生命周期和线程相同(创建线程同时会创建与之相对应的虚拟机栈)。虚拟机栈为Java方法(也就是字节码)的执行提供支持:在每个方法被执行的时候会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每一个方法从调用到结束,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表:是一组变量值存储空间,用于存放方法参数和方法定义的局部变量。例如编译期就可知的8大基础数据类型(boolean,byte,char,short,int,float,long,double),对象引用(对象本身存储在堆中)和returanAddress类型 (指向了一条字节码指令的地址)。

操作数栈:它是一个先入后出栈,最大深度也在编译时写入Code属性的max_stacks数据项中,里面存储的元素可以是任意Java类型,32位数据类型占栈容量为,64位数据类型所占栈容量位2。当一个方法刚执行的时候该栈位空,在方法的执行过程中会有各种字节码指令往操作数栈中写入和提取。列入在做算数运算的时候是通过操作数栈来进行的;或者在调用其它方法的时候也是通过操作数

方法出口信息(方法返回地址):当一个方法执行只有两种可以结束,第一种当执行引擎遇到任意一个方法返回的字节码指令这种为正常完成出口,根据遇到字节码指令不同确定是包含返回值。第二种当方法执行中遇到异常,并且异常在方法体中未得到异常处理,这种退出方式为异常完成出口。

1.4 本地方法栈

与虚拟机栈的功能很相似,也是线程私有的,只不过Java虚拟机栈为虚拟机执行方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

被native关键字修饰的方法一般称为本地方法,该方法的实现由非java语言实现。例如在某java应用程序出有某个需求需要调用底层硬件,而java是无法做到,这个时候我们一般使用c语言来实现底层的调用,再通过native关键字表明使该程序可以在java中使用(其实java源码中本身就存在大量调用本地方法,有兴趣可以了解下Java中Unsafe类)。

Hotspot虚拟机将本地方法栈和虚拟机方法栈合二为一。

1.5程序计数器

和虚拟机栈一样,程序计算器也是线程私有的。它本身是一快很小的内存,用于记录着当前线程所执行的字节码(方法)的行号.

java中的多线程的执行是轮流切换并分配cpu的执行时间来实现的,那么在任何一个时刻一个cpu( 如果是多核处理器指的是一个内核)只会执行任意一个线程中代码,为了保证线程切换后能够恢复到正确的执行位置,每个线程都需要一个独立的组件去记录当前线程执行的位置,这个组件就是程序计数器。

如果线程执行的是一个java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址,如果执行的是一个Native方法那么这个计数器为空(Undefined).

1.6直接内存

直接内存并不是JVM管理的内存区域, 它实际指定的jvm运行环境中的机器内存。Java中对于此内存处理依赖于Unsafe提供的操作堆外内存的native方法。DirectByteBuffer是Java用于实现直接内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer( DirectByteBuffer的构造函数是包级私有的,因此外部是调用不到的。可以通过ByteBuffer.allocateDirect()构建)对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。

2:内存溢出异常

2.1 java堆内存溢出

java中对象实例的存储在堆中,如果不断的创建对象,并垃圾回收器无法回收清除这些对象时(可参考垃圾回收机制)。当堆中内存总量达到设定阀值时,就会产生内存溢出异常。如代码所示:

   public static void main(String[] args) {

        List<byte[]> datas = new ArrayList<>();

        while(true){
            datas.add(new byte[1024*1024]);
        }
}

通过参数设置堆内存大小:-Xms10m(最小堆内存数) -Xmx10m(最大堆内存数)。尽量使最大内存数与最小内存数一致避免java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。在测试项目稳定性时。可通过设置-XX:+HeapDumpOnOfMemoryError使当jvm方式内存溢出是Dump出当前内存堆转储快照帮助我们发现内存溢出点,完善代码。

2.2虚拟机栈和本地方法栈溢出

虚拟机栈和本地方法栈会产生两种异常

  1. 如果线程请求的栈深度大于虚拟机所容许的最大深度,此时就会抛出StackOverflowError异常。(无法评估的方法递归递归)很容易产生此异常
  2. 如果虚拟机在扩展栈时无法申请到足够的内存空间时,就会抛出OutOfMemoryError异常。

测试代码:

1:对于StackOverflowError异常非常容易产生,进行无终止的递归方法调用就可以产生

    //方法递归
    public static void test(){
        test();
    }

    public static void main(String[] args) {
        test();
    }

2:对于虚拟机栈产生OutOfMemoryError异常

/**
 * 测试虚拟机栈尝试OOM异常
 * 此代码会导致机器死机 在使用之前先保存当前工作 不然易丢失数据
 * -Xss=10m 设置 Java 线程堆栈大小
 * @author  fangyuan
 */
public class JavaStackOOM {

    private void dontStop(){
        while (true){

        }
    }

    public void test(){

        //不断创建线程 每创建一个线程都会创建与之对应的虚拟机栈
        while(true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        new JavaStackOOM().test();
    }
}

运行结果:Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread;

因为Hotspot虚拟机将虚拟机栈和本地方法栈合并在一起,-Xoss参数(设置本地方法栈大小)已无用。对于栈容量只能通过-Xss参数设置。

2.3方法区内存溢出

测试代码:

/**
 * 通过cglib动态产生大量的类 导致方法区内存溢出
 *-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m jdk1.8
 *-XX:PermSize=10m -XX:MaxPermSize=10m jdk1.7
 * @author fangyuan
 */
public class MethodMemoryErrorTest {

    public static void main(String[] args) {

        while (true) {

            Enhancer enhancer = new Enhancer();

            enhancer.setSuperclass(Test.class);

            enhancer.setUseCache(false);

            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }

    }

    /**
     * 测试类
     */
    static class Test {}
}

2.3.1: 对于jdk1.7之前是永久代内存溢出:

 

2.3.2:对于jdk1.8之后是元空间内存溢出:

2.4直接内存溢出

我们可以通过-XX:MaxDirectMemorySize指定。如果不指定默和Java堆最大值(-Xmx指定)一样。

测试代码:

/**
 * 这里通过调用Unsafe方法直接操作直接内存
 * -XX:MaxDirectMemorySize=20m 设置直接内存使用最大数为50m 快速产生效果
 * @author fangyuan
 */
public class NativeMemoryOOM {

    private static final int _1MB = 1024 *1024;

    public static void main(String[] args) throws Exception {

        Field field =Unsafe.class.getDeclaredFields()[0];

        field.setAccessible(true);

        Unsafe unsafe =  (Unsafe)field.get(null);

        while(true){
            unsafe.allocateMemory(_1MB);
        }

    }
}

借鉴:周志明老师的<深入理解Java虚拟机>

JVM了解以下?JVM系列~内存区域与内存溢出异常 JVM存在的目的和意义是什么?为什么要学习JVM? JVM是Java Virtual Machine的缩写,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。 本文着重分析JVM中运行... 阅读详情

相关推荐

【iOS开发】dyld: Symbol not found: ___chkstk_darwin 问题解决

【iOS开发】dyld: Symbol not found: ___chkstk_darwin 问题解决 Xcode 13.2.1 运行到iOS12.x版本上崩溃报错如下:dyld: Symbol not found: ___chkstk_darwin dyld: Symbol not found: ___chkstk_darwin Referenced from: /private/var/containers/Bundle/Application/A424CEFD-51DB-450E-9D62-B8

wm9028的专栏 6563

【JVM】Java内存溢出分析(堆溢出、栈溢出、方法区溢出、直接内存溢出)

Java内存溢出的原因为程序在申请内存(堆/栈/方法区/直接内存等等)时,没有足够的内存空间。

小明的Java问道之路 5232

混合开关电容DCDC变换器技术解析与工程应用指南

在电源设计领域,传统DCDC变换器在高降压比或高升压比场景下,往往面临占空比过窄、开关应力大、效率偏低等瓶颈。开关电容变换器虽能实现固定比例的电压变换,却受限于调节能力。混合开关电容DCDC变换器通过“开关电容网络粗调变比+电感式DCDC细调输出”的策略,兼顾高效率、高功率密度与宽调压范围。本文梳理了其典型拓扑(如2:1/4:1 SC+Buck)、关键对比、控制策略(含软充电)及工程应用中的常见问题,帮助电源工程师在服务器供电、快充、车载电源等场景下快速选型与调试。

weixin_29164185的博客 312

使用jdk1.7运行时遇到的堆内存溢出问题

加上这句话: -server -Xms512M -Xmn256M -Xmx1024M -XX:PermSize=512M -XX:MaxPermSize=1024m。1. 修改tomcat配置。

m0_50573190的博客 369

virtualbox虚拟机里面最顶部没有控制、设置和帮助等选项

无意中操作,导致virtualbox虚拟机里面最顶部没有控制,设置和帮助选项  virtualbox虚拟机窗口显示如下如: 如何设置,窗口显示如下图所示呢: virtualbox虚拟机窗口模式显示设置,切换可以通过如下图所示操作进行切换: PS:一定要是右边的Ctrl+C

WEL测试 8780

企业AI Agent的审计与合规

本文将带你从**“0基础理解企业AI Agent审计与合规”到核心概念梳理:用大白话+学术定义拆解什么是“企业AI Agent”、“可审计性(Auditability)”、“可解释性(Explainability,XAI)≠可审计性”、“合规性(Compliance)”、“黑盒拆解的三层维度”等硬核但必须掌握的概念;风险与合规背景:从**内部风险(数据泄露、误操作、模型偏差)和。

Java大师兄的博客 301

java异常_异常、堆内存溢出、OOM的常见情况及解决方案

堆内存溢出【情况一】:java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环;如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决:< jvm-arg>-Xms3062m < / jvm-arg>< jvm-arg>-Xmx3062m < /...

weixin_39628594的博客 626

当OTN网络遭遇LOS告警:5个运维老手才知道的故障定位技巧(含CA动作对照表)

本文深入解析OTN网络中LOS告警的实战定位技巧,涵盖光功率深度诊断、模块差异分析及核心的CA动作机制。通过逆向追踪告警流与对照速查表,帮助运维人员快速锁定故障段落,区分硬软故障,并掌握系统健康度评估方法,从而提升排障效率与网络稳定性。

weixin_29266007的博客 551

jdk1.7中使用@Async注解导致线上生产环境栈内存溢出

在@Async注解下配置的线程池是ThreadPoolExecutor 在某个服务调佣这个方法便会从线程池获取一个线程执行 解决办法就是把jdk版本换到1.8以上,线程池才能正确的创建线程和调度线程。

qq_34612657的博客 1092

3D 结构光相机工作原理全解 | 从编码投射到三维重建,原理 + 代码 + 工业实战一站式掌握

本文从3D结构光相机的整体架构出发,层层拆解了编码投射、相位计算、相位解包裹、三维重建的核心原理,补充了工业实战所需的公式推导、代码实现、参数调优、设备选型和案例落地,核心总结如下:相位是3D结构光成像的“灵魂”,其精度、稳定性直接决定3D重建的效果,包裹相位与绝对相位的区分的是工业实战的基础;编码投射的核心是“正弦条纹+格雷码条纹”组合,正弦条纹负责高精度相位提取,格雷码条纹辅助解包裹,适配工业场景的精度与稳定性需求;

getusushu的博客 414

手把手教你学PCIE(10.5)--PCIE故障排除方法的分步排查法介绍

通过以上分步排查法,你可以系统地诊断和解决 PCIe 设备的故障。每个步骤都有助于逐步缩小问题范围,最终找到并解决问题。

MHD0815的博客 1871

JVM之Java内存区域与内存溢出异常

例如,在JDK 6或更早版本中,String.intern() 方法会将其内容复制到永久代的字符串常量池中,如果调用 intern() 的字符串过多,也会导致永久代溢出。检查应用是否使用了动态生成类的技术,或者是否存在类加载器泄漏(例如,在Web容器中重复部署应用时,旧的类加载器及其加载的类没有被卸载)。,是指程序在申请内存时,没有足够的内存空间供其使用。如果是内存溢出,检查业务逻辑是否合理,或者考虑增加堆内存大小(-Xmx),优化数据结构(如使用更节省内存的结构),或者分批处理大数据。

2301_77037373的博客 830

OpenHarmony WiFi子系统全解析:从架构到代码实现(大合集)

头文件路径[相对路径][相对路径][相对路径][相对路径]// 网络配置结构 (wifi_device_config.h)// SSID字符串// 预共享密钥// 安全类型// 网络ID// 优先级// 是否隐藏网络// 扫描信息结构 (wifi_scan_info.h)// SSID// BSSID (MAC地址)// 安全类型int rssi;// 信号强度// 频率// 信道宽度// 时间戳// 连接信息结构 (wifi_linked_info.h)

m0_67505153的博客 1059

JVM-JAVA内存区域与内存溢出异常

C/C++拥有着绝对的内存管理权限,但是也正因如此控制这些内存从无到有,再从有到无就异常繁琐。但是java不需要担心这些,他们把他全权交给了虚拟机去实现,也正因如此,如果你不了解java虚拟机,那么一旦出现了内存泄漏和溢出方面的问题,解决起来将会异常繁琐。

qq_74763440的博客 1371

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

java虚拟机阅读笔记----jvm内存区域与内存溢出异常

jvm内存区域与内存溢出异常 内存布局 java虚拟机的内存主要划分为几个部分: 运行方法区、java堆,程序计数器、虚拟机栈、本地方法栈。如下图所示: 程序计数器 程序 计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号。 java虚拟机栈 java虚拟机栈和程序计数器一样是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方...

sinat_27952629的博客 272

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

JVM-java内存区域与内存溢出异常

JVM-java内存区域与内存溢出异常 说明 运行时数据区域 1 程序计数器 2 java虚拟机栈 3 本地方法栈 4 java堆 5 方法区 对象的创建 一个有意思的现象 JVM-java内存区域与内存溢出异常1 说明java 与 c++之间有一堵由内存动态分配和垃圾回收技术所围成的高墙,墙外的人想进来, 墙内的人想出去。然而java的使用者就是这些墙里的人。这篇文章就是介绍java虚拟机内存的各

努力在追梦的路上 1326

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

JVM-Java内存区域与内存溢出异常总结

在读《深入理解Java虚拟机》一书中,对整个jvm体系有了一个深入理解。本文对Java内存区域与内存溢出异常做一个知识总结。 运行时数据区: java虚拟机管理的内存会分为: 线程私有:程序计数器,虚拟机栈,本地方法栈,程序计数器, 线程共享:方法区,堆。 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,通过程序计数器来获取下一条指令。(之所以这部分是线程私有的原因是:在线程切换后能恢复到原来正确的执行位置,每个线程都必须有一个程序计数器,各个程序计数器

weixin_45787987的博客 233

JVM学习(一)Java内存区域与内存溢出异常

Java内存区域与内存溢出异常 文章目录Java内存区域与内存溢出异常????运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存????HotSpot虚拟机对象对象的创建对象的内存布局对象的访问定位????实战:OutOfMemoryError异常Java堆溢出虚拟机栈和本地方法栈溢出方法区和运行时常量池溢出本机直接内存溢出????JVM参数设定 ????运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的

像写诗一样优雅地写代码~ 5615

tomcat内存溢出解决方案_JVM了解以下?JVM系列~内存区域与内存溢出异常

JVM存在的目的和意义是什么?为什么要学习JVM? JVM是Java Virtual Machine的缩写,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成...

weixin_39836726的博客 137

《深入理解JVM》第二章 Java内存区域与内存溢出异常

-

给时光以生命 287

JVM自动内存管理(一):Java内存区域与内存溢出异常

JVM自动内存管理(一):Java内存区域与内存溢出异常 (一)运行时数据区 线程共享: 方法区(JDK8含运行时常量池)、堆内存(JDK8含字符串常量池) 线程私有:栈内存(虚拟机栈、本地方法栈)、程序计数器PC 直接内存:与NIO有关 程序计数器PC Native方法 定义:一个Native Method就是一个Java调用非Java代码的接口。方法的实现由非Java语言...

See what you want to see 251

JVM学习--(一)Java内存区域与内存溢出异常

Java内存区域与内存溢出异常 一.Java技术体系 JDK:Java程序设计语言、JVM、Java类库三部分所构成 支持Java程序开发的最小的环境 JRE:JVM以及Java类库API的Java SE API子集两部分所构成 支持程序运行的标准环境 二.JVM内存区域分布 1.程序计数器 程序计数器本质上是标记下一条需要执行的字节码指令。特点: 1.线程私有,每个线程都有自己的程...

jacobbbbbbbb的博客 210
上一篇: Java中HashMap底层实现
下一篇: Java中的逃逸分析
F_Hello_World
博客等级 码龄11年 38粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值