Java虚拟机内存管理(一)—内存划分

Java 与 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的 “高墙”,墙外面的人想进去,墙里面的人却想出来。——《深入理解Java虚拟机:JVM高级特性与最佳时实践(第二版)》周志明

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以作为一个 Java 程序员要比 C++ 程序员幸福,但是内存方面一旦出现问题,如果对虚拟机怎样使用内存不了解,就很难排查错误。

这段时间看周志明先生的《深入理解Java虚拟机:JVM高级特性与最佳时实践(第二版)》,下面就对 Java 虚拟机对内存的管理做一个系统的整理,本篇文章是该专题的第一篇。

1、内存划分

内存是计算机中运行系统和软件的场所,而内存划分是 Java 虚拟机管理内存中人为添加的概念,是为了更好的描述 Java 虚拟机对内存的管理。下图中的的运行时数据区域即是 Java 虚拟机所管理的内存区域。

内存划分.png

1.1 程序计数器

在 CPU 的寄存器中有指令计数器,而在 Java 虚拟机内存管理中也有类似的程序计数器。程序计数器占用一块很小的内存空间,并且每条线程中都有独立的程序计数器。指令计数器记录的是 CPU 将要执行的下一条指令的地址,而程序计数器略有不同。在线程执行的 Java 方法时,程序计数器记录的是正在执行的虚拟机字节码指令的地址,而在线程执行 Native 方法时,程序计数器为空,因为此时 Java 虚拟机调用是和操作系统相关的接口,与 Java 语言无关。

此区域是唯一一个在 Java 虚拟机规范中没有规定会出现 OutOfMemoryError 情况的区域,对 OutOutOfMemoryError 的讲解会在后面说到。

1.2 Java 虚拟机栈

我们常在程序运行的内存划分为堆区和栈区,但是在 Java 中,这样的划分是很粗糙的,Java 虚拟机中栈有 Hava虚拟机栈和本地方法栈。同程序计数器一样,Java 虚拟机栈也是每条线程私有的。虚拟机栈描述的是 Java 方法执行时的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈这种数据结构,就不多说了,特点是先进先出(FIFO),而栈帧就是栈中的数据元素,下图中是栈帧的这种数据在 Java 栈中的结构图。

栈帧数据结构.png

局部变量表中存放的是编译期可知的各种基本数据类型,包括 Java 的八大基本数据类型和对象引用(reference)类型(这种类型不在这里详细说了)。一个方法需要在帧中分配多大的局部变量空间是完全确定的,并且在其方法运行期间不会改变局部变量表的大小,进而可以知道局部变量表所需的内存空间在编译期就确定下来了。

在 Java 虚拟机规范中,对这个区域规定了两种异常出现的情况:

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

1.3 本地方法栈

本地方法栈和虚拟机栈作用是相似的,他们之间的区别无非是虚拟机栈为虚拟机执行的是 Java 方法,本地方法栈为虚拟机使用的是 Native 方法。其实,不同的 Java 虚拟机,对栈区域的实现是不同的,比如主流的 HotSpot 虚拟机就把虚拟机栈和本地放栈合二为一了。

与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

1.4 Java 堆

Java 堆是 Java 虚拟机内存所管理的内存最大的一块,所有的线程都共享此区域,此区域可以说是 Java 对象的出生地,此区域的唯一目睹就是存放 Java 实例,几乎所有的对象实例都在这里分配内存(不同的编译器有所不同)。Java 堆也是垃圾收集器管理的主要区域,也被称为是 “GC堆”。Java 堆在物理上可以处于不连续的内存空间,只要在逻辑上是连续的就可以了,就像磁盘空间存放文件一样。Java 堆既可以是固定大小的,也可以是可扩展的,在主流的 java 虚拟机中是按照可扩展来实现的。关于 Java 堆的详细介绍将在后面说明。

如果在 Java 堆中没有足够的内存空间完成对象实例的分配,并且堆也无法再扩展,将会抛出 OutOfMemoryError 异常。

1.5 方法区

同 Java 堆一样,方法区也是各个线程共享的区域,它用于存储已经被虚拟机加载过的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java 虚拟机规范中把方法区描述为堆的一个逻辑部分,也叫做 “非堆”,也是为了和 Java 堆区分开来。方法区和 Java 堆一样,也不需要连续的内存空间,在 Java 虚拟机的实现中,也是可以选择固定大小或者可扩展,并且还可以选择不实现垃圾回收,因为这个区域需要用到回收的地方很少,但是实际开发种的教训告诉我们,对方法区进行垃圾回收也是很有必要的,这个区域同样会出现内存泄漏的问题。

在方法区中,有一部分被称为是运行时常量池。常量池除了用于存放在编译期生成的各种字面量和符号引用,此外还有直接引用也被存储在运行时常量池中。运行时常量池具有动态性,常量并不一定实在编译期才被放入该常量池,在运行期间也可以有新的常量放入池中,如我们在开发中使用 String 类的 intern() 方法时。

对字面量和符号引用不清楚的小伙伴可以看下面两篇扩展阅读。

字面量,常量和变量之间的区别?

个人理解 java虚拟机中的符号引用和直接引用

方法区中并不是像字面意思那样存放方法的,它很像一个Java世界的身份信息中心,类,常量、变量的信息都有。——个人理解

当方法区无法满足内存分配时,抛出 OutOfMemoryError 异常。

1.6 直接内存

直接内存并不在 Java 虚拟机管理的内存区域内,也不是 Java 虚拟机规范中定义的内存区域。直接内存是 Java 程序不经过 Java 虚拟机分配,直接使用主机的物理内存,在一些场景(如文件赋值)中可以提高性能,但是直接在使用直接内存中也要注意主机内存大小的限制(包括物理和系统级的限制),否则也会抛出 OutOfMemoryError 异常。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏养码场

再刷一波起来!Java后端开发面经大集锦2.0,刷完顺利拿下Offer!

昨天场主献上Java后端开发面经大集锦1.0,反响特别好!还有程序员“指控”场主:为啥不早点推送??并送上了一个意味深长的微笑

1202
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

2962
来自专栏我是攻城师

理解Java里面的代理模式

代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看...

4361
来自专栏Jerry的SAP技术分享

Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理

面试问题:Java里的代理设计模式(Proxy Design Pattern)一共有几种实现方式?这个题目很像孔乙己问“茴香豆的茴字有哪几种写法?”

3.2K3
来自专栏微信公众号:Java团长

《深入理解Java虚拟机》笔记

也就是说,我们完全可以做一个工具,从一个文件中读入指令,然后将这些指令运行起来。上面代码中“编好的机器指令”当然指的是能在CPU上运行的,如果这里我还实现了一个...

631
来自专栏人工智能LeadAI

Python调用C函数的方法以及如何编写Python的C扩展

01 前言 前言属闲聊,正文请转后。 标题比较长,其实“如何用Python调用C的函数”以及“如何编写Python的C扩展”在广义上是同一件事,因为都是用C写底...

4216
来自专栏技术碎碎念

探究JVM——运行时数据区

最近在读《深入理解Java虚拟机》,收获颇丰,记录一下,部分内容摘自原书。 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域...

3517
来自专栏WindCoder

Java基础小结(三)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection)

701
来自专栏木木玲

设计模式 ——— 状态模式

1232
来自专栏技术小黑屋

JVM运行时的数据区

理解JVM运行时的数据区是Java编程中的进阶部分。我们在开发中都遇到过一个很头疼的问题就是OutOfMemoryError(内存溢出错误),但是如果我们了解J...

1153

扫码关注云+社区

领取腾讯云代金券