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中运行... 阅读详情

相关推荐

Creo报错代码-9?手把手教你重新生成许可证文件(基于Creo 10.0)

本文详细解析Creo报错代码-9的成因及解决方案,重点介绍如何重新生成许可证文件以应对网络环境变化导致的验证失败。通过分步指南和高级技巧,帮助用户有效管理多网络环境下的Creo许可证问题,提升工作效率。

weixin_30839881的博客 557

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

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

小明的Java问道之路 5232

让数学变得更生动manim库的使用(1)-基本概念

0、概述 数学理论与数学公式的展示总是一件很枯燥的事情,但3b1b的manim动画库在github上开源之后,让这一切变得非常简单。目前这个库已经分成两个版本一个由3b1b自身维护。但作者并没有承诺长期维护该库,所以由专门团队再次基础上研发了相应的社区版,并对该版本进行长期维护。本文的所有案例都基于社区版的官方案例。本文主要介绍该库的基本概念相关的案例,文章主要以代码为主,关键代码都有相关注释。 另外本文的运行环境维jupyter这与其他的ide环境运行代码稍微有些不同 1、类库引入已经相关测试 引

amao1998的博客 9965

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

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

m0_50573190的博客 369

python爬取《你好, 李焕英》豆瓣评论数据

# 导入工具包 import requests from bs4 import BeautifulSoup import time import pandas as pd import numpy as np # 请求头 headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36'} # =

python技巧分享 3456

3D Slicer5.7在win11下的源码编译安装

本博客主要参考官方安装文档以及部分优快云的博客,部分内容的书写会直接参考官方文档的内容。

Topsort的博客 4759

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

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

weixin_39628594的博客 626

11、rsa-目标主机支持RSA密钥交换【原理扫描】的漏洞

通过将 etcd 的 TLS 密钥交换算法从 RSA 升级为 ECDHE,可实现前向保密,彻底消除 “私钥泄露导致历史流量解密” 的风险。核心步骤是通过强制启用含ECDHE的套件,并禁用 RSA 密钥交换套件,同时建议升级到 TLS 1.2+ 并优化证书类型(优先 ECDSA)。这一配置符合云原生安全最佳实践,也是 Kubernetes 等依赖 etcd 的系统推荐的安全标准。生成临时密钥的具体步骤是什么?如何验证etcd是否已成功更新为ECDH密钥交换算法?配置临时密钥交换算法可能带来哪些性能影响。

xuebo1129927648的博客 1万+

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

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

qq_34612657的博客 1092

告别零散脚本:用Playwright+Pytest+Yaml+Allure搭建可维护的UI自动化项目

UI自动化测试是软件质量保障的关键环节,其核心在于模拟用户操作,对Web应用界面进行功能验证。传统的脚本堆砌方式常因代码耦合度高、维护成本大而难以为继。工程化解决方案通过分离关注点,将测试逻辑、数据和配置解耦,从而显著提升项目的可读性、可维护性和可扩展性。其技术价值在于构建可持续运行的自动化资产,而非一次性脚本。典型的应用场景包括Web应用的回归测试、兼容性测试及CI/CD流水线中的质量门禁。本文以Playwright、Pytest、Yaml和Allure为核心技术栈,详细阐述了如何从项目架构、页面对象模型

weixin_42529328的博客 299

【论文精读】Latent Diffusion

Latent Diffusion

weixin_44934783的博客 4476

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

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

2301_77037373的博客 830

SAP Single Sign-On 全景指南:从传统 SAP SSO 到 SAP Secure Login Service for SAP GUI 的架构、场景与迁移实践

本文系统梳理了SAP生态下的单点登录技术方案,重点分析了SAP GUI的云化替代方案SAP Secure Login Service(SLS)。内容涵盖SNC、Kerberos/SPNEGO、SAML 2.0等核心协议的技术实现,对比了传统SAP SSO与新型SLS for SAP GUI的架构差异,并给出AS ABAP系统的具体实施路径。针对SAP GUI访问、浏览器访问Fiori/移动端等不同场景,提供了详细的协议选型建议和配置指南,帮助团队构建安全可靠且适应云时代的统一认证体系。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 273

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

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

qq_74763440的博客 1371

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

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

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

sinat_27952629的博客 272

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

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

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

努力在追梦的路上 1326

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

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的博客 209
上一篇: 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、付费专栏及课程。

余额充值