对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。

运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
以 JDK 1.8 为例,虚拟机按线程共享和私有的方式进行划分
线程共享的:

  • 方法区
  • 直接内存

线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

image.png

堆内存是 Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在堆中分配内存。
Java 堆是垃圾收集器管理的主要区域,因为也被称作 GC 堆,从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点可以分为:Eden 空间,From Survivor,To Survivor 空间等。进一步划分的目的是更好的回收内存、更快的分配内存
在 JDK 1.8 版本之后移除了方法区(永久代),取而代之的是元空间,元空间使用的是直接内存
image.png
上图所示的 Eden 区、两个 Survivor 区都属于新生代,中间一层属于老年代。通常情况下,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会增加 1,当他的年龄增加到一定程度后(默认为 15 岁),就会被晋升到老年代中,对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 设置
堆中最容易出现的就是 OutOfmemoryError 错误,并且出现这中错误之后的表现形式可以细分为如下几种:

  • OutOfMemoryError: GC Overhead limit Exceed:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误
  • java.lang.OutOfMemoryError: Java heap space:假如在创建新的对象时,堆内存中的空间不足以存放新创建的对象,就会引发此错误

    方法区

    方法区和 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆)

    为什么要将永久代替换为元空间呢?

    永久代大小都由一个 JVM 来设置固定大小上限,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小

    直接内存

    直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,而去也可能导致 OOM 异常

    程序计数器

    程序计数器是一块较小的空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖计数器完成。
    另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,这类内存区域被称为“线程私有”的内存

    虚拟机栈

    虚拟机栈时为虚拟机执行 Java 方法服务的,和程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
    Java 内存可以简单的分为 堆内存(Heap) 和 栈内存(Stack),其中栈内存指的就是虚拟机栈
    Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError

  • StackOverFlowError:若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常

  • OutOfMemoryError:Java 虚拟机栈的内存大小可以动态扩展,如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常

    本地方法栈

    本地方法栈是为虚拟机使用到的 Native 方法服务的,它的作用和虚拟机栈非常相似。并且也会出现 StackOverFlowErrorOutOfMemoryError