19

《深入理解Java虚拟机》- Java 内存区域读书笔记

 3 years ago
source link: https://baiwenhui.com/2018/04/22/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E3%80%8B-Java-%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

对于 Java 程序员来说,在我们平时的编码过程中,并不会手动的去分配内存和回收内存,因为对于内存这块的处理都是依赖于 JVM 的,这样会导致一个问题:就是如果我们对 JVM 的内存管理机制不是特别熟悉的前提下,如果程序在内存这块出现了莫名的问题,此时处理这些问题就是一个灾难,因此,对于 Java 内存区域这块需要我们有一个清晰的认知,这样才可以在出现问题是根据 JVM 内存管理这块有好的解决方案。
在我们学习 运行时数据区域之前,我们先概览一下 JVM 的整体架构,这样对于我们下面内容的理解是有很大的帮助的,先将 JVM 整体的架构图展示如下:
简版的图如下:

详细点儿的图如下:

运行时数据区域

一般情况下,运行时数据区域主要包含:程序计数器Java 虚拟机栈本地方法栈Java 堆方法区这几个大的部分。对于此结构,通过图的方式进行如下展示:

由图示意可知:其中Method Area (方法区) Heap(堆) 是线程共享的,VM Stack (虚拟机栈)Native Method Area (本地方法栈)Programmer Computer Register(程序计数器) 是线程私有的。当我们分析到这边之后,会有一个疑问?为什么 JVM 要将内存划分为线程共享和非线程共享呢?

我们可以回顾下 Java 程序执行的流程。Java 源文件 – 字节码文件(使用 Javac 编译为 *.class) – 每个 Java 程序都需要运行在 自己的 JVM 之上,JVM 会找到程序运行的入口,JVM 通过字节码解释器加载字节码运行。程序运行后JVM 如何对内存进行划分呢?其实 JVM 在初始化的时候,会分配好方法区(Method Area) 和 堆(Heap),当 JVM 遇到一个线程的时候,便会为其分配一个程序计数器(Programmer Register)JVM虚拟机栈(VM Stack)本地方法栈(Native Method Stack),当线程死亡的时候,虚拟机栈本地方法栈程序计数器所占用的内存会被释放掉。通过这里的分析,便可以了解到,JVM 将内存区域分为线程共享和非线程共享两个部分,非线程共享的区域与所属的线程的生命周期相同,而线程共享的区域与 JVM 虚拟机的生命周期相同。这样也可以说明,其实 JVM 大多数的垃圾回收是发生在线程共享的区域(实际上更多位于堆上)。

对于运行时数据区域总体的理解,可以参考下图:

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,可看做是当前线程所执行字节码行号的指示器。Java 虚拟机中多线程的执行原理是通过线程轮流切换并分配执行时间的方式来实现的,一般情况下在单核的处理器中,处理器在某一时刻只能处理一条线程中的指令。为了线程切换后能恢复到上次执行的位置,每个线程需要一个独立的计数器,每个线程之间的计数器是互不影响的。因此计程序计数器区域为“线程私有” 的内存。

如果线程执行的是一个 Java 方法,则程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果执行的是 Native 方法,则计数器为Undefined。

Java 虚拟机栈

Java 虚拟机栈和程序计数器一样属于线程私有。虚拟机栈主要描述 Java 执行时的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame) 用于存储变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

虚拟机栈中的局部变量表存放在编译期间可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向一条字节码指令的地址)。

其中 64 位长度的 long 和 double 会占用 2 个局部变量空间,其它的只占用 1 个局部变量空间。一般局部变量所需在帧中分配多大的局部变量空间在程序编译期间完成分配,程序运行期间不会改变局部变量表的大小。

Java 虚拟机规范中指出,当线程请求的栈深度大于虚拟机所允许的深度时,会抛出 StackOverflowError 异常。

本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用非常相似,它们之间的区别在于虚拟机栈为虚拟机执行 Java 方法(执行字节码)服务,而 本地方法栈为虚拟机使用的 Native 方法服务。虚拟机规范中没有对本地方法栈做强制的规定,由具体的虚拟机自己实现,Sun HotSpot 虚拟机字节将本地房发展和虚拟机栈合二为一。通虚拟机栈类似,本地房发展也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

Java 堆

Java 堆是Java 虚拟机所管理内存中最大的一块。Java 堆被所有线程共享,此区域的唯一目的便是存放对象的实例。 Java 虚拟机规范规定:所有的对象实例以及数组都要在堆上进行分配,随着 JIT 编译器的发展与内存逃逸技术的成熟,栈上分配、标量替换优化技术使所有对象分配在堆上的结论不是那么“绝对”。

Java 堆是垃圾回收的主要区域,可将 Java 堆划分为:新生代和老年代;更细的划分新生代包含 Eden 空间、From Survivor、To Survivor 空间等。

Java 虚拟机规范规定,Java 堆可以处于物理机器上不连续的内存空间中。当堆空间无法分配扩展时,将会抛出 OutOfMemoryError 异常。

对于堆的加深理解可以参考此图:

方法去(Method Area)和 Java 堆类似,也属于各个线程共享的内存区域,主要用来存放被 JVM 虚拟机加载的类信息、常量、静态变量。即时编译器编译后的代码等数据。方法区又称为非堆。方法区本质上与“永久代”(Permanent Generation)不等价,主要原因是 HotSpot 虚拟机的设计团队把 GC 分代收集扩展到了方法区,换句话说就是使用永久代实现了方法区。在 JDK1.7 的 HotSpot中,由于永久代区域是一个类的静态区域,主要存储类的加载信息,常量池,方法代码等内容,默认大小只有 4 m,如果在常量池中大量的比如使用 String.intern() 方法会直接导致发生 java.lang.OutOfMemoryError: PermGen space,所以在 jdk7 的版本中,字符串常量池已经从 Perm 区移动到 Java Heap 区域了。主要是原因是:Perm 区域太小,在 Java 8 中已经取消了永久代进而代替的是元区域(Meta Space)。根据 Java 虚拟机规范规定,当方法区无法满足内存分配需求时,会产生 OutOfMemoryError 异常,也称为 OOM 。

运行时常量池

运行时常量池(Runtime Constant Pool) 属于方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项便是常量池(Runtime Constant Pool),常量池主要作用为:存放编译期间生成的各种字面量和符号引用,这些内容将在 Java 类被加载后进入方法区的运行时常量池中存放。运行时常量另外一个重要特征便是具备动态性,具体的案例便是使用 String 的 intern 方法,不要求常量只在类编译期间才能产生,在运行期间也可以将新的常量放入池中。运行时常量池属于方法区的一部分,当常量池无法进行内存分配时,也会抛出 OutOfMemoryError 异常。

直接内存(Direct Memory) 不是虚拟机运行时数据区域的一部分,也不是 Java 虚拟机规范中定义的内存区域。这部分的内存也会被频繁使用,当内存分配空间不足时,也会抛出 OutOfMemoryError 异常。对于直接内存使用的典型案例便是,在 JDK 1.4 之后,新加入了 NIO(New Input/Output) 类,其核心原理是基于通道 (Channel) 与 缓冲区 (Buffer) 的 I/O 方式,可以通过 Native 函数库直接分配堆外内存,然后通过存储在 Java 堆中的 DirectByteBuffer 对象作为内存的引用进行操作。 这样的优点就是在一些场景中提高性能,避免了 Java 堆和 Native 来回赋值数据的开销。
本地内存的分配不会受到 JVM 堆大小的限制,但是会受到系统本身内存大小的限制,当系统本身内存空间不够用时,也会出现 OutOfMemory 异常。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK