前言

开始认真更新了,先开始肝JVM相关知识,本篇是最基础的运行时数据区域,内容都节选自<深入理解java虚拟机>。



一、运行时数据区域


根据《java虚拟机规范》的规定,java虚拟机所管理内存将会包括以下几个运行时数据区域,如下图所示:

JVM之内存结构_数据

如上图,主要分为4个组成部分:

  1. 类加载器:负责加载类信息到内存中;
  2. 运行时数据区域:JVM内存,分线程独占区和线程共享区;
  3. 执行引擎:将字节码翻译成底层系统指令,再交由 CPU 去执行(字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行);
  4. 本地库接口:负责同其他编程语言交互的接口。

运行时数据区域主要分为线程独占区和线程共享区2大类,其中 线程独享有 程序计数器、本地方法栈、虚拟机栈;线程共享有 堆、方法区。

二、线程独享

1.程序计数器

定义:

程序计数器 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,属于线程独享。

字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完。

且此区域是唯一一个在《java虚拟机规范》中没有规定任何OutOfMemeryError情况的区域。

2.虚拟机栈

定义:

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。先入后出(FILO)。

存储:局部变量表存放了编译期可知的基本数据类型、对象引用、返回值和地址类型。

异常:线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowError异常;栈在扩展时无法申请到足够的内存就会抛出OOM异常。

JVM之内存结构_字节码_02

3.本地方法栈

定义:

和虚拟机栈的作用类似,区别就是虚拟机栈为虚拟机执行java方法服务,而本地方法栈是为虚拟机使用本地(native)方法服务。

异常:同虚拟机栈一样,当栈深度溢出或栈扩展失败时都会抛出StackOverflowError或OOM异常。

JVM之内存结构_数据_03

三、线程共享


1.java堆

定义:

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。内部会划分出多个线程私有的分配缓冲区。可以位于物理上不连续的空间,但是逻辑上要连续。也是GC回收的主要区域。

存储:所有new对象都分配在堆上。

异常:当堆中没有内存完成实例分配且无法扩展时,JVM会抛出OOM异常。

请看下图通过直接指针访问对象 可以看出java栈、java堆和方法区关系。

JVM之内存结构_字节码_04

JVM之内存结构_方法区_05

2.方法区

定义:

和堆一样都是线程共享区域,用于存储被虚拟机加载的类加载信息、运行时常量池、代码缓存等数据,为了和堆做区别也叫 非堆。

实现:

JDK8之前方法区的实现是永久代,在jdk7时就做准备换掉永久代将字符常量池、静态变量移到堆中;在JDK8时将永久代剩余内容(主要是类加载信息)全部移到元空间,并删除永久代,则方法区实现变成了元空间。元空间使用的内存 叫做 本地内存 (主要是区别于堆内存)。

回收对象:GC主要对方法区的回收是废弃的常量池和不再使用类型。

异常:方法区无法满足新的内存分配时,会抛出OOM异常。

以下图就是JDK6-JDK8方法区实现图:

JVM之内存结构_JVM内存结构_06

JVM之内存结构_数据_07


四、直接内存

在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。

注意点:

1. 直接内存不受堆内存影响;

2. 动态扩容时内存不足会抛出oom异常。


总结

本篇基本上都是摘抄于<深入理解java虚拟机>这本书,也是JVM内存结构的基础。