一、前言

Java 虚拟机,简称 JVM(Java Virtual Machine),是 Java 语言中最为核心的一个东西,Java 程序运行离不开它,因为它的存在,使得 Java 拥有“一次编译,多次运行”的特点。任何平台只要装有针对于该平台的 Java 虚拟机,字节码文件(.class)就可以在该平台上运行。

JVM 是 Java 中最难以理解、而且非常重要的知识点,也常常用来衡量一个人 Java 基本功是否牢靠,更是在面试中被问及最多、最频繁的知识点之一。本文将从 Java 虚拟机内存模型开始入手,一步步来了解它。

Java 虚拟机内存模型是 Java 程序运行的基础,为了使 Java 应用程序正常运行,JVM 将其内存数据分为程序计数器、虚拟机栈、本地方法栈、堆和方法区,如下图所示:

(在 JDK1.8 开始,已经去掉了方法区的概念,用元空间(Metaspace)进行了代替.)

程序计数器用于存放下一条运行的指令;虚拟机栈和本地方法栈用于存放函数方法调用堆栈信息;Java 堆用于存放 Java 程序运行时所需的对象等数据;方法区用于存放程序的元数据信息。

其中,一部分是线程私有的,而另一部分却是线程共享的。

  • 线程私有:程序计数器、虚拟机栈、本地方法栈

  • 线程共享:堆、方法区

二、程序计数器

程序计数器是一块很小的内存空间用于存放下一条运行的指令,它是线程私有的,可以认作为当前线程的行号指示器。

由于 Java 是支持线程的语言,当线程数量超过 CPU 数量时,线程之间根据时间片轮询抢夺 CPU 资源。对于单核 CPU 而言,每一时刻,只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器,来记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作。

如果当线程正在执行一个 Java 方法,则程序计数器记录正在执行的 Java 字节码地址,如果当前线程正在执行一个 Native 方法,则程序计数器为空。

三、虚拟机栈(栈)

栈保存的是方法的局部变量、部分结果,并参与方法的调用和返回,即:栈帧数据。

1.栈帧

每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接方法、返回地址等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈(方法调用)到出栈(方法返回)的过程。

栈帧结构如下图所示:

如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中局部变量表就会比较大,栈帧就很很大,因此,单个方法调用所需的栈空间大小也会很大。(在程序开发时,尽量避免这种情况,尤其是递归方法中要避免递归调用的深度)

以下代码片段中,通过逐步设置递归方法调用的深度,将会抛出栈溢出异常(StackOverflowError)。

public class StackTest {// 递归次数private final int count = 100000;/*** 递归方法* @param num*/public void recursionMethod(int num) {num++;if (num < count) {recursionMethod(num);}}@Testpublic void stackDepthTest() {recursionMethod(0);}
}

2.栈溢出、内存溢出

Java 虚拟机规范中允许栈的大小是动态的或者是固定的,定义了两种异常与栈空间相关:StackOverflowError 和 OutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则会抛出 StackOverflowError 异常,如果栈能够动态扩展,而在扩展过程中,没有足够的内存空间来支持栈的扩展,则会抛出 OutOfMemoryError 异常。

其中,可以使用 JVM 参数-Xss 来调整设置栈的大小,从而决定了方法调用可以达到的深度。

3.jclasslib 工具

篇外话,但觉得还是有必要提出来,在研究 JVM 时,总是会去研究一些字节码指令、Class 类文件结构、大小等数据,而 jclasslib 工具恰恰满足这些,有了它更有助于我们对 Java、JVM 有更深入的了解。

大家可根据自己的喜好,选择安装,有单机软件版、IDE 插件可供使用,在此,我选择的是在 idea 中安装了 jclasslib 插件,方便使用。此工具将伴随着你在 JVM 的世界里翱翔,一探 JVM 究竟。

以上述代码为例进行说明,如下图所示,在 idea 中通过 jclasslib 插件查看 StackTest.class 文件,展开方法 recursionMethod 后,查看 Code 属性的 Misc 页签中,当前方法的最大局部变量表的容量为 2。因为在该方法中只有一个 int 类型的参数,所以共占 2 字。

关于 jclasslib 工具的更多使用技巧,在不断的使用中去摸索吧。

四、本地方法栈

本地方法栈和虚拟机栈的功能很相似,虚拟机栈用于管理 Java 方法的调用,而本地方法栈用于管理本地方法的调用。

本地方法并不是用 Java 实现的,而是使用 C 实现的。本地方法栈保存的是 native 方法的信息,当一个 JVM 创建的线程调用 native 方法后,JVM 不再为其在虚拟机栈中创建栈帧,JVM 只是简单地动态链接并直接调用 native 方法。

在 Hot Spot 虚拟机中,是不区分本地方法栈和虚拟机栈的。因此,本地方法栈一样也会抛出异常 StackOverflowError 和 OutOfMemoryError。

五、堆

堆可以说是 Java 运行时内存中最为重要的部分,几乎所有的对象和数组都是在堆中分配空间的。堆分为新生代和老年代两部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被收回,生存得足够长,老年对象就会被移入老年代。

新生代又可以进一步细分为 eden、survivor space0(s0 或者 from space)和 survivor space1(s1 或者 to space)。eden 称之为伊甸园,即对象的出生地,大部分对象刚刚创建时,通常会存放在这里。s0 和 s1 为 survivor 空间,直译为幸存者,就是指存放其中的对象至少经历了一次垃圾回收,并得以幸存。如果在幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代。

换言之,堆空间简单分为新生代和老年代,新生代用于存放刚产生的新对象,老年代则存放年长的对象(存放时间较长,经过垃圾回收次数较多的对象)。

堆空间结构如下图所示:

六、方法区

方法区同堆一样,是所有线程共享的内存区域,为了区分堆,又被称为非堆。主要保存的信息是类的元数据,即类的类型信息、常量池、域信息、方法信息,如 static 修饰的变量加载类的时候就被加载到方法区中。

类型信息包括类的完整名称、父类的完整名称、类型修饰符(public/protected/private)和类型的直接接口类表;常量池包括这个类方法、域等信息所引用的常量信息;域信息包括域名称、域类型和域修饰符;方法信息包括方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法帧栈的局部变量区大小以及异常表。总之,方法区内保存的信息,大部分都来自于 class 文件。

在 Hot Spot 虚拟机中,方法区也成为永久区,是一块独立于 Java 堆的内存空间。虽然叫做永久区,但是永久区中的对象,同样也是可以被 GC 回收的。只是对于 GC 的表现和 Java 堆空间略不相同。对永久区 GC 的回收,通常主要从两个方面分析:一是 GC 对永久区常量池的回收,二是永久区对类元数据的回收。

方法区也成为永久区,主要存放常量和类的定义信息。

(在 JDK1.8 的 HotSpot 虚拟机中,已经去掉了方法区的概念,用 Metaspace 代替,并且将其移到了本地内存来规划了。)

Java 虚拟机内存模型相关推荐

  1. 【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )

    文章目录 一. Java 虚拟机内存模型 二. 程序计数器 ( 线程私有区 ) 三. 虚拟机栈 ( 线程私有区 ) 四. 本地方法栈 ( 线程私有区 ) 五. 方法区 ( 共享数据区 ) 1. 方法区 ...

  2. 小白都能看得懂的java虚拟机内存模型

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:土豆是我的最爱 blog.csdn.net/qq_37141 ...

  3. Java虚拟机内存模型初步学习

    Java虚拟机-Java8内存模型 1.概述 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要手动释放内存,不容易出现内存泄露和内存溢出问题.一旦出现内存泄露和溢出方面的问题,如果不 ...

  4. java虚拟机内存模型与垃圾回收知识复习总结

    今天日子很特殊,1024程序员节,本来每个月计划的是至少一篇博客,刚好这个月还没写,今天的日志又特殊,必须要写一篇博客. 之前看过一些讲java虚拟机的课程,但是学过容易忘,总结一下,平时可以多看看. ...

  5. java虚拟机系列:java虚拟机内存模型

    java内存模型,分为程序计数器,虚拟机栈,本地方法栈,java堆,java栈.根据受访的权限不同设置,可以分为线程共享和线程私有.线程共享指可以允许所有的线程共享访问的一类内存区域,包括堆内存区,方 ...

  6. Java虚拟机内存模型简单介绍

    一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操作系统底层的硬件指令集是不同的. 不知道同学们还记不记得,在下载jdk的时候,我们在oracle官网,基于不同的操作系统 ...

  7. Java虚拟机内存模型

    Java运行时的数据区域分为线程共享区域和线程隔离的数据区域: 线程共享:方法区.堆.执行引擎.本地库接口 线程隔离:虚拟机栈.本地方法栈.程序计数器 方法区: 存储已被虚拟机加载的类信息.常量.静态 ...

  8. java虚拟机内存模型种类_深入理解volatile类型——从Java虚拟机内存模型角度

    一.前言 在java多线程编程中,volatile可以用来定义轻量级的共享变量,它比synchronized的使用成本更低,因为它不会引起线程上下文的切换和调度.所谓知己知彼.百战不殆.本文从JVM内 ...

  9. java从内存角度理解类变量_深入理解volatile类型——从Java虚拟机内存模型角度...

    一.前言 在java多线程编程中,volatile可以用来定义轻量级的共享变量,它比synchronized的使用成本更低,因为它不会引起线程上下文的切换和调度.所谓知己知彼.百战不殆.本文从JVM内 ...

最新文章

  1. 关于机器学习,不可不知的15个概念
  2. 苏黎世华人博士提出模型SwinIR,只用33%的参数量就碾压图像修复领域sota
  3. osfmount 命令加载虚拟光驱_OSFMount挂载虚拟磁盘工具下载
  4. 每日一皮:传说中的三次握手...
  5. 解决STM32 SPI 半残废 NSS无法拉高
  6. SpringMVC级联获取前台数据
  7. python reduce函数_Python reduce函数作用及实例解析
  8. C语言字符串中最后一个单词的长度的算法(附完整源码)
  9. 贝叶斯优化神经网络参数_贝叶斯超参数优化:神经网络,TensorFlow,相预测示例
  10. 【写作技巧】毕业论文写作:本科、硕士论文写作必备数据库
  11. DB2 9 根本(730 检修)认证指南,第 6 局部: 数据并发性(2)
  12. mysql 问题:Unknown system variable 'query_cache_size'
  13. Java自学!java题库网站
  14. SpringBoot+Quartz+数据库存储
  15. H264/H265 PS 流分析
  16. linux apache 查看mpm 配置方式,Apache Prefork、Worker和Event三种MPM详解
  17. vscode 使用手册
  18. 核磁计算机系统包括,核磁共振和CAT扫描系统
  19. “后疫情时代”的金融科技价值
  20. 字母大小写转换在线工具

热门文章

  1. [597]Oracle插入或修改报ORA-01704: 文字字符串太长
  2. 奇迹Mu如何修改模型?
  3. P2800 又上锁妖塔 dp
  4. 千年一瞬,兵马俑竟眨眼了!外国小哥用AI复原了他
  5. 解决SublimeText3显示中文乱码问题(新增支持GBK/GB2312编码)
  6. 爱 F★Q 恨---心声
  7. 成功的人一直在执行的话
  8. Python通过url获取图片的几种方法
  9. 计算机绘画小房子教案,幼儿园小班绘画房子美术教案
  10. Overview of the English Slot Filling Track at the TAC2014 Knowledge Base Population Evaluation