第一章 JVM体系结构

文章目录

  • 第一章 JVM体系结构
  • 一、JVM 体系结构
    • 1.体系结构
    • 2.程序计数器(Program Counter Register)
    • 2.虚拟机栈(JVM Stacks)
    • 3.本地方法栈(Native Method Stacks)
    • 4.堆区(Heap)
    • 5.方法区(Method Area)
    • 6.每个区域存储的内容
    • 7.直接内存(Direct Memory)
  • 参考链接

初学 JVM,已经尽力在网上寻找最正确的资料,如有纰漏望指正!


一、JVM 体系结构

1.体系结构

下图是 JVM 的体系结构,基于 HotSpot VM

下图是 JDK 1.8 的内存模型

我们看到,第一张图中的方法区在第二张图里并没有找到。这里我们需要注意,方法区只是一种抽象的概念,在不同的 JDK 版本,它的实现方式不同

版本 变化
JDK 1.6 及之前 有永久代,类信息、静态变量、和常量池存放在永久代
JDK 1.7 字符串常量池、静态变量移出永久代,被放在堆中
JDK 1.8 及之后 去除了永久代,由本地内存的元数据区(Metaspace)取代


  • JDK 1.7 中字符串常量池 (StringTable) 为什么从永久代移到堆中?
    永久代的回收效率很低,只有 Full GC 才会触发,而字符串常量池回收效率不高,开发中会有大量字符串被创建,放到堆里能够及时回收内存
  • JDK 1.8 为什么去除永久代?
    第一点,永久代如果存放在 JVM 中,很难确定合适的大小。如果被分配在本地内存,则无需考虑大小问题
    第二点,原来在运行时会生成大量的类,比如使用反射和代理,导致经常需要 Full GC
    第二点,对永久代调优很困难

需要注意,元数据区从虚拟机转移到了本地内存意味着什么?默认情况下,元数据区的大小仅受本地内存的限制,这意味着以后不会因为永久代空间不够而抛出 OOM 异常了。JDK 1.8 以前版本的 class 和 jar 包数据存储在永久代,永久代的大小是固定的,而且项目之间无法共用公有的 class,所以很容易碰到 OOM 异常。改成元数据区后,各个项目会共享同样的 class 内存空间,比如多个项目都引用了 apache-common 包,在元数据区只会存储一份 apache-common 的 class,提高了内存的利用率,垃圾回收更有效率

2.程序计数器(Program Counter Register)

程序计数器是一个记录着当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。内存空间小、线程私有

Java 代码编译后的字节码在未经过 JIT (实时编译器)编译前,其执行方式是通过 “字节码解释器” 进行解释执行。简单的工作原理为:解释器读取装载入内存的字节码,按照顺序读取字节码指令。在读取一个指令后,将该指令 “翻译” 成固定的操作,并根据这些操作进行分支、循环、跳转、异常处理、线程恢复等基础功能

从上面的描述中,可能会产生程序计数器是否多余的疑问,因为沿着指令的顺序执行下去,即使是分支跳转这样的流程没跳转到指定的指令处按顺序继续执行是完全能够保证程序的执行顺序的。假设程序永远只有一个线程,程序计数器确实多余,但是实际上程序是通过多个线程协同合作执行的,这就有说法了

首先我们要搞清楚 JVM 的多线程实现方式。JVM 的多线程是通过 CPU 时间片轮转(即线程轮流切换并分配处理器执行时间)来实现的。因此,某个线程在执行过程中可能会因为时间片耗尽而被挂起,另一个线程会获取到时间片并开始执行。当被挂起的线程重新获取到时间片的时候,它想要从被挂起的地方继续执行,就必须直到它上次执行到哪个位置,在 JVM 中,就是通过程序计数器来记录某个线程的字节码执行位置的。所以,程序计数器具备线程隔离的特性,每个线程工作时都有属于自己的独立计数器

如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是 Native 方法,这个计数器的值则为(Undefined)。因为 Native 方法是 Java 通过 JNI(本地方法接口)直接调用本地 C/C++ 库,可以近似地认为 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口到 C/C++ 方法。由于该方法是通过 C/C++ 而不是 Java 进行实现。那么自然无法产生相应的字节码,并且 C/C++ 执行时的内存分配是由自己语言决定的,而不是由 JVM 决定

2.虚拟机栈(JVM Stacks)

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程

  • 局部变量表:存放了编译器可知的八大基本类型、引用类型和 returnAddress 类型(指向了一条字节码指令的地址)

虚拟机栈也会发生 OOM 异常

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
  • OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存

虚拟机栈是用于描述 Java 方法执行的内存模型。方法调用时,创建栈帧,并压入虚拟机栈。方法执行完毕,栈帧出栈并被销毁

3.本地方法栈(Native Method Stacks)

区别于虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常

如何去服务 Native 方法、Native 方法使用什么语言实现、怎么组织像栈帧这种为了服务方法的数据结构等问题,虚拟机规范并未给出强制规定,因此不同的虚拟机可以进行自由实现,我们常用的 HotSpot 虚拟机选择合并了虚拟机栈和本地方法栈,HotSpot 虚拟机不区分本地方法栈和虚拟机栈

4.堆区(Heap)

线程共享,主要是存放对象和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。发生 OOM 的原因是堆中没有内存完成实例分配,并且堆也无法再扩展。一般来说,堆无法分配对象时会进行一次 GC,如果 GC 后仍然无法分配对象,才会报错

堆是用于存放对象的内存区域。因此,它是垃圾收集器(GC)管理的主要目标,其具有以下特点:

  • 堆在逻辑上划分为 “新生代” 和 “老年代”。由于 Java 中的对象大部分是朝生夕灭,还有一部分能够长期地驻留在内存中,为了对这两种对象进行有效的回收,将堆划分为新生代和老年代,并且执行不同的回收策略。不同的垃圾收集器对这两个逻辑区域的回收机制不尽相同
  • 堆占用的内存并不要求物理连续,只需要逻辑连续即可
  • 堆一般实现成可扩展内存大小,使用 “-Xms” 与 “-Xmx” 控制堆的最小与最大内存,扩展动作交由虚拟机执行。但由于该行为比较消耗性能,因此一般将堆的最大最小内存设为相等
  • 堆是所有线程共享的内存区域,因此每个线程都可以拿到堆上的同一个对象
  • 堆的生命周期是随着虚拟机的启动而创建的

5.方法区(Method Area)

方法区,也称为非堆(Non-Heap),其中主要存储加载的字节码、class/mehod/field 等元数据对象、static-final 常量、static 变量、JIT 编译器编译后的代码等数据。此外方法区包含了一个特殊的区域 “运行时常量池”。方法区有一个最大值上限,因此,若方法区占用内存到达最大值,且无法再申请到内存时,便会抛出 OOM 异常。比如,我们使用 cglib 字节码生成框架不断生成新的类,最终使方法区内存占满,除此之外,动态语言、JSP、基于 OSGI(面向Java的动态模型系统) 的应用都会在方法区额外产生大量的类信息

  • 加载的类字节码
    要使用一个类,首先需要将其字节码加载到 JVM 的内存中。至于类的字节码来源,可以多种多样,如 .class 文件、网络传输或者由 cglib 字节码框架直接生成
  • class/method/field 等元数据对象
    字节码加载之后,JVM 会根据其中的内容,为这个类生成 Class/Method/Field 等对象,它们用于描述一个类,通常在反射中用的比较多。不同于存储在堆中的 Java 实例对象,这两种对象存储在方法区中
  • static-final 常量、static 常量
    对于这两种类型的类成员,JVM 会在方法区为它们创建一份数据,因此同一个类的 static 修饰的类成员只有一份
  • JIT 编译器编译后的代码
    以 HotSpot 虚拟机为例,其在运行时会使用 JIT 即时编译器对热点代码进行优化,优化方式为将字节码编译成机器码。通常情况下,JVM 使用 “解释执行” 的方式执行字节码,即 JVM 在读取到一个字节码指令时,会将其按照预先定好的规则执行栈操作,而栈操作会进一步映射为底层的机器操作;通过 JIT 编译后,执行的机器码会直接和底层机器打交道

运行时常量池

类的字节码在加载时会被解析并生成不同的东西存入方法区。类的字节码中不仅包含了类的版本、字段、方法、接口等描述信息,还包含了一个常量池。常量池用于存放在字节码中使用到的所有字面量和符号引用(如字符串字面量),在类加载时,它们进入方法区的运行时常量池存放

运行时常量池是方法区中一个比较特殊的部分,具备动态性,也就是说,除了类加载时将常量池写入其中,Java 程序运行期间也可以向其中写入常量

//使用 StringBuilder 在堆上创建字符串 abc,再使用 intern 将其放入运行时常量池
String str = new StringBuilder("abc");
str.intern();
//直接使用字符串字面量 xyz,其被放入运行时常量池
String str2 = "xyz";

开头我们提到 JDK 1.8 去除了永久代,在了解了方法区的细节之后,我们再来思考一下,为什么使用永久代并将 GC 分代收集扩展至方法区不好?首先要明白方法区的回收目标是什么,方法区存储了类的元数据信息和各种常量,它的内存回收目标理应是对这些类型的卸载和常量的回收。但由于这些数据被类的实例引用,卸载条件变得复杂且严格,回收不当会导致堆中的类实例失去元数据信息和常量信息。因此,回收方法区内存不是一件简单高效的事情,往往 GC 在做无用功。另外随着应用规模的变大,各种框架的引入,尤其是使用了字节码生成技术的框架,会导致方法区内存占用越来越大,最终 OOM

6.每个区域存储的内容

7.直接内存(Direct Memory)

非虚拟机运行时数据区的部分。直接内存会受到本机内存的限制,如果内存区域总和大于物理内存限制会导致动态扩展时抛出 OOM 异常

在 JVM 的内存模型里并不包含直接内存,也就是说这块内存区域并不是 JVM 运行时数据区的一部分,但它却会被频繁的使用,原因是 NIO 这个包。在 JDK 1.4 中系加入 NIO(New Input/Output)类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配对外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作

可以看出,直接内存的大小并不受到 Java 堆大小的限制,甚至不受到 JVM 进程内存大小的限制。它只受限于本机总内存(RAM 及 SWAP 区或者分页文件)大小以及处理器寻址空间的限制(最常见的就是 32 位/64 位 CPU 的最大寻址空间限制不同)

直接内存出现 OOM 的原因是对该区域进行内存分配时,其内存与其他内存加起来超过最大物理内存限制(包括物理的和操作系统级别的限制)。另外,若我们通过参数 “XX:MaxDirectMemorySize” 指定了直接内存的最大值,其超过指定的最大值时,也会抛出 OOM 异常


参考链接

建议结合以下链接理解

先看视频入门
【狂神说Java】JVM快速入门篇
对JVM还有什么不懂的?资深架构师马士兵一节课带你深入浅出JVM虚拟机丨JVM从入门到实战

jdk1.8中jvm的变化
面试题系列第5篇:JDK的运行时常量池、字符串常量池、静态常量池,还傻傻分不清?
jdk1.8——jvm分析与调优

注意:以下文章基于 JDK 1.7
Java虚拟机(JVM)你只要看这一篇就够了!
01-JVM内存模型:程序计数器
02-JVM内存模型:虚拟机栈与本地方法栈
03-JVM内存模型:堆与方法区
04-JVM内存模型:直接内存

【JVM】第一章 JVM体系结构相关推荐

  1. 计算机网络-第一章 计算机网络体系结构(详细知识点总结)

    目录 第一章 计算机网络体系结构 [大纲] 1.1 计算机网络概述 1.1.1 计算机网络的概念 1.1.2 计算机网络的组成 1.1.3 计算机网络的功能 1.1.4 计算机网络的分类 1.1.5 ...

  2. 第一章 JVM与Java体系结构

    文章目录 前言 Java vs C++ 推荐书目 Java 及 JVM 简介 1.Java:跨平台的语言 2.JVM:跨平台的语言 3.字节码 4.多语言混合编程 5.Java发展的重大事件 6.Op ...

  3. 计算机网络 第一章 计算机网络体系结构

    众所周知,第一章一般是作为绪论,整本书的知识点基本都点一下,但是却不会深入,这一章的知识主要是作为引入的角色. 1.1 计算机网络概述 所谓计算机,就是一些互联的.自治的计算机系统的集合.其定义主要有 ...

  4. 王道考研计算机网络学习心得——第一章-计算机网络体系结构

    ​ 前言   本文主要是我学习计网的心得,用王道考研的原因主要是因为,又有老师教又有直接的资料书,个人觉得方便一点,不用到处找网课,也不用想到底看哪本书好,等这一轮学完了再看别的书,想必也能得心应手一 ...

  5. 【计算机网络】第一章:体系结构

    第一章:计网体系结构 OVERVIEW 第一章:计网体系结构 一.计网概述 1.定义: 2.功能: 3.组成: (1)组成部分 (2)工作方式 (3)功能组成 4.分类: (1)分布范围 (2)按拓扑 ...

  6. 【知识强化】第一章 网络体系结构 1.2 计算机网络体系结构与参考模型

    学习计算机网络的分层结构以及在这种分层结构下所产生出的一些名词.协议.接口以及服务. 为什么要有这样一个分层结构?我们的主机在进行通信的时候也可以实现一些资源的共享,比如说我这台主机要给你发送一个文件 ...

  7. 第一章 计算机系统体系结构

    1.1 什么是计算机体系结构 本章的第一个概念是计算机系统(computer system). 计算机系统包括读取并执行程序的 中央处理单元(CPU, 保存程序和数据的存储器以及将芯片转换为实用系统的 ...

  8. 【王道计算机网络】第一章 计算机网络体系结构

    视频指路:王道考研bilibili 第二章指路:[王道计算机网络]第二章 物理层 已经有基础,以找工作为目的请移步:计算机网络笔试面试准备 目录 1. 计算机网络概述 1.1 概念 1.2 组成 1. ...

  9. 考研 计算机网络第一章计算机网络体系结构 知识点总结

    概念 计算机网络:是一个将分散具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统. 计算机是互联的.自治的计算机集合 互联:互相联通 自治:无主从关系( ...

最新文章

  1. php多表数据排除,thinkphp中多表查询中防止数据重复的sql语句(必看)
  2. [html] 如何动态修改`<title>`的标题名称?
  3. 授人以鱼不如授人以渔,UCHome全面大解析培训【第二集】
  4. 为什么不断做迁移,那是在还技术债
  5. 百万数据查询优化技巧三十则
  6. 监控服务器ssh登录,并发送报警邮件
  7. 创建页面html,DW软件新建一个html网页
  8. C# 从入门到精通 学习笔记1 第2章 使用变量、操作符和表达式
  9. eXtremeComponents 分页列表
  10. java初级程序员学习思路
  11. FPGA实验-VGA显示
  12. python seo 采集内容_SEO如何处理采集内容(4)–转自{GoGo闯}
  13. Codeforces Round #822 (Div. 2) C Removing Smallest Multiples(复杂度为调和级数级别的暴力)
  14. Spring Cloud实战(三)-监控中心
  15. 贝塞尔曲线 弯曲动画ios_用贝塞尔曲线弯曲
  16. 奖励稀疏_好奇心解决稀疏奖励任务
  17. google android模拟器多系统,Android模拟器安装教程_体验google_Android系统手机
  18. 关于AntMotion动画使用
  19. Qt 多线程中地信号与槽
  20. 模仿新浪微博雷达搜索动画效果

热门文章

  1. C语言期末成绩计算机平均分,用C语言编程平均分数
  2. 2091: [Poi2010]The Minima Game
  3. xhtml html
  4. 跑了10千米,再一次伤了膝盖
  5. ASP.NET - Eval使用自定义的方法
  6. SQL SERVER2008 存储过程、表、视图、函数的权限
  7. Ubuntu 通过Deb 安装 MySQL 5.5 [转载]
  8. python爬虫url参数有随机数、如何确定是正确的链接_Python爬虫知识点——请求
  9. qemu a fast and portable dynamic translator——大致翻译
  10. mysql set语句_从强网杯随便注浅析mysql存储过程