• 深入理解Java虚拟机:Java内存区域与内存溢出异常


    1. 运行时数据区域

    JVM

    1.1 程序计数器:

    线程私有的内存区域,一块较小的内存空间,作用是当前线程所执行的字节码的行号指示器

    1.2 Java虚拟机栈:

    JVMStacks,也是 线程私有 的,生命周期与线程相同

    描述的是Java方法执行的内存模型:每个方法被执行的时候会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程

    一般网上说的栈内存指的是这里的局部变量表,局部变量表的内存空间在编译期间完成分配

    如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError,如果虚拟机栈动态扩展时无法申请到足够的内存会抛出 OutOfMemoryError

    1.3 本地方法栈:

    与JVMStacks的区别是,为虚拟机使用到的Native方法服务

    1.4 Java堆:

    被所有线程共享的一块内存区域,在虚拟机启动时候创建。对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。此内存区域的唯一目的就是存放对象实例

    Java堆也是垃圾收集器广利的主要区域,因此很多时候也被称作”GC堆“

    由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代,再细致一点的有Eden空间、From Survivor空间、To Survivor空间

    当前主流虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError

    • -Xms:设置进程堆内存的最小大小
    • -Xmx:设置进程堆内存的最大大小
    • -Xmn:设置堆内存新生代的大小

    1.5 方法区:

    与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError

    1.5.1 运行时常量池

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

    1.5.2 直接内存

    JDK1.4中新加入的NIO(New Input/Output)类,引入了一种基于通道(channel)与缓冲区(buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。


    2. 对象访问

    如果使用句柄访问方式,Java堆中将会划分出一块内存来作为句柄池,reference
    中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的
    具体地址信息,如图所示。

    在这里插入图片描述

    如果使用直接指针访问方式,Jva堆对象的布局中就必须考虑如何放置访问类型
    数据的相关信息,reference中直接存储的就是对象地址,如图所示

    在这里插入图片描述
    这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
    使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Jva中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。就这里讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象


    3. OutOfMemoryError异常

    3.1 Java堆溢出

    出现Java堆内存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap space”。要解决这个区域的异常,一般的手段是:

    1. 通过内存映像发内心工具对dump出来的堆转储快照进行分析,先确认是内存泄漏还是内存溢出
    2. 如果是内存泄漏,进一步通过工具查看泄漏对象到GC Roots的引用链,通过引用链信息,就可以比较准确地定位泄漏代码的位置
    3. 如果不存在泄漏,那就应当检查虚拟机的堆参数(-Xmx 和 -Xms)与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗

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

    对于HotSpot虚拟机来说,栈容量只由-Xss参数设定。但是这部分抛出 StackOverflowError 的情况比OOM要常见

    3.3 运行时常量池溢出

    如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()方法。该方法的作用是:如果池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

    可以通过-XX:PermSize-XX:MaxPermSize限制方法区的大小,从而间接限制其常量池的容量

    3.4 方法区溢出

    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。对于这个区域的测试,可以在运行时产生大量的类去填满方法区,直到溢出。

    3.5 本机直接内存溢出

    该部分可铜鼓 -XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异,真正申请分配内存的方法是unsafe.allocateMemory()

  • 相关阅读:
    Win:使用组策略启用和禁用 USB 驱动器
    lnmp环境部署
    不连续页分配器 & 每处理器内存分配器 & 页表
    【SQLAlChemy】表之间的关系,外键如何使用?
    Golang链路追踪:实现高效可靠的分布式系统监控
    手动验证 TLS 证书
    ES6基本语法之扩展运算符、解构赋值、箭头函数
    【校招VIP】专业课考点之网络存储
    .net core 3.0 + angular 8.0 ----项目创建过程
    react 手写树形渲染组件
  • 原文地址:https://blog.csdn.net/qq_37776700/article/details/127799538