Java 多线程 -- 如果我们有共享堆,为什么我们需要查看主内存 (RAM)?
Java Multi-threading -- Why do we need to look into main memory(RAM) if we have shared heap?
如果我们应用 volatile 关键字,线程从主内存而不是它自己的缓存中读取。如果我们有公共堆内存,那么为什么我们需要查看主内存?我们想说主内存 (RAM) 和堆是一样的吗?
If we have common heap memory then why do we need to look into main memory?
Java 堆存储在计算机的主内存中。
其他存储在主存中的还有线程栈、方法区、常量池。这些在 JVM 规范第 2.5 节中介绍 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5
这个问题有两个方面。
heap/RAM/main内存
在此上下文中,术语 RAM 和主存储器指的是同一事物。我将在本文的其余部分说 RAM post。这些术语在硬件级别和软件级别上都有意义。
堆是一个在软件级别使用的术语,仅表示可以在其中创建对象的 RAM 区域。
在软件层面,除了堆,还有栈。栈也是RAM中的一块内存区域。在 java 中,堆栈只能包含原始数据类型,如 int 或 long,以及指向堆内部对象的指针(内存地址)。
易变
在硬件层面,程序员不可见的,有许多内存缓存用于缓存经常重复使用的数据。这使得计算机 运行 更快,因为内存访问花费的时间更少。在几乎所有情况下,作为程序员的您都不会在任何地方看到这些缓存。您只能在某些特殊情况下注意到性能提升。
当多个 CPU 核心(想想线程)访问 RAM 中的相同内存地址时,它们访问的数据实际上可能来自 RAM 和 CPU 之间的缓存。如果一个变量 没有 标记为易失性,那么这些缓存可以包含不同的数据,因此不同的 CPU 核心将接收相同内存地址的不同数据。这通常是不希望的。因此存在 volatile 关键字。如果您将变量标记为易失性,CPU 内核确保它们直接从 RAM 读取并将更新内存写回 RAM 以确保其他 CPU 内核可以访问最新值。
If we apply volatile keyword, threads read from main memory instead of its own cache.
这是不正确的。 volatile 变量确保在该变量的写入和该变量的任何后续读取之间有一个 happens before edge。这导致访问顺序一致。如果您有 2 个冲突访问(2 个线程访问同一地址,其中至少一个是写入)并且这些冲突访问不是按边缘之前发生的顺序进行的,那么您就会发生数据争用,并且数据争用程序的行为是未定义。
你不应该考虑主内存和高速缓存;考虑 Java 内存模型,在这种情况下是易失性变量规则。缓存在 X86 上总是一致的;所以不可能从缓存中读取过时的数据。
仅供参考:RAM 是所有 CPU 之间的共享内存。堆是对 RAM 的一种使用,就像堆栈一样。在 Java 中,对象通常是在堆上创建的(我不知道任何基于堆栈的分配 JVM 实现;只有标量替换)。
如果我们应用 volatile 关键字,线程从主内存而不是它自己的缓存中读取。如果我们有公共堆内存,那么为什么我们需要查看主内存?我们想说主内存 (RAM) 和堆是一样的吗?
If we have common heap memory then why do we need to look into main memory?
Java 堆存储在计算机的主内存中。
其他存储在主存中的还有线程栈、方法区、常量池。这些在 JVM 规范第 2.5 节中介绍 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5
这个问题有两个方面。
heap/RAM/main内存
在此上下文中,术语 RAM 和主存储器指的是同一事物。我将在本文的其余部分说 RAM post。这些术语在硬件级别和软件级别上都有意义。 堆是一个在软件级别使用的术语,仅表示可以在其中创建对象的 RAM 区域。
在软件层面,除了堆,还有栈。栈也是RAM中的一块内存区域。在 java 中,堆栈只能包含原始数据类型,如 int 或 long,以及指向堆内部对象的指针(内存地址)。
易变
在硬件层面,程序员不可见的,有许多内存缓存用于缓存经常重复使用的数据。这使得计算机 运行 更快,因为内存访问花费的时间更少。在几乎所有情况下,作为程序员的您都不会在任何地方看到这些缓存。您只能在某些特殊情况下注意到性能提升。
当多个 CPU 核心(想想线程)访问 RAM 中的相同内存地址时,它们访问的数据实际上可能来自 RAM 和 CPU 之间的缓存。如果一个变量 没有 标记为易失性,那么这些缓存可以包含不同的数据,因此不同的 CPU 核心将接收相同内存地址的不同数据。这通常是不希望的。因此存在 volatile 关键字。如果您将变量标记为易失性,CPU 内核确保它们直接从 RAM 读取并将更新内存写回 RAM 以确保其他 CPU 内核可以访问最新值。
If we apply volatile keyword, threads read from main memory instead of its own cache.
这是不正确的。 volatile 变量确保在该变量的写入和该变量的任何后续读取之间有一个 happens before edge。这导致访问顺序一致。如果您有 2 个冲突访问(2 个线程访问同一地址,其中至少一个是写入)并且这些冲突访问不是按边缘之前发生的顺序进行的,那么您就会发生数据争用,并且数据争用程序的行为是未定义。
你不应该考虑主内存和高速缓存;考虑 Java 内存模型,在这种情况下是易失性变量规则。缓存在 X86 上总是一致的;所以不可能从缓存中读取过时的数据。
仅供参考:RAM 是所有 CPU 之间的共享内存。堆是对 RAM 的一种使用,就像堆栈一样。在 Java 中,对象通常是在堆上创建的(我不知道任何基于堆栈的分配 JVM 实现;只有标量替换)。