当前位置:文档之家› 深入理解Java内存模型

深入理解Java内存模型

深入理解Java内存模型
深入理解Java内存模型

目录
深入理解 Java 内存模型(一)——基础 ...................................................................................... 2 并发编程模型的分类............................................................................................................... 2 Java 内存模型的抽象.............................................................................................................. 2 重排序....................................................................................................................................... 4 处理器重排序与内存屏障指令 ............................................................................................... 5 happens-before ....................................................................................................................... 7 参考文献................................................................................................................................... 8 关于作者................................................................................................................................... 8 深入理解 Java 内存模型(二)——重排序 .................................................................................. 9 数据依赖性............................................................................................................................... 9 as-if-serial 语义 ....................................................................................................................... 9 程序顺序规则......................................................................................................................... 10 重排序对多线程的影响 ......................................................................................................... 10 参考文献................................................................................................................................. 13 关于作者................................................................................................................................. 13 深入理解 Java 内存模型(三)——顺序一致性 ........................................................................ 13 数据竞争与顺序一致性保证 ................................................................................................. 13 顺序一致性内存模型............................................................................................................. 13 同步程序的顺序一致性效果 ................................................................................................. 16 未同步程序的执行特性 ......................................................................................................... 17 参考文献................................................................................................................................. 19 作者简介................................................................................................................................. 20 深入理解 Java 内存模型(四)——volatile ............................................................................... 20 volatile 的特性 ....................................................................................................................... 20 volatile 写-读建立的 happens before 关系 ........................................................................ 22 volatile 写-读的内存语义 ..................................................................................................... 23 volatile 内存语义的实现 ....................................................................................................... 25 JSR-133 为什么要增强 volatile 的内存语义 ...................................................................... 29 参考文献................................................................................................................................. 30 作者简介................................................................................................................................. 30 深入理解 Java 内存模型(五)——锁 ........................................................................................ 30 锁的释放-获取建立的 happens before 关系 ..................................................................... 30 锁释放和获取的内存语义 ..................................................................................................... 32 锁内存语义的实现................................................................................................................. 34 concurrent 包的实现 ............................................................................................................ 40 参考文献................................................................................................................................. 41 关于作者................................................................................................................................. 42 深入理解 Java 内存模型(六)——final .................................................................................... 42 写 final 域的重排序规则 ....................................................................................................... 43 读 final 域的重排序规则 ....................................................................................................... 44 如果 final 域是引用类型 ....................................................................................................... 46

为什么 final 引用不能从构造函数内“逸出”........................................................................ 48 final 语义在处理器中的实现 ................................................................................................ 49 JSR-133 为什么要增强 final 的语义 ................................................................................... 50 参考文献................................................................................................................................. 50 关于作者................................................................................................................................. 50 深入理解 Java 内存模型(七)——总结 .................................................................................... 50 处理器内存模型..................................................................................................................... 50 JMM,处理器内存模型与顺序一致性内存模型之间的关系............................................ 52 JMM 的设计 .......................................................................................................................... 53 JMM 的内存可见性保证 ...................................................................................................... 56 JSR-133 对旧内存模型的修补 ............................................................................................. 56 参考文献................................................................................................................................. 57 关于作者................................................................................................................................. 57
深入理解 Java 内存模型(一)——基础
https://www.doczj.com/doc/358011814.html,/cn/articles/java-memory-model-1
并发编程模型的分类
在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里 的线程是指并发执行的活动实体)。通信是指线程之间以何种机制来交换信息。在命令式编 程中,线程之间的通信机制有两种:共享内存和消息传递。 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公 共状态来隐式进行通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须 通过明确的发送消息来显式进行通信。 同步是指程序用于控制不同线程之间操作发生相对顺序的机制。 在共享内存并发模型里, 同 步是显式进行的。 程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。 在消 息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。 Java 的并发采用的是共享内存模型,Java 线程之间的通信总是隐式进行,整个通信过程对 程序员完全透明。如果编写多线程程序的 Java 程序员不理解隐式进行的线程之间通信的工 作机制,很可能会遇到各种奇怪的内存可见性问题。
Java 内存模型的抽象
在 java 中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本 文使用“共享变量”这个术语代指实例域, 静态域和数组元素) 。 局部变量 (Local variables) , 方法定义参数(java 语言规范称之为 formal method parameters)和异常处理器参数 (exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也 不受内存模型的影响。

Java 线程之间的通信由 Java 内存模型(本文简称为 JMM)控制,JMM 决定一个线程对共 享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM 定义了线程和主内存之间 的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个 私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本 地内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其 他的硬件和编译器优化。Java 内存模型的抽象示意图如下:
从上图来看,线程 A 与线程 B 之间如要通信的话,必须要经历下面 2 个步骤: 1. 首先,线程 A 把本地内存 A 中更新过的共享变量刷新到主内存中去。 2. 然后,线程 B 到主内存中去读取线程 A 之前已更新过的共享变量。 下面通过示意图来说明这两个步骤:

如上图所示,本地内存 A 和 B 有主内存中共享变量 x 的副本。假设初始时,这三个内存中 的 x 值都为 0。线程 A 在执行时,把更新后的 x 值(假设值为 1)临时存放在自己的本地内 存 A 中。当线程 A 和线程 B 需要通信时,线程 A 首先会把自己本地内存中修改后的 x 值刷 新到主内存中,此时主内存中的 x 值变为了 1。随后,线程 B 到主内存中去读取线程 A 更 新后的 x 值,此时线程 B 的本地内存的 x 值也变为了 1。 从整体来看,这两个步骤实质上是线程 A 在向线程 B 发送消息,而且这个通信过程必须要 经过主内存。JMM 通过控制主内存与每个线程的本地内存之间的交互,来为 java 程序员提 供内存可见性保证。
重排序
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型: 1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执 行顺序。 2. 指令级并行的重排序。 现代处理器采用了指令级并行技术 (Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指 令的执行顺序。 3. 内存系统的重排序。 由于处理器使用缓存和读/写缓冲区, 这使得加载和存储操作看上去可 能是在乱序执行。 从 java 源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

上述的 1 属于编译器重排序,2 和 3 属于处理器重排序。这些重排序都可能会导致多线程程 序出现内存可见性问题。对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器 重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM 的处理器重排序 规则会要求 java 编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers, intel 称之为 memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不 是所有的处理器重排序都要禁止)。 JMM 属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止 特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。
处理器重排序与内存屏障指令
现代的处理器使用写缓冲区来临时保存向内存写入的数据。 写缓冲区可以保证指令流水线持 续运行,它可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。同时,通过以 批处理的方式刷新写缓冲区, 以及合并写缓冲区中对同一内存地址的多次写, 可以减少对内 存总线的占用。虽然写缓冲区有这么多好处,但每个处理器上的写缓冲区,仅仅对它所在的 处理器可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操 作的执行顺序,不一定与内存实际发生的读/写操作顺序一致!为了具体说明,请看下面示 例: Processor A Processor B a = 1; //A1 b = 2; //B1 x = b; //A2 y = a; //B2 初始状态:a = b = 0 处理器允许执行后得到结果:x = y = 0 假设处理器 A 和处理器 B 按程序的顺序并行执行内存访问,最终却可能得到 x = y = 0 的结 果。具体的原因如下图所示:

这里处理器 A 和处理器 B 可以同时把共享变量写入自己的写缓冲区(A1,B1),然后从内 存中读取另一个共享变量(A2,B2),最后才把自己写缓存区中保存的脏数据刷新到内存 中(A3,B3)。当以这种时序执行时,程序就可以得到 x = y = 0 的结果。 从内存操作实际发生的顺序来看,直到处理器 A 执行 A3 来刷新自己的写缓存区,写操作 A1 才算真正执行了。虽然处理器 A 执行内存操作的顺序为:A1->A2,但内存操作实际发生 的顺序却是:A2->A1。此时,处理器 A 的内存操作顺序被重排序了(处理器 B 的情况和处 理器 A 一样,这里就不赘述了)。 这里的关键是, 由于写缓冲区仅对自己的处理器可见, 它会导致处理器执行内存操作的顺序 可能会与内存实际的操作执行顺序不一致。 由于现代的处理器都会使用写缓冲区, 因此现代 的处理器都会允许对写-读操做重排序。 下面是常见处理器允许的重排序类型的列表: Load-LoadLoad-StoreStore-StoreStore-Load数据依赖 sparc-TSON N N Y N x86 N N N Y N ia64 Y Y Y Y N PowerPC Y Y Y Y N 上表单元格中的“N”表示处理器不允许两个操作重排序,“Y”表示允许重排序。 从上表我们可以看出:常见的处理器都允许 Store-Load 重排序;常见的处理器都不允许对 存在数据依赖的操作做重排序。sparc-TSO 和 x86 拥有相对较强的处理器内存模型,它们 仅允许对写-读操作做重排序(因为它们都使用了写缓冲区)。 ※注 1: sparc-TSO 是指以 TSO(Total Store Order)内存模型运行时, sparc 处理器的特性。 ※注 2:上表中的 x86 包括 x64 及 AMD64。 ※注 3:由于 ARM 处理器的内存模型与 PowerPC 处理器的内存模型非常类似,本文将忽 略它。 ※注 4:数据依赖性后文会专门说明。 为了保证内存可见性,java 编译器在生成指令序列的适当位置会插入内存屏障指令来禁止 特定类型的处理器重排序。JMM 把内存屏障指令分为下列四类: 屏障类型 LoadLoad Barriers 指令示例 说明 确保 Load1 数据的装载, 之前于 Load2 及所有后续装载指令的 装载。 确保 Store1 数据对其他处理器可见(刷新到内存) ,之前于 Store2 及所有后续存储指令的存储。 确保 Load1 数据装载,之前于 Store2 及所有后续的存储指令 刷新到内存。 确保 Store1 数据对其他处理器变得可见(指刷新到内存) ,之 前于 Load2 及所有后续装载指令的装载。StoreLoad Barriers
Load1; LoadLoad; Load2 Store1; StoreStore StoreStore; Barriers Store2 Load1; LoadStore LoadStore; Barriers Store2 StoreLoad Store1; Barriers StoreLoad;

会使该屏障之前的所有内存访问指令(存储和装载指令)完成 之后,才执行该屏障之后的内存访问指令。 StoreLoad Barriers 是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处 理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂 贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)。 Load2
happens-before
从 JDK5 开始, java 使用新的 JSR -133 内存模型 (本文除非特别说明, 针对的都是 JSR- 133 内存模型)。JSR-133 提出了 happens-before 的概念,通过这个概念来阐述操作之间的内 存可见性。 如果一个操作执行的结果需要对另一个操作可见, 那么这两个操作之间必须存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线 程之间。 与程序员密切相关的 happens-before 规则如下: 程序顺序规则: 一个线程中的每个操作, happens- before 于该线程中的任意后续操作。 ? 监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加 锁。 ? volatile 变量规则:对一个 volatile 域的写,happens- before 于任意后续对这个 volatile 域的读。 ? 传递性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。 注意,两个操作之间具有 happens-before 关系,并不意味着前一个操作必须要在后一个操 作之前执行!happens-before 仅仅要求前一个操作(执行的结果)对后一个操作可见,且 前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。happens- before 的定义很微妙,后文会具体说明 happens-before 为什么要这 么定义。 happens-before 与 JMM 的关系如下图所示:
?

如上图所示,一个 happens-before 规则通常对应于多个编译器重排序规则和处理器重排序 规则。对于 java 程序员来说,happens-before 规则简单易懂,它避免程序员为了理解 JMM 提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现。
参考文献
1. 2. 3. 4. 5. 6. 7. 8. Programming Language Pragmatics, Third Edition The Java Language Specification, Third Edition JSR-133: Java Memory Model and Thread Specification Java theory and practice: Fixing the Java Memory Model, Part 2 Understanding POWER Multiprocessors Concurrent Programming on Windows The Art of Multiprocessor Programming Intel? 64 and IA-32 ArchitecturesvSoftware Developer’s Manual Volume 3A: System Programming Guide, Part 1 9. Java Concurrency in Practice 10. The JSR-133 Cookbook for Compiler Writers
关于作者
程晓明,Java 软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程, 就职于富士通南大。个人邮箱:asst2003@https://www.doczj.com/doc/358011814.html,。

深入理解 Java 内存模型(二)——重排序
https://www.doczj.com/doc/358011814.html,/cn/articles/java-memory-model-2
数据依赖性
如果两个操作访问同一个变量, 且这两个操作中有一个为写操作, 此时这两个操作之间就存 在数据依赖性。数据依赖分下列三种类型: 名称 代码示例 说明 写后读a = 1;b = a;写一个变量之后,再读这个位置。 写后写a = 1;a = 2;写一个变量之后,再写这个变量。 读后写a = b;b = 1;读一个变量之后,再写这个变量。 上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。 前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守 数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。 注意, 这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作, 不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。
as-if-serial 语义
as-if-serial 语义的意思指: 不管怎么重排序 (编译器和处理器为了提高并行度) , (单线程) 程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守 as-if-serial 语义。 为了遵守 as-if-serial 语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因 为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被 编译器和处理器重排序。为了具体说明,请看下面计算圆面积的代码示例:
double pi = 3.14; double r = 1.0; //A //B
double area = pi * r * r; //C
上面三个操作的数据依赖关系如下图所示:

如上图所示,A 和 C 之间存在数据依赖关系,同时 B 和 C 之间也存在数据依赖关系。因此 在最终执行的指令序列中,C 不能被重排序到 A 和 B 的前面(C 排到 A 和 B 的前面,程序 的结果将会被改变)。但 A 和 B 之间没有数据依赖关系,编译器和处理器可以重排序 A 和 B 之间的执行顺序。下图是该程序的两种执行顺序:
as-if-serial 语义把单线程程序保护了起来,遵守 as-if-serial 语义的编译器,runtime 和处理 器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。 as-if-serial 语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。
程序顺序规则
根据 happens- before 的程序顺序规则,上面计算圆的面积的示例代码存在三个 happensbefore 关系: 1. A happens- before B; 2. B happens- before C; 3. A happens- before C; 这里的第 3 个 happens- before 关系,是根据 happens- before 的传递性推导出来的。 这里 A happens- before B,但实际执行时 B 却可以排在 A 之前执行(看上面的重排序后的 执行顺序)。在第一章提到过,如果 A happens- before B,JMM 并不要求 A 一定要在 B 之前执行。JMM 仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按 顺序排在第二个操作之前。这里操作 A 的执行结果不需要对操作 B 可见;而且重排序操作 A 和操作 B 后的执行结果,与操作 A 和操作 B 按 happens- before 顺序执行的结果一致。 在这种情况下,JMM 会认为这种重排序并不非法(not illegal),JMM 允许这种重排序。 在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽 可能的开发并行度。编译器和处理器遵从这一目标,从 happens- before 的定义我们可以看 出,JMM 同样遵从这一目标。
重排序对多线程的影响
现在让我们来看看,重排序是否会改变多线程程序的执行结果。请看下面的示例代码:
class ReorderExample { int a = 0;

boolean flag = false;
public void writer() { a = 1; flag = true; } //1 //2
Public void reader() { if (flag) { int i = a * a; …… } } } //3 //4
flag 变量是个标记,用来标识变量 a 是否已被写入。这里假设有两个线程 A 和 B,A 首先执 行 writer()方法,随后 B 线程接着执行 reader()方法。线程 B 在执行操作 4 时,能否看到线 程 A 在操作 1 对共享变量 a 的写入? 答案是:不一定能看到。 由于操作 1 和操作 2 没有数据依赖关系, 编译器和处理器可以对这两个操作重排序; 同样, 操作 3 和操作 4 没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们 先来看看,当操作 1 和操作 2 重排序时,可能会产生什么效果?请看下面的程序执行时序 图:

如上图所示,操作 1 和操作 2 做了重排序。程序执行时,线程 A 首先写标记变量 flag,随 后线程 B 读这个变量。由于条件判断为真,线程 B 将读取变量 a。此时,变量 a 还根本没 有被线程 A 写入,在这里多线程程序的语义被重排序破坏了! ※注:本文统一用红色的虚箭线表示错误的读操作,用绿色的虚箭线表示正确的读操作。 下面再让我们看看,当操作 3 和操作 4 重排序时会产生什么效果(借助这个重排序,可以 顺便说明控制依赖性)。下面是操作 3 和操作 4 重排序后,程序的执行时序图:
在程序中,操作 3 和操作 4 存在控制依赖关系。当代码中存在控制依赖性时,会影响指令 序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相 关性对并行度的影响。以处理器的猜测执行为例,执行线程 B 的处理器可以提前读取并计

算 a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓 存中。当接下来操作 3 的条件判断为真时,就把该计算结果写入变量 i 中。 从图中我们可以看出,猜测执行实质上对操作 3 和 4 做了重排序。重排序在这里破坏了多 线程程序的语义! 在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是 as-if-serial 语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的 操作重排序,可能会改变程序的执行结果。
参考文献
1. 2. 3. 4. 5. Computer Architecture: A Quantitative Approach, 4th Edition Concurrent Programming on Windows Concurrent Programming in Java?: Design Principles and Pattern JSR-133: Java Memory Model and Thread Specification JSR 133 (Java Memory Model) FAQ
关于作者
程晓明,Java 软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程, 就职于富士通南大。个人邮箱:asst2003@https://www.doczj.com/doc/358011814.html,。
深入理解 Java 内存模型 (三) ——顺序一致性
https://www.doczj.com/doc/358011814.html,/cn/articles/java-memory-model-3
数据竞争与顺序一致性保证
当程序未正确同步时,就会存在数据竞争。java 内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量, ? 在另一个线程读同一个变量, ? 而且写和读没有通过同步来排序。 当代码中包含数据竞争时, 程序的执行往往产生违反直觉的结果 (前一章的示例正是如此) 。 如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序。 JMM 对正确同步的多线程程序的内存一致性做了如下保证:
? ?
如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consistent)--即 程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同 (马上我们将会看到, 这对于程序员来说是一个极强的保证) 。这里的同步是指广义上的同步,包括对常用同 步原语(lock,volatile 和 final)的正确使用。
顺序一致性内存模型
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型, 它为程序员提供了极 强的内存可见性保证。顺序一致性内存模型有两大特性:

一个线程中的所有操作必须按照程序的顺序来执行。 ? (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内 存模型中,每个操作都必须原子执行且立刻对所有线程可见。 顺序一致性内存模型为程序员提供的视图如下:
?
在概念上, 顺序一致性模型有一个单一的全局内存, 这个内存通过一个左右摆动的开关可以 连接到任意一个线程。同时,每一个线程必须按程序的顺序来执行内存读/写操作。从上图 我们可以看出, 在任意时间点最多只能有一个线程可以连接到内存。 当多个线程并发执行时, 图中的开关装置能把所有线程的所有内存读/写操作串行化。 为了更好的理解,下面我们通过两个示意图来对顺序一致性模型的特性做进一步的说明。 假设有两个线程 A 和 B 并发执行。其中 A 线程有三个操作,它们在程序中的顺序是: A1->A2->A3。B 线程也有三个操作,它们在程序中的顺序是:B1->B2->B3。 假设这两个线程使用监视器来正确同步:A 线程的三个操作执行后释放监视器,随后 B 线 程获取同一个监视器。那么程序在顺序一致性模型中的执行效果将如下图所示:

现在我们再假设这两个线程没有做同步, 下面是这个未同步程序在顺序一致性模型中的执行 示意图:
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的, 但所有线程都只能看到一个一 致的整体执行顺序。以上图为例,线程 A 和 B 看到的执行顺序都是: B1->A1->A2->B2->A3->B3。 之所以能得到这个保证是因为顺序一致性内存模型中的每个操 作必须立即对任意线程可见。

但是,在 JMM 中就没有这个保证。未同步程序在 JMM 中不但整体的执行顺序是无序的, 而且所有线程看到的操作执行顺序也可能不一致。 比如, 在当前线程把写过的数据缓存在本 地内存中,且还没有刷新到主内存之前,这个写操作仅对当前线程可见;从其他线程的角度 来观察, 会认为这个写操作根本还没有被当前线程执行。 只有当前线程把本地内存中写过的 数据刷新到主内存之后,这个写操作才能对其他线程可见。在这种情况下,当前线程和其它 线程看到的操作执行顺序将不一致。
同步程序的顺序一致性效果
下面我们对前面的示例程序 ReorderExample 用监视器来同步, 看看正确同步的程序如何具 有顺序一致性。 请看下面的示例代码:
class SynchronizedExample { int a = 0; boolean flag = false;
public synchronized void writer() { a = 1; flag = true; }
public synchronized void reader() { if (flag) { int i = a; …… } } }
上面示例代码中,假设 A 线程执行 writer()方法后,B 线程执行 reader()方法。这是一个正 确同步的多线程程序。根据 JMM 规范,该程序的执行结果将与该程序在顺序一致性模型中 的执行结果相同。下面是该程序在两个内存模型中的执行时序对比图:

在顺序一致性模型中,所有操作完全按程序的顺序串行执行。而在 JMM 中,临界区内的代 码可以重排序(但 JMM 不允许临界区内的代码“逸出”到临界区之外,那样会破坏监视器的 语义)。JMM 会在退出监视器和进入监视器这两个关键时间点做一些特别处理,使得线程 在这两个时间点具有与顺序一致性模型相同的内存视图(具体细节后文会说明)。虽然线程 A 在临界区内做了重排序,但由于监视器的互斥执行的特性,这里的线程 B 根本无法“观察” 到线程 A 在临界区内的重排序。这种重排序既提高了执行效率,又没有改变程序的执行结 果。 从这里我们可以看到 JMM 在具体实现上的基本方针:在不改变(正确同步的)程序执行结 果的前提下,尽可能的为编译器和处理器的优化打开方便之门。
未同步程序的执行特性
对于未同步或未正确同步的多线程程序, JMM 只提供最小安全性: 线程执行时读取到的值, 要么是之前某个线程写入的值,要么是默认值(0,null,false),JMM 保证线程读操作读 取到的值不会无中生有(out of thin air)的冒出来。为了实现最小安全性,JVM 在堆上分配 对象时,首先会清零内存空间,然后才会在上面分配对象(JVM 内部会同步这两个操作)。 因此,在以清零的内存空间(pre-zeroed memory)分配对象时,域的默认初始化已经完成 了。

JMM 不保证未同步程序的执行结果与该程序在顺序一致性模型中的执行结果一致。因为未 同步程序在顺序一致性模型中执行时,整体上是无序的,其执行结果无法预知。保证未同步 程序在两个模型中的执行结果一致毫无意义。 和顺序一致性模型一样,未同步程序在 JMM 中的执行时,整体上也是无序的,其执行结果 也无法预知。同时,未同步程序在这两个模型中的执行特性有下面几个差异: 1. 顺序一致性模型保证单线程内的操作会按程序的顺序执行,而 JMM 不保证单线程内的操 作会按程序的顺序执行(比如上面正确同步的多线程程序在临界区内的重排序) 。这一点 前面已经讲过了,这里就不再赘述。 2. 顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而 JMM 不保证所有线程能 看到一致的操作执行顺序。这一点前面也已经讲过,这里就不再赘述。 3. JMM 不保证对 64 位的 long 型和 double 型变量的读/写操作具有原子性, 而顺序一致性模 型保证对所有的内存读/写操作都具有原子性。 第 3 个差异与处理器总线的工作机制密切相关。在计算机中,数据通过总线在处理器和内 存之间传递。 每次处理器和内存之间的数据传递都是通过一系列步骤来完成的, 这一系列步 骤称之为总线事务(bus transaction)。总线事务包括读事务(read transaction)和写事务 (write transaction) 。 读事务从内存传送数据到处理器, 写事务从处理器传送数据到内存, 每个事务会读/写内存中一个或多个物理上连续的字。这里的关键是,总线会同步试图并发 使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其它所有的处理器和 I/O 设 备执行内存的读/写。下面让我们通过一个示意图来说明总线的工作机制:

如上图所示, 假设处理器 A, B 和 C 同时向总线发起总线事务, 这时总线仲裁 (bus arbitration) 会对竞争作出裁决,这里我们假设总线在仲裁后判定处理器 A 在竞争中获胜(总线仲裁会 确保所有处理器都能公平的访问内存)。此时处理器 A 继续它的总线事务,而其它两个处 理器则要等待处理器 A 的总线事务完成后才能开始再次执行内存访问。假设在处理器 A 执 行总线事务期间(不管这个总线事务是读事务还是写事务),处理器 D 向总线发起了总线 事务,此时处理器 D 的这个请求会被总线禁止。 总线的这些工作机制可以把所有处理器对内存的访问以串行化的方式来执行; 在任意时间点, 最多只能有一个处理器能访问内存。这个特性确保了单个总线事务之中的内存读/写操作具 有原子性。 在一些 32 位的处理器上,如果要求对 64 位数据的读/写操作具有原子性,会有比较大的开 销。 为了照顾这种处理器, java 语言规范鼓励但不强求 JVM 对 64 位的 long 型变量和 double 型变量的读/写具有原子性。当 JVM 在这种处理器上运行时,会把一个 64 位 long/ double 型变量的读/写操作拆分为两个 32 位的读/写操作来执行。这两个 32 位的读/写操作可能会 被分配到不同的总线事务中执行,此时对这个 64 位变量的读/写将不具有原子性。 当单个内存操作不具有原子性,将可能会产生意想不到后果。请看下面示意图:
如上图所示,假设处理器 A 写一个 long 型变量,同时处理器 B 要读这个 long 型变量。处 理器 A 中 64 位的写操作被拆分为两个 32 位的写操作, 且这两个 32 位的写操作被分配到不 同的写事务中执行。同时处理器 B 中 64 位的读操作被拆分为两个 32 位的读操作,且这两 个 32 位的读操作被分配到同一个的读事务中执行。 当处理器 A 和 B 按上图的时序来执行时, 处理器 B 将看到仅仅被处理器 A“写了一半“的无效值。
参考文献
1. JSR-133: Java Memory Model and Thread Specification 2. Shared memory consistency models: A tutorial

3. The JSR-133 Cookbook for Compiler Writers 4. 深入理解计算机系统(原书第 2 版) 5. UNIX Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers 6. The Java Language Specification, Third Edition
作者简介
程晓明,Java 软件工程师,国家认证的系统分析师、信息项目管理师。专注于并发编程, 就职于富士通南大。个人邮箱:asst2003@https://www.doczj.com/doc/358011814.html,。
深入理解 Java 内存模型(四)——volatile
https://www.doczj.com/doc/358011814.html,/cn/articles/java-memory-model-4
volatile 的特性
当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别。理解 volatile 特性的一 个好方法是:把对 volatile 变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写 操作做了同步。下面我们通过具体的示例来说明,请看下面的示例代码:
class VolatileFeaturesExample { volatile long vl = 0L; //使用 volatile 声明 64 位的 long 型变量
public void set(long l) { vl = l; } //单个 volatile 变量的写
public void getAndIncrement () { vl++; } //复合(多个)volatile 变量的读/写
public long get() { return vl; } } //单个 volatile 变量的读

java技术面试必问:JVM 内存模型讲解

java技术面试必问:JVM 内存模型讲解 今天我们就来聊一聊Java内存模型,面试中面试官会通过考察你对jvm的理解更深入得了解你的水平。在了解jvm内存模型前我们先回顾下,java程序的执行过程: java文件在通过java编译器生产.class 字节码文件,然后由jvm中的类加载器加载各个类中的字节码文件,加载完成后由jvm执行引擎执行,在整个加载过程中,jvm用一段空间来存储程序执行期间需要的数据和相关信息,这个空间就叫做jvm内存。 一、JVM 的重要性 首先你应该知道,运行一个 Java 应用程序,我们必须要先安装 JDK 或者 JRE 。这是因为 Java 应用在编译后会变成字节码,然后通过字节码运行在 JVM 中,而 JVM 是JRE 的核心组成部分。 二、优点 JVM 不仅承担了 Java 字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。这个机制可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使 Java 开发人员不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。 三、缺点 这个机制在提升 Java 开发效率的同时,也容易使 Java 开发人员过度依赖于自动化,弱化对内存的管理能力,这样系统就很容易发生 JVM 的堆内存异常、垃圾回收(GC)的不合适以及 GC 次数过于频繁等问题,这些都将直接影响到应用服务的性能。 四、内存模型 JVM 内存模型共分为5个区:堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)。 其中,堆(Heap)、方法区(Method Area)为线程共享,程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)为线程隔离。 五、堆(Heap) 堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。 堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 区和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成。

java数据在内存中存储详解

博客分类: JAVA 1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java 一族偏重于系统架构。说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出,此说法不虚,但掌握Java的底层实现对Java程序员来说是至关重要的,本文介绍了Java中的数据在内存中的存储。 2内存中的堆(stack)与栈(heap) Java程序运行时有6个地方可以存储数据,它们分别是寄存器、栈、堆、静态存储、常量存储和非RAM存储,主要是堆与栈的存储。 【随机存储器:Random Access Memory】 栈与堆都是Java用来在RAM中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。另外,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 【寄存器位于CPU中】 3Java中数据在内存中的存储 3.1基本数据类型的存储 Java的基本数据类型共有8种,即int,short,long,byte,float,double, boolean,char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a=3;long b=255L;的形式来定义的,称为自动变量。值得注意的是:自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a=3;这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

java内存屏障与JVM并发详解

深入Java底层:内存屏障与JVM并发详解(1) 本文介绍了内存屏障对多线程程序的影响,同时将研究内存屏障与JVM并发机制的关系,如易变量(volatile)、同步(synchronized)和原子条件式(atomic conditional)。 AD:内存屏障,又称内存栅栏,是一组处理器指令,用于实现对内存操作的顺序限制。本文假定读者已经充分掌握了相关概念和Java内存模型,不讨论并发互斥、并行机制和原子性。内存屏障用来实现并发编程中称为可见性(visibility)的同样重要的作用。 关于JVM更多内容,请参阅:JVM详解 Java虚拟机原理与优化 内存屏障为何重要? 对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序执行。当数据是不可变的,同时/或者数据限制在线程范围内,这些优化是无害的。 如果把这些优化与对称多处理(symmetric multi-processing)和共享可变状态(shared mutable state)结合,那么就是一场噩梦。当基于共享可变状态的内存操作被重新排序时,程序可能行为不定。一个线程写入的数据可能被其他线程可见,原因是数据写入的顺序不一致。适当的放置内存屏障通过强制处理器顺序执行待定的内存操作来避免这个问题。 内存屏障的协调作用 内存屏障不直接由JVM暴露,相反它们被JVM插入到指令序列中以维持语言层并发原语的语义。我们研究几个简单Java程序的源代码和汇编指令。首先快速看一下Dekker算法中的内存屏障。该算法利用volatile变量协调两个线程之间的共享资源访问。 请不要关注该算法的出色细节。哪些部分是相关的?每个线程通过发信号试图进入代码第一行的关键区域。如果线程在第三行意识到冲突(两个线程都要访问),通过turn变量的操作来解决。在任何时刻只有一个线程可以访问关键区域。 1. // code run by first thread // code run by second thread 2. 3. 1 intentFirst = true; intentSecond = true;

Java内存区域划分、内存分配原理

本文由我司收集整编,推荐下载,如有疑问,请与我司联系 Java 内存区域划分、内存分配原理 2014/11/16 2448 运行时数据区域 Java 虚拟机在执行Java 的过程中会把管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程 的启动而存在,而有的区域则依赖线程的启动和结束而创建和销毁。 Java 虚拟机包括下面几个运行时数据区域: 程序计数器 程序计数器是一块较小的区域,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的模型里,字节码指示器就是通过改变程序计数器的值 来指定下一条需要执行的指令。分支,循环等基础功能就是依赖程序计数器来完成的。 由于java 虚拟机的多线程是通过轮流切换并分配处理器执行时间来完成,一个处理器同一时间只会执行一条线程中的指令。为了线程恢复后能够恢复正确的 执行位置,每条线程都需要一个独立的程序计数器,以确保线程之间互不影响。因 此程序计数器是“线程私有”的内存。 如果虚拟机正在执行的是一个Java 方法,则计数器指定的是字节码指令对应的地址,如果正在执行的是一个本地方法,则计数器指定问空undefined。程序计数器区域是Java 虚拟机中唯一没有定义OutOfMemory 异常的区域。 Java 虚拟机栈 和程序计数器一样也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应 一个栈帧在虚拟机栈中从入栈到出栈的过程。

精选大厂java多线程面试题50题

Java多线程50题 1)什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。 2)线程和进程有什么区别? 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。更多详细信息请点击这里。 3)如何在Java中实现线程? https://www.doczj.com/doc/358011814.html,ng.Thread类的实例就是一个线程但是它需要调用https://www.doczj.com/doc/358011814.html,ng.Runnable接口来执行,由于线程类本身就是调用的 Runnable接口所以你可以继承https://www.doczj.com/doc/358011814.html,ng.Thread类或者直接调用Runnable接口来重写run()方法实现线程。 4)Thread类中的start()和run()方法有什么区别? 这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你

调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。 5)Java中Runnable和Callable有什么不同? Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。 6)Java内存模型是什么? Java内存模型规定和指引Java程序在不同的内存架构、CPU 和操作系统间有确定性地行为。它在多线程的情况下尤其重要。 Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。 ●线程内的代码能够按先后顺序执行,这被称为程序次序 规则。 ●对于同一个锁,一个解锁操作一定要发生在时间上后发 生的另一个锁定操作之前,也叫做管程锁定规则。 ●前一个对Volatile的写操作在后一个volatile的读操作之 前,也叫volatile变量规则。 ●一个线程内的任何操作必需在这个线程的start()调用之 后,也叫作线程启动规则。 ●一个线程的所有操作都会在线程终止之前,线程终止规

字节跳动面试经验分享(非常详细)

字节跳动面试官一招差点KO我,一共面试了3轮(5年经验),艰难拿下2-2职级offer! 前言 我从大学毕业开始做Android开发,现在已经五年时间了,现在在山东老家济南做Android开发。这三年里面,也只是一心在做Android开发,其他语言接触的并不多,了解点JS之类的。现在感觉Android开始不像以前那样好做了,也可能是现在年纪慢慢大了,要考虑的事情变多了的缘故吧。 不知道以后应该何去何从,总是感觉做Android或者说做程序员一直处在一种不稳定之中,在一些中小公司里面,可能工作一年两年就因为各种各样的原因而离职。马上就要结婚了,该买房了。济南的房价一直在涨,而自己的收入还是这么不温不火的,加上这不稳定的工作,让人对于前途实在是乐观不起来。 再加上今年的大环境非常差,互联网企业裁员的现象比往年更严重了,可今年刚好是我的第一个“五年计划”截止的时间点,说什么也不能够耽搁了,所以早早准备的跳槽也在疫情好转之后开始进行了。但是,不得不说,这次字节的面试真的太难为我了,可以说是和面试官大战了3个回合,不过好在最后给了offer。 我个人情况是5年Android开发经验,字节跳动定级2-2(年薪是50-100w左右含加班费和股票折现,不含车餐房补)的样子,我是拿到了年薪60w+,13薪。下面是我的面试经历,与学习经验分享,希望能带来一些不一样的启发和帮助。 我与字节跳动面试官“大战”3回合,胜! 我的学习经验 1—4年大学 ?Java

无论什么级别的Android从业者,Java作为Android开发基础语言。不管是工作还是面试中,Java都是必考题。如果不懂Java的话,薪酬会非常吃亏(美团尤为重视Java基础) 详细介绍了Java泛型、注解、并发编程、数据传输与序列化、高效IO、容器集合、反射与类加载以及JVM重点知识线程、内存模型、JVM运行时内存、垃圾回收与算法、Java中四种引用类型、GC 分代收集算法 VS 分区收集算法、GC 垃圾收集器、JAVA IO/NIO 、JVM 类加载机制的各大知识点。 ?筑基必备 Android架构师筑基包括哪些内容呢: 1.深入 Java 泛型. 2.解深入浅出 3.并发编程 4.数据传输与序列化 5.Java 虚拟机原理 6.反射与类加载 7.高效 IO 8.Kotlin项目实战 大学1-4年架构师筑基必备 ?学习笔记整理 架构师筑基必备目录 架构师筑基必备第一章

java初级面试题

JAVA相关基础知识 1.Finally,final,finalize Finally:释放资源(内存之外的,打开的文件、连接、屏幕上的图形,,) ①总会执行 ②非后台线程结束,后台线程被强关,不会执行finally ③当try和catch中有return时,finally在return之后执行,但是返回值不会改变(finally中不会改变已保存的返回结果) ④finally中最好不要包含return,否则程序会从finally中退出,返回值不是try或catch中保存的返回值。 final: 基本数据类型:不可更改 类:不可继承 对象:引用不可变,对象内容可变 finalze:回收前调用,不适合用来清理或释放资源。对象免死最后机会!保证会被调用,但不保证会执行完(在低优先级线程中执行) 2. 数据在各个网络层之间是怎么传输的? 数据在各层之间的单位都是不一样的, 在物理层数据的单位称为比特(bit);在数据链路层,数据的单位称为(frame);在网络层,数据的单位称为数据包(packet);传输层,数据的单位称为数据段(segment)。 3. Hashtable、HashMap Hashtable 与HashMap类似,但是主要有6点不同。 1.HashTable的方法是同步的,HashMap未经同步,如Vector和ArrayList一样。 2.HashTable不允许null,key和value都不可以,HashMap允许null值,key和value 都可以。HashMap允许key值只能由一个null

3.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。 4.遍历的时候,HashTable使用Enumeration,HashMap使用Iterator。 5.HashTable中hash数组默认大小是11,增加的方式是old*2+1。HashMap中hash 数组的默认大小是16,而且一定是2的指数。 6.哈希值的使用不同,HashTable直接使用对象的hashCode。 Hashtable继承自Dictionary类,实现了Map接口。而HashMap是继承自AbstractMap,实现了Map接口。 4. GET,POST区别? 答:基础知识:Http的请求格式如下。 主要包含三个信息:1、请求的类型(GET或POST),2、要访问的资源(如\res\img\a.jif),3、Http版本(http/1.1)

用来说明服务器要使用的附加信息 这是Http的规定,必须空一行 [] 请求的内容数据 区别: 1、Get是从服务器端获取数据,Post则是向服务器端发送数据。 2、在客户端,Get方式通过URL提交数据,在URL地址栏可以看到请求消息,该消息被编码过;Post数据则是放在Html header内提交。

JAVA经典面试题:Java内存模型

JAVA经典面试题:Java内存模型 因为Java内存模型不仅是java重点要学习的技术知识,还是面试的时候经典面试题,希望引起同学们的重视,今天千锋小编就来分享一下java内存模型的相关技术知识。 不同的渠道,内存模型是不一样的,但是jvm的内存模型标准是一致的。其实java的多线程并发问题都会反映在java的内存模型上,所谓线程安全无非是要操控多个线程对某个资源的有序拜访或修改。总结java的内存模型,要解决两个首要的问题:可见性和有序性。 可见性:多个线程之间是不能相互传递数据通信的,它们之间的交流只能经过同享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程同享的。当new一个目标的时分,也是被分配在主内存中,每个线程都有自己的作业内存,作业内存存储了主存的某些目标的副本,当然线程的作业内存大小是有限制的。当线程操作某个目标时,履行次序如下: (1) 从主存仿制变量到当前作业内存(read and load) (2) 履行代码,改动同享变量值(use and assign) (3) 用作业内存数据改写主存相关内容(store and write)

当一个同享变量在多个线程的作业内存中都有副本时,如果一个线程修改了这个同享变量,那么其他线程应该可以看到这个被修改后的值,这就是多线程的可见性问题。 有序性:线程在引证变量时不能直接从主内存中引证,如果线程作业内存中没有该变量,则会从主内存中复制一个副本到作业内存中,完成后线程会引证该副本。当同一线程再度引证该字段时,有可能从头从主存中获取变量副本(read-load-use),也有可能直接引证本来的副本(use),也就是说read,load,use次序可以由JVM完成体系决议。 线程不能直接为主存中字段赋值,它会将值指定给作业内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store- write),至于何时同步往昔,依据JVM完成体系决议。有该字段,则会从主内存中将该字段赋值到作业内存中,这个进程为read-load,完成后线程会引证该变量副本。 知识就财富,这句话再IT行业显示的尤其现实残酷,懂就是懂,不懂就是不懂,所以各位同学,你的努力与否与你财富直接挂钩。一起加油吧!更多java 技术经典面试题欢迎关注千锋小编。

杭研Java秋招面经——【网易 笔试面试精品资源】

一面 Java内存模型讲一下 GC算法,知道的都讲一下 HashMap,get,put实现 JsonWebToken具体实现流程(简历) Spring AOP如何实现,写一个 AOP功能的主要流程 数据库引擎用过哪些,它们的区别 设计大流量访问系统,要做节流控制(类似秒杀) Linux命令用过哪些 频繁 gc排查处理 内存过大排查的处理,用 jmap,jstack怎么做,不用又怎么做 MySQL主从复制场景问题(bin_log) 项目中技术上最大的成长,项目中的问题解决方案讲一下 二面 手撕算法:比如 123+234=357,对应两个链表 3->2->1,4->3->2(输

入),输出结果链表(7->5->3),写一个函数实现,输入为两个链表,输出为一个结果链表。(考虑极端情况和进位情况,花了很久写出来还是不完善) 输入网址到展现发生了什么,越详细越好(我考虑了 DNS轮询,负载均衡和 CDN以及状态码,然后就全讲) 负载均衡是怎么做的,CDN具体是怎么实现的 TCP三次握手四次挥手 cookie和 session的区别,多台服务器的情况呢 四次挥手时最后两者的状态,Client的 TIME_WAIT避免什么问题,没有它会怎么样 SSM和 Spring Boot的区别 MyBatis和 Hibernate区别

TCP流量控制和拥塞控制,具体在场景中是怎么起作用的 Java线程和 OS中的线程的关系,与内存对应关系,一个 JVM线程数的上限受哪些因素限制 HR 挨个讲讲项目 项目哪个对技术成长大,哪个对个人成长大 面了哪些公司,走到了什么流程 为什么选择网易 之后的学习规划 全程自己讲了很多 总监面 protocolbuf主要优势(性能、安全性、跨语言) Java本身序列化存在的问题(不知道) Java内存模型 网络 IO编程中的内存使用了 JMM哪部分

java内存模型

12.Java内存模型 (原本准备把内存模型单独放到某一篇文章的某个章节里面讲解,后来查阅了国外很多文档才发现其实JVM内存模型的内容还蛮多的,所以直接作为一个章节的基础知识来讲解,可能该章节概念的东西比较多。一个开发Java的开发者,一旦了解了JVM内存模型就能够更加深入地了解该语言的语言特性,可能这个章节更多的是概念,没有太多代码实例,所以希望读者谅解,有什么笔误来Email告知:silentbalanceyh@https://www.doczj.com/doc/358011814.html,,本文尽量涵盖所有Java语言可以碰到的和内存相关的内容,同样也会提到一些和内存相关的计算机语言的一些知识,为草案。因为平时开发的时候没有特殊情况不会进行内存管理,所以有可能有笔误的地方比较多,我用的是Windows平台,所以本文涉及到的与操作系统相关的只是仅仅局限于Windows平台。不仅仅如此,这一个章节牵涉到的多线程和另外一些内容并没有讲到,这里主要是结合JVM内部特性把本章节作为核心的概念性章节来讲解,这样方便初学者深入以及彻底理解Java 语言) 本文章节: 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏 1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面,有时候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型。究竟什么是内存模型?内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的,这点没有错,但是编译器、运行库、处理器或者系统缓存可以有特权在变量指定内存位置存储或者取出变量的值。【JMM】(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了final或synchronized明确请求了某些可见性的保证。 1)JSR133:

jvm内存结构

结构概览 JVM内存区域也称为Java运行时数据区域。其中包括:程序计数器、栈、堆、方法区等。 内存结构主要分为三大部分:堆内存,方法区和栈。 堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间。 方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。 JVM和系统调用之间的关系:

Java堆(Heap) Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。 根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。 简要归纳:新的对象分配是首先放在年轻代 (Young Generation) 的Eden区,Survivor区作为 Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到老年代Old中。 方法区(Method Area) 是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(存放的是Class)。它有一个别名,叫非堆。

Java新手必学的21个技术点

Java新手必学的21个技术点 以下为大家盘点作为Java新手必学的21个技术点,希望能够对想要学习编程,学习JAVA的人有些帮助! JNI Java Native Interface,可以允许Java中调用本地接口方法,一般用于C/C++代码的调用。需要注意的是在java中加载so/dll文件的路径问题,本身调用接口并不复杂,但是经常在是否加载了所需的本地接口库中花费较多时间 RMI RemoteMethodInvocation ,java语言特有的远程调用接口,使用还是比较简单方便。不过需要跨语言的情况下,就需要使用 webservice 等其他方式来支持。一般来说,程序都不需要使用RMI,不过可以在特定的情况下使用,我就在一个项目中,使用RMI来进行程序远程启动停止的控制。 标注 也是jdk5 之后引入的。Spring是个优秀的框架,最开始就以xml作为标准的配置文件。不过到了Spring3 之后,尤其是 spring-boot 兴起之后,越来越推崇使用标注来简化xml配置文件了,对于开发者来说,可以节省不少xml配置的时间。但是劣势是在于标注散落在各个类中,不像xml,可以对所有配置有个全局性的理解和管理,所以还没有办法说完全就取代所有的xml。对于一般开发者,会使用标注即可,一些公共组建的开发者可能会需要了解标注的定义和实现,可以在具体需要的时候再细看。 泛型

这是JDK5开始引入的新概念,其实是个语法糖,在编写java代码时会有些许便利,一般的应用或者是业务的开发,只需要简单使用,不一定会用到定义泛型这样的操作,但是开发一些基础公共组件会使用到,可以在需要的时候再细看这个部分,一般情况下只要会简单使用即可。 Maven的使用 Maven 也不是Java里面的内容,但是maven是革命性的,给java开发带来了巨大的便利。从依赖的引入和管理,开发流程的更新和发布产出,乃至版本的更新,使用maven可以大大简化开发过程中的复杂度,从而节省大量时间。可以说,maven已经成为java开发者的标配了。所以我把maven也作为一个java开发者对于基础必备的知识点。以后会再放上一些我的一些对于maven使用的经验和技巧等,这里就不再细说了 XML解析/ JSON解析 其实这两块内容都不是J2SE里面的内容,但是在日常开发中,和其他程序交互,和配置文件交互,越来越离不开这两种格式的解析。 不过对于一个开发者来说,能够了解一些XML/JSON具体解析的原理和方法,有助于你在各个具体的场景中更好的选择合适你的方式来使得你的程序更有效率和更加健壮。 XML:需要了解 DOM解析和 SAX解析的基本原理和各自的适用场景JSON:需要了解一些常用JSON框架的用法, 如 Jackson, FastJson, Gson 等。 时间日期处理

java面试2019

J2SE基础: 1. 九种基本数据类型的大小,以及他们的封装类。 2. Switch能否用string做参数? 3. equals与==的区别。 4. Object有哪些公用方法? 5. Java的四种引用,强弱软虚,用到的场景。 6. Hashcode的作用。 7. ArrayList、LinkedList、Vector的区别。 8. String、StringBuffer与StringBuilder的区别。 9. Map、Set、List、Queue、Stack的特点与用法。 10. HashMap和HashTable的区别。 11. HashMap和ConcurrentHashMap的区别,HashMap的底层源码。 12. TreeMap、HashMap、LindedHashMap的区别。 13. Collection包结构,与Collections的区别。 14. try catch finally,try里有return,finally还执行么? 15. Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况。 16. Java面向对象的三个特征与含义。 17. Override和Overload的含义去区别。 18. Interface与abstract类的区别。 19. Static class 与non static class的区别。 20. java多态的实现原理。 21. 实现多线程的两种方法:Thread与Runable。 22. 线程同步的方法:sychronized、lock、reentrantLock等。 23. 锁的等级:方法锁、对象锁、类锁。

适用于云计算的数据库开发和使用案例

适用于云计算的数据库 开发及案例
Copyright ? Versant Corp. All rights reserved.
0001
By Tiger Lau,CTO of Versant China

数据库发展简史
模型
层次化,结构化 网络化 关系型 对象型
优点
性能 性能 灵活性, 支持查询 性能,灵活性
缺点
灵活性, 对查询的支持 灵活性, 对查询的支持 性能 随机性较强, 支持既成的查询
数据
简单 简单 简单 复杂
这两点是现有很多系统的核心问题所在

什么是Versant云数据库
Versant数据库是大规模的分布式数据库 Versant数据库是专门为复杂数据提供服务的数据库:
设计目标是优化对象的存储与操作。 有能力管理任何类型的复杂模型。
简单类型: 整型, 字符串 数据类型与程序语言定义完全一致, 非自建数据结构 多值类型(Multi-valued): 动态类型数组 可以有效支持复杂程序数据结构,无需拆分
有能力管理对象间的任何关系。
对象间引用 (链接) 集合 (唯一性), 列表 (排序), 图 (关联性查找) 一次性存储,透明装载,能极大提高系统效率
Versant数据库能够实现数据与程序语言的无缝集成。

什么是复杂数据?
应用自身的数据结构以及数据非常复杂。
面向图形/GIS的复杂应用系统 基于复杂对象导航访问模式的应用系统
受到面向对象模型的影响而形成的复杂数据。
继承 集合 关联
由于无法简单的映射到关系模型而 形成的复杂数据。
大量的映射代码 大量的联合(JOIN)操作 性能不佳

JAVA 内存管理总结

JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题。(两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。 2. 什么叫java的内存泄露 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。 3. JVM的内存区域组成 java把内存分两种:一种是栈内存,另一种是堆内存1。在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;2。堆内存用来存放由new创建的对象和数组以及对象的实例变量在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理 堆和栈的优缺点 堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。 缺点就是要在运行时动态分配内存,存取速度较慢;栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。 另外,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 4. Java中数据在内存中是如何存储的 a) 基本数据类型

Java并发编程:JMM(Java内存模型)和volatile

Java并发编程:JMM(Java内存模型)和volatile 1. 并发编程的3个概念 并发编程时,要想并发程序正确地执行,必须要保证原子性、可见性和有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。 1.1. 原子性 原子性:即一个或多个操作要么全部执行并且执行过程中不会被打断,要么都不执行。 一个经典的例子就是银行转账:从账户A向账户B转账1000元,此时包含两个操作:账户A减去1000元,账户B加上1000元。这两个操作必须具备原子性才能保证转账安全。假如账户A减去1000元之后,操作被打断了,账户B却没有收到转过来的1000元,此时就出问题了。 1.2. 可见性 可见性:即多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的最新值。 例如下段代码,线程1修改i的值,线程2却没有立即看到线程1修改的i的最新值: 假如执行线程1的是CPU1,执行线程2的是CPU2。当线程1执行i=10 时,会将CPU1的高速缓存中i的值赋值为10,却没有立即写入主内存中。此时线程2执行j=i,会

先从主内存中读取i的值并加载到CPU2的高速缓存中,此时主内存中的i=0,那么就会使得j最终赋值为0,而不是10。 1.3. 有序性 有序性:即程序执行的顺序按代码的先后顺序执行。 例如下面这段代码: 在代码顺序上i=1 在flag=true 前面,而JVM 在真正执行代码的时候不一定能保证i=1 在flag=true 前面执行,这里就发生了指令重排序。 指令重排序 一般是为了提升程序运行效率,编译器或处理器通常会做指令重排序: ?编译器重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序 ?处理器重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。CPU 在指令重排序时会考虑指令之间的数据依赖性,如果指令2必须依赖用到指令1的结果,那么CPU会保证指令1在指令2之前执行。 指令重排序不保证程序中各个语句的执行顺序和代码中的一致,但会保证程序最终执行结果和代码顺序执行的结果是一致的。比如上例中的代码,i=1 和flag=true 两个语句先后执行对最终的程序结果没有影响,就有可能CPU 先执行flag=true,后执行i=1。2. java 内存模型 由于volatile 关键字是与java 内存模型相关的,因此了解volatile 前,需要先了解下java 内存模型相关概念

JAVA虚拟机规范

运行时数据区域 Java虚拟机所管理的内存将会包括以下几个运行时数据区域: 程序计数器 Program Counter Register是一块较小的内存空间,作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机规范里,字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等基础功能都依赖程序计数器完成。 由于Java虚拟机的多线程时通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因为,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间的计数器不影响,独立存储,因此这类区域为“线程私有”的内存。 如果线程正在执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址:如果执行的是Native方法,计数器值为空Undefined。此区域是虚拟机规范中没有规定任何OutOfMemoryError情况的区域。 Java虚拟机栈 Java虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执

行的内存模型:每个方法被执行时会创建一个栈帧Stack Frame用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用到执行完成,对应一个栈帧在栈中从入栈到出栈的过程。 局部变量表存放了编译期可知的8种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据占用2个局部变量空间(Slot),其余数据类型占用1个。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。 在虚拟机规范中,这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常;如果栈可以动态扩展,当扩展无法申请到足够的内存时抛出OutOfMemoryError异常。 本地方法栈 Native Method Stack与虚拟机栈作用类似,不过是虚拟机栈为虚拟机执行的Java方法(即字节码)服务,而本地方法栈为虚拟机使用的Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。 Java堆 Java虚拟机规范描述是:所有对象实例以及数组都在堆上分配。堆是所有线程共享的,但从内存分配角度看,线程共享的堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例。 方法区 Method Area是线程共享的,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 Java虚拟机规范对方法区限制比较宽松,除了和堆一样不需要连续的内存和可用选择固定大小或者可扩展外,还可以选择不实现垃圾收集。一般来说,垃圾收集行为在这个区域比较少见,但并非数据进入了方法区就永久存在了。这个区域的内存回收目标主要针对常量池的回收和类型的卸载。该区域无法分配内存时,抛出OutOfMemoryError异常。 运行时常量池Runtime Constant Pool是方法区一部分,Class文件除了有类的版本、字段、方法和接口等描述信息外,还有常量池Constant Pool Table,用于存放编译期生成的字面量和符号引用。 Java语言不要求常量一定只能在编译期生成,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,比如String类的intern()方法。 对象访问 Object obj=new Object()

深入理解 Java String#intern() 内存模型

深入理解 Java String#intern() 内存模型

深入理解 Java String#intern() 内存模型 字符串常量池是一个固定大小的HashMap,桶的数量默认是1009, 从Java7u40开始,该默认值增大到60013。在Java6当中,字符串常量池是放在Perm空间的,从Java7开始,字符串常量池被移到Heap空间。下面,我们通过测试程序来窥探字符串常量池在Java6,Java7两个不同版本底下的内存分配情况。 测试程序 public class StringPoolTest { public void testStringPoolWithLongString(){ long i=0; while(true){ String longString = "This is a very long stri ng, very very long string to test the gc behavior of the string constant pool"+i; longString.intern(); i++; } } public static void main(String[] args){ StringPoolTest stringPoolTest = new StringPoolTes t(); stringPoolTest.testStringPoolWithLongString(); } } 测试程序很简单,一个死循环,循环里面通过递增变量i制造唯一的字符串,然后用main函数启动程序。 Java 6 我们使用版本Jdk1.6.0_29来跑该程序,打开Java VisualVM监控,可以看到,Perm区不断发生GC,由此的出结论,虽然字符串常量池放在Perm空间,但当Perm空间接近满的时候,JVM会将字符串常量池中的无用字符串回收掉。

15-JVM面试题(87题)

JVM面试题 1、java中会存在内存泄漏吗,请简单描述。 会。自己实现堆载的数据结构时有可能会出现内存泄露,可参看e?ective java. 2、64 位JVM 中,int 的长度是多数? Java 中,int 类型变量的长度是一个固定值,与平台无关,都是32 位。意思就是说,在32 位和64 位的Java 虚拟机中,int 类型的长度是相同的。 3、Serial 与Parallel GC 之间的不同之处? Serial 与Parallel 在GC 执行的时候都会引起stop-the-world。它们之间主要不同serial 收集器是默认的复制收集器,执行GC 的时候只有一个线程,而parallel 收集器使用多个GC 线程来执行。4、32 位和64 位的JVM,int 类型变量的长度是多数? 32 位和64 位的JVM 中,int 类型变量的长度是相同的,都是32 位或者4个字节。 5、Java 中WeakReference 与SoftReference 的区别? 虽然WeakReference 与SoftReference 都有利于提高GC 和内存的效率,但是WeakReference ,一旦失去最后一个强引用,就会被GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到JVM 内存不足的时候。

6、JVM 选项-XX:+UseCompressedOops 有什么作用?为什么要使用 当你将你的应用从32 位的JVM 迁移到64 位的JVM 时,由于对象的指针从32 位增加到了64 位,因此堆内存会突然增加,差不多要翻倍。这也会对CPU缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到64 位的JVM主要动机在于可以指定最大堆大小,通过压缩OOP 可以节省一定的内存。通过-XX:+UseCompressedOops 选项,JVM 会使用32 位的OOP,而不是64 位的OOP。 7、怎样通过Java 程序来判断JVM 是32 位还是64位? 你可以检查某些系统属性如sun.arch.data.model 或os.arch 来获取该信息。 8、32 位JVM 和64 位JVM 的最大堆内存分别是多数? 理论上说上32 位的JVM 堆内存可以到达2^32,即4GB,但实际上会比这个小很多。不同操作系统之间不同,如Windows 系统大约1.5 GB,Solaris 大约3GB。64 位JVM 允许指定最大的堆内存,理论上可以达到2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到100GB。甚至有的JVM,如Azul,堆内存到1000G 都是可能的。 9、JRE、JDK、JVM 及JIT 之间有什么不同? JRE 代表Java 运行时(Java run-time),是运行Java 引用所必须的。JDK 代表Java 开发工具(Java development kit),是Java 程序的开发工具,如Java编译器,它也包含JRE。JVM 代表Java 虚拟机(Java virtual machine),它的责任是运行Java 应用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高Java 应用的性能。 10、解释Java 堆空间及GC? 当通过Java 命令启动Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是JVM 内部的一个进程,回收无效对象的内存用于将来的分配。 11、JVM 内存区域

相关主题
文本预览
相关文档 最新文档