虚拟机

JVM 剖析花园:2 - 透明大页

JVM 剖析花园:2 - 透明大页

问题 什么是大页(Large Page)?什么是透明大页(Transparent Huge Page)?它对我有什么帮助? 理论 虚拟内存现在已被视为理所当然。现在只有少数人还记得,更不用说做一些“真实模式”编程了,在这种情况下,你会接触到实际的物理内存。相反,每个进程都有自己的虚拟内存空间,该空间被映射到实际内存上。例如,两个进程在相同的虚拟地址 0x42424242 上拥有不同的数据,而这些数据将由不同的物理内存支持。现在,当程序访问该地址时,应将虚拟地址转换为物理地址。 Figure 1. 虚拟内存地址与物理内存地址之间的关系 这通常由操作系统维护 “页表”,硬件通过“页表遍历”来实现地址转换。如果在页面粒度上维护翻译,整个过程就会变得简单。但这样做的成本并不低,而且每次内存访问都需要这样做!因此,还需要对最新的翻译进行小型缓存,即 转译后备缓冲区(Translation Lookaside Buffer (TLB))。TLB 通常很小,只有不到 100 个条目,因为它的速度至少要与 L1 缓存相当,甚至更快。对于许多工作负载来说,TLB 未命中和相关的页表遍历需要大量时间。 既然我们无法将 TLB 做得更大,那么我们可以做其他事情:制作更大的页面!大多数硬件有 4K 基本页和 2M/4M/1G “大页”。用更大的页来覆盖相同的区域,还能使页表本身更小,从而降低页表遍历的成本。 在 Linux 世界中,至少有两种不同的方法可以在应用程序中实现这一点: hugetlbfs。切出系统内存的一部分,将其作为虚拟文件系统公开,让应用程序通过 mmap(2) 从其中获取。这是一个特殊的接口,需要操作系统配置和应用程序更改才能使用。这也是一种“要么全有,要么全无”的交易:分配给 hugetlbfs(持久部分)的空间不能被普通进程使用。 透明大页(Transparent Huge Pages (THP))。让应用程序像往常一样分配内存,但尽量以透明方式为应用程序提供大容量页面支持的存储空间。理想情况下,不需要更改应用程序,但我们会看到应用程序如何从了解 THP 的可用性中获益。但在实际应用中,会产生内存开销(因为会为小文件分配整个大页面)或时间开销(因为 THP 有时需要对内存进行碎片整理以分配页面)。好在有一个中间方案:通过 madvise(2) 可以让应用程序告诉 Linux 在哪里使用 THP。 不明白为什么术语中会交替使用 "large "和 "huge"。总之,OpenJDK 支持这两种模式: $ java -XX:+PrintFlagsFinal 2>&1 | grep Huge bool UseHugeTLBFS = false {product} {default} bool UseTransparentHugePages = false {product} {default} $ java -XX:+PrintFlagsFinal 2>&1 | grep LargePage bool UseLargePages = false {pd product} {default} -XX:+UseHugeTLBFS 将 Java 堆映射到 hugetlbfs 中,后者应单独准备。
JVM 剖析花园:1 - 锁粗化及循环

JVM 剖析花园:1 - 锁粗化及循环

D瓜哥
“JVM 剖析花园”是由 JVM 研发专家及性能极客 Aleksey Shipilëv 撰写的一个系列文章,专门介绍一些有关 JVM 的基本知识。笔者也是前几年无意间发现的一片宝藏文章。早就有翻译过来,介绍给大家的想法,可惜一直未能付诸实践。最近在查资料时,无意间又翻到了这个系列,遂下定决心,完成这个萌发已久的小想法。 为了便于理解,对该系列的名字做了微调,原文是“JVM Anatomy Quarks”,将原文的“Quarks”(夸克)翻译为了“花园”。 “JVM 解剖花园”是一个正在进行中的小型系列文章,每篇文章都会介绍一些有关 JVM 的基本知识。这个名字强调了一个事实,即单篇文章不能孤立地看待,这里描述的大部分内容都会很容易地相互影响。 阅读这篇文章大约需要 5-10 分钟。因此,它只针对单一主题、单一测试、单一基准和单一观察进行深入探讨。这里的证据和讨论可能是轶事,并没有对错误、一致性、写作风格、语法和语义错误、重复或一致性进行实际审查。请自行承担使用和/或信任的风险。 以上是该系列介绍。这里介绍一次,后续文章不再赘述。 问题 众所周知,Hotspot 可以进行 锁粗化优化,有效合并多个相邻的锁定块,从而减少锁定开销。它能有效地对如下代码做优化: synchronized (obj) { // statements 1 } synchronized (obj) { // statements 2 } 优化后: synchronized (obj) { // statements 1 // statements 2 } 现在,今天提出的一个有趣问题是:Hotspot 是否会对循环进行这种优化?例如: for (...) { synchronized (obj) { // something } } 是否会被优化成如下这样: synchronized (this) { for (...) { // something } } 理论上,没有什么能阻止我们这样做。我们甚至可以把这种优化看作是 循环判断外提,只不过这里是针对锁而已。然而,这样做的缺点是有可能使锁变得过于粗糙,从而导致特定线程在执行大循环时占用锁。
深入理解 Java 代码块

深入理解 Java 代码块

D瓜哥
在 Java 虚拟机操作码探秘:常量指令 中对 Java 虚拟机操作码中关于常量操作的指令(操作码)做了初步介绍。估计会有人疑问:文中的“栈”、“栈顶”等是什么?接下来就准备解答这些疑问。 在答疑解惑之前,先来了解一下 Java 编译器对 Java 代码中的代码块是如何处理的?常见的代码块有普通代码块和静态代码块,下面对其做分别介绍。由于涉及到构造函数,所以,先对构造函数做一个介绍。 构造函数 无构造函数 先来看看当没有声明构造函数时,编译结果是什么样的: /** * 无构造函数示例 * * @author D瓜哥 · https://www.diguage.com */ public class Example { } 编译后,使用 javap -c 查看一下编译结果: $ javap -c Example Compiled from "Example.java" public class Example { public Example(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return } 从结果上来看:编译器自动给没有声明构造函数的类,生成了一个无参构造函数,并且在其中调用了父类(这里是 Object)的无参构造函数。这是大家都熟知的基础知识。 有参构造函数 再来看看当有声明参数的构造函数时,编译结果是什么样的: /** * 有参构造函数示例 * * @author D瓜哥 · https://www.
Java 虚拟机操作码探秘:常量指令

Java 虚拟机操作码探秘:常量指令

D瓜哥
在 Java 虚拟机指令(操作码)集 中给出了一个操作码的列表。针对所有的指令,仅仅给出了一个大概介绍,对理解来说可以说毫无助力。为了弥补这个短板,这里也学习 “Hessian 协议解释与实战”系列 那样,来一个详细解释和实战,配合实例来做个深入分析和讲解。这是这个系列的第一篇文章,就以列表中第一部分“常量”指令开始。 从 Java 虚拟机指令(操作码)集 列表上来看,一共 21 个指令;按照处理数据的类型,合并同类项后,剩下有 nop、 aconst_null、 iconst_<i>、 lconst_<l>、 fconst_<f>、 dconst_<d>、 bipush、 sipush、 ldc 和 ldc2_w 等几个指令。下面,按照顺序,对其进行一一讲解。 操作码助记符的首字母一般是有特殊含义的,表示操作码所作用的数据类型: i 代表对 int 类型的数据操作; l 代表 long; s 代表 short; b 代表 byte;c 代表 char;f 代表 float, d 代表 double; a 代表 reference。 尖括号之间的字母指定了指令隐含操作数的数据类型,<n> 代表非负的整数; <i> 代表是 int 类型数据; <l> 代表 long 类型; <f> 代表 float 类型; <d> 代表 double 类型。 另外还需要指出一点:这种指令表示法在整个 Java 虚拟机规范之中都是通用的。
Java 虚拟机指令(操作码)集

Java 虚拟机指令(操作码)集

D瓜哥
最近在研究 Java 虚拟机字节码。在 《Java虚拟机规范》 看到一个整理完整的 Java 虚拟机指令集(也叫操作码)列表。转载过来,方便查阅。 关于 Java 虚拟机指令(操作码),准备写一个“探秘”系列: Java 虚拟机操作码探秘:常量指令 — 重点介绍一下关于“常量”指令。 分类 操作码 助记符 指令含义 常量 0 0x00 nop 什么都不做 1 0x01 aconst_null 将 null 推送至栈顶 2 0x02 iconst_m1 将 int 类型 -1 推送至栈顶 3 0x03 iconst_0 将 int 类型 0 推送至栈顶 4 0x04 iconst_1 将 int 类型 1 推送至栈顶 5 0x05 iconst_2 将 int 类型 2 推送至栈顶 6 0x06 iconst_3 将 int 类型 3 推送至栈顶