OR博客
Java内存区域与内存溢出异常
苗锦洲
创建于:2021-09-23 22:40:52
0
31
267
0
在使用C或C++的时候,需要手动分配内存,而使用Java的时候只需要一个new关键字,JVM会帮我们为对象分配内存与对象回收,虽然很方便,但是当出现内存泄露和溢出相关的问题,如果不了解JVM是怎么管理内存的,排查错误将会异常艰难。

在使用 C 或 C++ 的时候,需要手动分配内存,而使用 Java 的时候只需要一个 new 关键字,JVM 会帮我们为对象分配内存与对象回收,虽然很方便,但是当出现内存泄露和溢出相关的问题,如果不了解 JVM 是怎么管理内存的,排查错误将会异常艰难。

Java 自动内存管理

首先说明,Java 运行时数据区域和内存模型是不一样的东西。

运行时数据区域

是指 JVM 运行时将数据分区域存储,强调对内存空间的划分。

内存模型

(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式,如果我们要想深入了解 Java 并发编程,就要先理解好 Java 内存模型。

Java 运行区域(运行时数据区域)

线程私有

ThreadLocal

  • 程序计数器
    PC Register

    • What

      • 概念

        • 较小的一块区域,字节码行号指示器,程序控制流的指示器,每个线程都需要一个独立的 PC
      • 存储内容

        • Java 方法

          • 字节码地址
        • Native 方法

    • How

      • JVM 概念模型中,字节码解释器通过改变这块内存的值来选取下一条需要执行的字节码指令
    • Why

      • 多线程

        • 线程切换后可以恢复到正确的执行位置,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖 PC 来完成
  • 虚拟机栈

    • What

      • 描述了方法执行的内存模型,用来保存栈帧的栈,生命周期与线程相同

      • 栈帧

        • 每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息,每一个方法从执行到结束,都对应了一个栈帧在虚拟机栈中从入栈到出栈的过程

          • 局部变量表

            • 存放基本数据类型、对象引用(指向对象起始地址的引用指针、句柄或其他与对象相关的地址)和 returnAddress 类型(指向字节码指令的地址)。这些类型在局部变量表中的存储空间以局部变量槽(Slot)表示,一个 Slot 对应的空间由 JVM 决定,long 和 double 占两个,其他占一个,编译的时候局部变量表大小已经确定,运行的时候根据实际情况分配真正的内存空间。
          • 动态连接

    • How

      • 存储栈帧(Stack Frame)
    • Why

    • StackOverFlowError

    • OutOfMemoryError

  • 本地方法栈

    • 与虚拟机栈类似,只不过是为本地方法服务,本地方法具体实现由虚拟机确定,甚至合二为一(Hot pot)

线程公有

  • Java 堆

    • What

      • 通常是最大的一块区域,虚拟机启动时创建,几乎所有的对象实例和数组都在这里分配内存

      • 不需要连续存储、可扩展

      • 详细划分

        • 内存回收角度

          • 由于现在的垃圾回收器基本都用分代收集算法,所以还可以细分为新生代和老年代

            • 新生代

              • Eden
              • From Survivor
              • To Survivor
        • 内存分配角度

          • 线程私有的分配缓冲区(TLAB)
    • How

    • Why

    • OutOfMemoryError

      • 内存不够,并且无法扩展(-Xmx,-Xms)
  • 方法区

    • What

      • 别名:非堆(Non-Heap)
      • 不需要连续存储、可扩展、可以选择不实现垃圾回收,不会收可能会造成内存泄露,主要是对常量池的回收和类型的卸载
      • 存储类信息、常量、静态变量、即时编译器编译后的代码
    • How

    • Why

    • OutOfMemoryError

    • 运行时常量池

直接内存

  • What

    • 是一块不属于运行时数据区和 JVM 规范中定义的内存区域
  • How

    • JDK1.4 引入 NIO(New Input/Output)类,引入了基于通道和缓冲区的 I/O 方式,可以使用 Native 函数直接分配堆外内存,然后通过存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用直接进行操作
  • Why

    • 在部分场景中显著提升性能,避免了在 Java 堆中和 Native 堆中来回复制数据
  • OutOfMemoryError

    • -Xmx

内存模型 JMM

// TODO

冷知识

"java"这个字符串在加载 sun.misc.Version 的时候进入常量池中的

评论