戴老师的博客 - javagc
type: channel
link: https://tushu.info/category/javagc/
description: javagc
items: [翻译]JSR 133 (Java Memory Model.html) FAQ
items: 我对Java内存模型的理解
items: Java的内存回收机制
items: JSR 133 (Java Memory Model.html) FAQ

[翻译]JSR 133 (Java Memory Model.html) FAQ
type: item
date: 2026-07-09T04:39:19+00:00
description: 目录[TOC]原文地址： http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#whatismm 作者： Jeremy Manson and Brian Goetz 时间： February 2004到底什么是内存模型？在一个多处理器系统中，处理器通常拥有多层缓存。这些缓存能够高速获取数据（因为缓存中的数据离处理器更近，访问路径为：CPU -> 寄存器 -> 缓存 -> 主存），并能减少主存的访问频率以降低总线的繁忙程度（因为很多操作可以通过缓存数据自满足，不需要与主存交互），从而提高 CPU 整体的运行速度。虽然缓存可以明显优化性能，但缓存技术的引入也带来了很多挑战。例如：当两个处理器同时访问同一个内存地址时会发生什么？在什么情况下这两个处理器会看到相同的值？缓存的引入是否会造成数据不一致问题？在处理器层面，内存模型需要定义必要且充足的条件限制，以保证一个处理器写入主存的数据对其他处理器的可见性，并且其他处理器的写入操作也可以对当前处理器可见。一些处理器设计展现出的是强内存模型：所有处理器读到的同一内存地址的数据都是一致的。还有些处理器设计展现出的是弱内存模型：需要一些内存栅栏（Memory Barrier）去将当前处理器缓存的数据刷入主存，或将当前处理器缓存置为无效。通过这些内存栅栏来保证处理器读写的数据一致性。这些内存栅栏通常在 lock/unlock 指令发生时被执行；另外，在语言层面这些内存栅栏通常不会被程序员直接感知。在强内存模型中，通常写程序会比较简单，因为对内存屏障的使用需求较少。然而，有时在一些非常强一致的内存模型中，使用内存栅栏也是非常必要的；通常这些内存栅栏的插入位置也是违反直觉的，令程序员摸不着头脑。最近处理器设计的趋势是鼓励弱内存模型的，因为这种缓存一致性的“弱化”在多处理器、大内存容量场景下带来了更强的扩展性。编译器的重排序使得线程间可见性问题更加复杂。例如：编译器可能为了提高效率会延后执行一个程序中的写操作，当然这种重排序只要不影响程序的整体语义就是完全允许的。那么一旦编译器将一个操作重排序，使得这个操作较后执行，那么这个被重排序的操作执行之前，其他线程是无法看到的。同样的情况也会出现在缓存中。此外，写入主存操作也可以向前重排序，提前执行。那么对于其他线程来说，会看见一个被提前执行了的写入操作。所有这些允许编译器、运行时、硬件重排序的灵活性，可以使机器代码在一个最优的顺序里执行，以获取最优性能。当然这些重排序优化，都是在当前内存模型的边界范围内。以下代码可以展示一个重排序的简单示例：class Reordering { int x = 0, y = 0; public void writer() { x = 1; y = 2; } public void reader() { int r1 = y; int r2 = x; } }当以上代码被两个线程并发执行，对 y 变量的读取会看到 y 的值为 2，因为 y=2 这行代码在 x=1 之后，所以程序员就会认为读取 y 的值发现是 2，那么 x 的值肯定是 1。然而，并不一定。writer() 中的代码也许已经被重排序后执行了。实际上可能会是这样的执行情况：线程 A 执行 writer() 方法，先执行了 y=2（被排序到首行执行），接着另一个线程 B 开始执行 reader() 方法，读取到 y 的值为 2，x 的值为 0 后，线程 A 才开始执行 x=1 的代码指令。最终线程 B 看到的就是 r1=2; r2=0。JMM（Java Memory Model）描述的是多线程代码中什么样的行为是合法的，线程间如何通过主存相互通信。JMM 描述的是一个程序中变量之间的相互影响；以及一个真实的计算机中，变量在主存中或者寄存器中的读写访问细节。并且 JMM 的正确实现可以不受各种各样的硬件限制、可以不受各种各样的编译器优化策略的限制，（跨平台）实现统一的内存模型。Java 中的 volatile、synchronized、final 关键字是为了帮助程序员在 Java 语言层面向编译器提出并发性的需求。JMM 规定了 volatile、synchronized 的行为，更重要的是确保一个被正确同步的 Java 程序可以畅通无阻的在各种各样的处理器上正确执行。这个问题总结（非原文）：缓存提升 CPU 整体效率，它的两个优势：缓存比主存的使用速度快、效率高；缓存通过部分替代主存的使用，减少总线阻塞。缓存虽然能明显提高 CPU 整体性能，但会存在缓存一致性问题。CPU 层面，JMM 通常必须定义一些约束，以保证一个 CPU 对主存的写操作，能够被其他 CPU 可见。 JMM 有强内存模型、弱内…

我对Java内存模型的理解
type: item
date: 2026-07-09T04:38:50+00:00
description: 编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。引言在所有编程语言中，「内存模型」（Memory Model）都是一个核心概念。区别于微架构层面的内存模型，高级语言的内存模型通常涵盖了编译器（Compiler）与微架构（Microarchitecture）两部分。笔者曾研究过 Java、C# 和 Go 语言的内存模型，发现其核心思想大同小异，主要差异在于具体实现细节。本文将重点探讨 Java 内存模型（Java Memory Model, JMM）。Java 内存模型抽象图提到 Java 内存模型，开发者通常会对下图非常熟悉：这张图揭示了线程运行时的内存交互机制：每个线程拥有一块专用的「工作内存」（Work Memory）。Java 程序将变量同步到线程所在的工作内存后，线程操作的是工作内存中的变量副本。至于工作内存中的变量值何时同步回「主内存」（Main Memory），则是不可预期的。为了保证线程安全，Java 内存模型规定通过使用关键词 synchronized 或 volatile 来满足特定的约束：volatile：保证读写操作直接针对主内存中的变量。synchronized：保证在代码块开始时将主内存的值同步到工作内存，而在块结束时将变量同步回主内存。基于以上描述，理论上我们可以写出线程安全的 Java 程序，JDK 也在很大程度上屏蔽了底层细节。工作内存的真实含义然而，当深入探究 JVM 实现时，你会发现实际上并不存在物理意义上的「工作内存」，即内存中并不会专门分配一块空间来运行 Java 程序。那么，「工作内存」究竟指代什么？这个问题曾困扰笔者许久，因为在 JVM 实现中很难找到与主内存同步的直接代码。例如，当使用 volatile 时，在源代码层面往往只能看到类似以下的汇编指令调用：__asm__ volatile ("lock; addl $0,0(%%esp.html)" : : : "cc", "memory");这条指令在部分微架构上的主要功能是防止指令重排（Instruction Reordering），即确保该指令前后的其它指令不会越过这个界限执行[注 1]。硬件层面的内存一致性在现代 x86/x64 微架构中，读写内存的一致性主要通过 MESI 协议（Intel 使用 MESI-F，AMD 使用 MOESI）来保证[注 2]。MESI 的状态转换图如下：更详细的中文文档描述可参考：http://blog.csdn.net/zhuliting/article/details/6210921编译器与微架构的优化既然物理上不存在「工作内存」，那么 Java 内存模型中所说的概念究竟映射到哪里？笔者的理解是，「工作内存」是一个虚拟概念，其承载主体主要包含两部分：编译器微架构编译器优化编译器的目标通常是执行速度越快越好。因此，编译器会尽量减少从内存读取数据的次数。如果一个数据已被加载到寄存器中，直接使用寄存器中的值性能最高。但这可能导致线程读不到最新的值。通过在 Java 语言中为变量加上 volatile 修饰符，可以强制告诉编译器该变量必须从主内存获得，此时编译器将不会进行此类缓存优化【案例可参考参考资料 5（一个 .Net 示例）】。微架构与指令重排对于微架构而言，在 x86/x64 环境下，CPU 在执行指令时可能会进行指令重排，即编译器生成的指令顺序与 CPU 实际执行的顺序可能不一致。当使用变量作为信号标志时，这种重排可能导致严重问题。例如以下代码：x = 0; y = 0; i = 0; j = 0; // thread A y = 1; x = 1; // thread B i = x; j = y;上述代码执行后，i 和 j 的值会是多少？答案是：00、01、10、11 均有可能。若希望得到确定的结果，则需要通过 synchronized（或者 java.util.concurrent.locks）来进行线程间同步。总结综上所述，笔者对 Java 内存模型的理解如下：在编译器各种优化及多种类型的微架构平台上，Java 语言规范制定者试图创建一个虚拟的概念（即 JMM），并传递给 Java 程序员，使他们能够基于这个虚拟概念写出线程安全的程序。而编译器实现者会根据 Java 语言规范中的各种约束，在不同的平台上达成程序员所需的线程安全目标。说明与参考资料版本说明：本文基于 Java 5（JSR-133）及之后的内存模型规范撰写。现代 JVM 实现细节可能随版本迭代而变化，请以官方文档为准。文中汇编示例涉及 32 位寄存器（%%esp），在 64 位环境下可能有所不同。注释与参考文献：[注 1] 关于 lock 前缀的…

Java的内存回收机制
type: item
date: 2026-07-09T04:34:36+00:00
description: 在Java中，它的内存管理包括两方面：内存分配（创建Java对象的时候）和内存回收，这两方面工作都是由JVM自动完成的，降低了Java程序员的学习难度，避免了像C/C++直接操作内存的危险。但是，也正因为内存管理完全由JVM负责，所以也使Java很多程序员不再关心内存分配，导致很多程序低效，耗内存。因此就有了Java程序员到最后应该去了解JVM，才能写出更高效，充分利用有限的内存的程序。1.Java在内存中的状态首先我们先写一个代码为例子：Person.javapackage test; import java.io.Serializable; public class Person implements Serializable { static final long serialVersionUID = 1L; String name; // 姓名 Person friend; //朋友 public Person() {} public Person(String name.html) { super(); this.name = name; } }Test.javapackage test; public class Test{ public static void main(String[] args.html) { Person p1 = new Person("Kevin".html); Person p2 = new Person("Rain".html); Person p3 = new Person("Sunny".html); p1.friend = p2; p3 = p2; p2 = null; } }把上面Test.java中main方面里面的对象引用画成一个从main方法开始的对象引用图的话就是这样的（顶点是对象和引用，有向边是引用关系）：当程序运行起来之后，把它在内存中的状态看成是有向图后，可以分为三种：1）可达状态：在一个对象创建后，有一个以上的引用变量引用它。在有向图中可以从起始顶点导航到该对象，那它就处于可达状态。2）可恢复状态：如果程序中某个对象不再有任何的引用变量引用它，它将先进入可恢复状态，此时从有向图的起始顶点不能再导航到该对象。在这个状态下，系统的垃圾回收机制准备回收该对象的所占用的内存，在回收之前，系统会调用finalize()方法进行资源清理，如果资源整理后重新让一个以上引用变量引用该对象，则这个对象会再次变为可达状态；否则就会进入不可达状态。3）不可达状态：当对象的所有关联都被切断，且系统调用finalize()方法进行资源清理后依旧没有使该对象变为可达状态，则这个对象将永久性失去引用并且变成不可达状态，系统才会真正的去回收该对象所占用的资源。上述三种状态的转换图如下：2.Java对对象的4种引用1）强引用 ：创建一个对象并把这个对象直接赋给一个变量，eg ：Person person = new Person("sunny".html); 不管系统资源有么的紧张，强引用的对象都绝对不会被回收，即使他以后不会再用到。2）软引用 ：通过SoftReference类实现，eg : SoftReference p = new SoftReference(new Person("Rain".html));,内存非常紧张的时候会被回收，其他时候不会被回收，所以在使用之前要判断是否为null从而判断他是否已经被回收了。3）弱引用 ：通过WeakReference类实现，eg : WeakReference p = new WeakReference(new Person("Rain".html));不管内存是否足够，系统垃圾回收时必定会回收。4）虚引用 ：不能单独使用，主要是用于追踪对象被垃圾回收的状态。通过PhantomReference类和引用队列ReferenceQueue类联合使用实现，eg ：package test; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class Test{ public static void main(String[] args.html) { //创建一个对象 Person person = new Person("Sunny".html); //创建一个引用队列 ReferenceQueue<Person> rq = new ReferenceQueue<Person>(); //创建一个虚引用，让此虚引用引用到person对象 PhantomReference<Person> pr = new PhantomRefere…

JSR 133 (Java Memory Model.html) FAQ
type: item
date: 2026-07-09T04:32:00+00:00
description: 目录[TOC]原文地址： http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#whatismm Jeremy Manson and Brian Goetz, February 2004到底什么是内存模型？在一个多处理器系统中，处理器通常拥有多层缓存。这些多层缓存能够高速获取数据（因为缓存中的数据与处理器更近，cpu—>寄存器—>缓存—>主存）和减少主存的访问频率以降低总线的繁忙程度(因为很多操作可以通过缓存的数据自满足，不需要在主存交互.html)来提高cpu整体的运行速度。虽然缓存可以明显的优化性能，但是缓存技术的引入也带来了很多调整。比如：当两个处理器同时访问同一个内存地址时会发生什么？在什么情况下这两个处理器会看到相同的值？缓存的引入会造成数据不一致问题?在处理器层面，一个内存模型中需要定义必要的、充足的条件限制，这些条件保证了一个处理器写入主存的数据对其他处理器的可见性，并且其他处理器的写入操作也可以对当前处理器可见。一些处理器设计所展现出的是一个强内存模型：所有处理器读到的同一内存地址的数据都是一致的。还有些处理器设计展现出一个较弱的内存模型:需要一些内存栅栏去将当前处理器缓存的数据刷入主存，或则将当前处理器缓存置为无效。通过这些内存栅栏去保证处理器读写的数据一致性。这些内存栅栏通常在lock\unlock指令发生时被被执行；另外，在语言层面这些内存栅栏是不会被程序员感知到的。在强内存模型中，通常写程序会比较简单，因为强内存模型中对内存屏障的使用需求较少。然而，有时在一些非常强一致内存模型中，使用内存栅栏也是非常必要的；通常这些内存栅栏的插入位置也是违反直觉的，令程序员摸不着头脑。最近处理器设计的趋势是鼓励弱内存模型的，因为这种缓存一致性 的"弱化"在多处理器、大内存容量场景下带来了更强的扩展性。编译器的重排序使得线程间可见性问题更加复杂。比如：编译器可能为了提高效率会延后执行一个程序中的写操作，当然这种重排序只要不影响程序的整体语义就是完全允许的。那么一旦编译器将一个操作重排序，使得这个操作较后执行，那么这个被重排序的操作执行之前， 其他线程是无法看到的。同样的情况也会出现在缓存中。此外，写入主存操作也可以向前重排序，提前执行。那么对于其他线程来说，会看见一个被提前执行了的写入操作。所有这些允许编译器、运行时、硬件重排序的灵活性，可以使机器代码在一个最优的顺序里执行，以获取最优性能。当然这些重排序优化，都是在当前内存模型的边界范围内。以下代码可以展示一个重排序的简单示例:Class Reordering { int x = 0, y = 0; public void writer() { x = 1; y = 2; } public void reader() { int r1 = y; int r2 = x; } } 当以上代码被两个线程并发执行，对y变量的读取会看到y的值为2，因为"y=2"这行代码在“x=1”之后，所以程序员就会认为 读取y的值发现是2，那么x的值肯定是1。然而，并不一定。writer()中的代码也许已经被重排序后执行了。实际上可能会是这样的执行情况：线程A执行writer()方法，先执行了y=2(被排序到首行执行.html)，接着另一个线程B开始执行reader()方法，读取到y的值为2，x的值为1后，线程A才开始执行x=1的代码指令。最终线程B看到的就是 r12;r20。JMM描述的是多线程代码中什么样的行为是合法的，线程间如何通过主存相互通信。JMM描述的是一个程序中变量之间的相互影响；以及一个真实的计算机中，变量在主存中或者寄存器中的读写访问细节。并且JMM的正确实现可以不受各种各样的硬件限制、可以不受 各种各样的编译器优化策略的限制，(跨平台.html)，实现统一的内存模型。java中的volatile、synchronized、final关键字是为了帮助程序员在java语言层面向编译器提出并发性的需求。JMM规定了 volatile、synchronized的行为，更重要的是确保一个被正确同步的java程序可以畅通无阻的在各种各样的处理器上正确执行这个问题的总结（非原文）： 1.缓存提升cpu整体效率，它的两个优势：缓存比主存的使用速度快效率高；缓存通过部门替代主存的使用，减少总线阻塞； 2.缓存虽然能明显提高cpu整体新能，但会存在缓存一致性问题。 3.cpu层面，JMM通常必须定义一些约束，以保证一个cpu对主存的写操作，能够被其他cpu可见。JMM有强内存模型、弱内存模型，这里的强指的是在这个模型中，一个cpu(或一个线程.html)写入内存的数据，对其他cpu(或其他线程.html)的可见性更强。强、弱…