Java虚拟机--内存模型与线程

高速缓存:处理器要与内存交互,如读取、存储运算结果,而计算机的存储设备和处理器的运算速度差异巨大,所以加入一层读写速度和处理器接近的高速缓存来作为内存和处理器之间的缓冲——将运算所需数据复制到缓存中,使得运算能快速进行;当运算结束后再将缓存同步回内存中,这样处理器无需等待缓慢的内存读写。

每个处理器都有自己的高速缓存,它们都共享同一个主内存,当多个处理器的运算任务都涉及同一块主内存区域时,将导致各自的缓存数据不一致,此时要同步数据到主内存以哪个处理器的缓存为主?这就是缓存一致性。为了解决这个问题,需要遵循一些缓存一致性协议。

Java内存模型

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储带内存以及从内存中取出变量这样的底层细节。这里的变量指的是非线程私有的实例字段、静态字段和构成数组对象的元素,不包括局部变量和方法参数,因为它们是线程私有、不被共享的。

Java内存模型中,所有的变量都存储在主内存中,每条线程还有自己的工作内存(与高速缓存类比),线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量;不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需通过主内存完成

如果将Java内存模型和Java堆、栈比较,主内存对应Java堆中的对象实例部分,工作内存对应虚拟机栈中的部分

内存交互

主内存和工作内存之间需要交互,Java内存模型中有8种原子操作:

  • lock:作用于主内存变量,将其标识为线程独占。
  • unlock:作用于主内存变量,将其从锁定状态释放,释放后才可被其他线程锁定。
  • read:作用于主内存的变量,将一个变量从主内存中传输到工作内存中,以便随后的load动作使用。
  • load:作用于工作内存中的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use:作用于工作内存的变量,把工作内存中的一个变量的值传递给执行引擎,当虚拟机需要使用到变量的值的字节码指令时会执行这个操作。
  • assign:作用于工作内存的变量,把一个从执行引擎接受到的值赋给工作内存中的变量。当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store:作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中,以便之后write操作使用。
  • write:作用于主内存中的变量,把store操作从工作内存中得到的变量值放入主内存的变量中。

如果一个变量从主内存复制到工作内存,必须先执行read然后执行load操作(read和load之间允许插入其他操作,只要保证这个顺序即可);如果要把变量从工作内存同步回主内存中,需要先执行store操作然后执行write操作(store和write之间允许插入其他操作,只要保证这个顺序即可)。

volatile关键字

推荐阅读这篇blog

一个变量被定义为volatile后,具备两个特性:

可见性。当一条线程修改了这个变量的值,新值能被其他线程立刻观察到。这是说:volatile的作用是在本CPU对变量的修改直接写入主内存中,同时这个写操作使得其他CPU中对应比变量的缓存行无效,这样其他线程在读取这个变量时候必须从主内存中读取,因为读取到的是最新的,这就是上面说得能被立即“看到”。

而且volatile在其汇编代码中有一个lock操作,这个操作相当于一个内存屏障,保证了有序性,禁止了指令重排:具体来说在执行到volatile变量时,其之前的语句一定被执行过了且结果对后面是已知的,而其后面的语句一定还没执行到;在volatile变量之前的语句不能被重排后其之后,相反其后的语句也不能被重排到之前。

而对于普通变量,普通变量在线程间的传递必须通过主内存这个“桥梁”,即须线程A修改了变量值后,向主内存回写,线程B在等回写完成后再从主内从中读取,新变量这时才对线程B可见。

volatile指令只能保证可见性、有序性,不能保证原子性。比如多个线程执行i++时。i++等价于i = i + 1这语句分为三步,首先读取i的值,作加1操作,将新值赋给i。有种情况是,即使i被volatile修饰,保证读取到的值是最新的正确的,假如i初始值为0,现在A、B线程都正确读取到后i的值为0,B现在加1直接写入主内存中,回到A,因为A读取过了,且读到的是0,这是A线程加1操作写入主内存中,i还是1,进行了两次自增,最后i的值却值增加了1。所以在多线程中即使使用了volatile也不能保证线程安全。

原子性、可见性、有序性

  • 原子性。Java内存模型能直接保证原子性变量操作有:read、load、assign、use、store、write,Java的基本数据类型的访问读写大致是具备原子性的。而lock和unlock这样的原子操作是由Java中的同步块——synchronized块,被其包围的块也具有原子性。
  • 可见性。Java内存模型通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量,这些操作都以来主内存这个“桥梁”。普通变量和volatile变量都是如此,不过volatile可保证新值能立即同步到主内存;使用前立即从主内存刷新,可以说volatile保证了多线程操作时变量的可见性。synchronized和final关键字也可实现可见性,前者:对一个变量执行unlock之前,必须先把次变量同步回主内存中(store、write);后者:final修饰的字段在构造器中一旦初始化完成且this引用没有被传递出去(this引用逃逸),其他线程中能看见final字段的值。
  • 有序性。在本线程内观察,所有操作都是有序的,这表现为“线程内表现为串行的语义”;在一个线程中观察另一个线程,所有操作都是无序的,表现为“指令重排”和“工作内存与主内存的延迟”。volatile本身禁止了指令重排,而synchronized保证了一个变量在同一个时刻只允许一条线程对其进行lock操作

先行发生原则

如果操作A先于操作B发生,其实就是说发生操作B之前,操作A产生的影响(修改了共享变量的值、发送了消息、调用了方法等)能被操作B观察到。Java内存模型自身具备以下先行发生关系,这些原则是指令重排不可违背的。

  • 程序次序原则:一个线程内保证语义的串行性
  • 锁规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile规则:对一个volatile变量的写操作先行发生于对这个变量的操作
  • 线程启动规则:线程的start()方法先于次线程的任何动作
  • 线程终止规则:线程的所有操作都先行发生于线程终结
  • 线程中断规则:对线程的interrupt()方法的调用先于被中断线程的代码
  • 对象终结规则:对象的初始化完成先于它的finalize()方法
  • 传递性:如果A先于B,B先于C,那么A先于C

线程

线程的实现主要有三种方式。

使用内核线程实现

内核线程就是直接由操作系统内核支持的线程,由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。

程序一般不直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程——也就是通常意义所讲的线程,每个轻量级进程都有一个内核线程支持,是一一对应的关系

每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作。但是轻量级进程基于内核线程,需要进行系统调用,而系统调用的代价高,需要在用户态和内核态中来回切换,所以轻量级进程要消耗内核资源。

使用用户线程实现

狭义上的用户线程是无需内核帮助的,完全建立在用户空间的线程库上,进程与用户线程的关系是一对多的线程模型。

用户线程无需内核支援,但这也是个缺点,所有的操作都需要用户程序自己处理,所以使用用户线程实现的程序一般比较复杂。

混合实现

将轻量级进程和用户线程混合。这种模式下,既存在用户线程又存在轻量级进程,轻量级进程成为了用户线程和内核线程的桥梁。用户线程和轻量级进程的数量比不定,可以称为是多对多的模型。

Java线程调度

大体上有两种调度方式。

  • 协同式线程调度:线程的执行时间由线程自己决定,当执行完自己的工作后,主动通知系统切换到另一个线程上。
  • 抢占式线程调度:每个线程的执行时间是由系统分配的,线程的切换不由线程本身决定,不会出现一个线程导致整个进程阻塞的问题。

Java使用的线程调度方式就是抢占式线程调度

Java的线程调度是系统自动完成的,但是可以通过设置线程的优先级,优先级越高的线程越容易被系统选择执行。

Java线程状态

共有6种线程状态。

  • 新建(New):创建后尚未启动的线程;
  • 运行(Runnable):包括了操作系统线程状态中的Running和Ready,此状态的线程有可能正在执行也可能正等待着CPU为它分配执行时间
  • 无限期等待(Waiting):此状态下不会被分配CPU执行时间,需要等待被其他线程唤醒
  • 限期等待():此状态下也不会被分配CPU时间,但无需被其他线程显式唤醒,在一定时间后会有系统自动唤醒
  • 阻塞(Blocked):线程被阻塞,阻塞状态中的线程在等待着获取到一个排他锁,这个事件将在其他线程放弃这个锁时发生;和“等待状态”不同,等待是在等待一段时间或者其他的线程的显式唤醒,在程序进入同步区域时,线程会进入阻塞状态。
  • 结束(Terminated):已终止线程的状,表示线程已经结束执行。


by @sunhaiyu

2018.6.20

Java虚拟机--内存模型与线程的更多相关文章

  1. Java虚拟机内存模型及垃圾回收监控调优

    Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存 ...

  2. Java虚拟机 - 内存模型

    本文主要介绍Java虚拟机的内存分布以及对象的创建过程. 一.Java虚拟机的内存分布 文章开始前读者需要了解Java虚拟机的运行时数据区是怎样划分的.如下图所示: 1.程序计数器(Program C ...

  3. JAVA虚拟机内存模型

    一.对于Java程序员来说,在虚拟机的自动内存管理机制下,我们不需要为每一个new操作去写匹配的delete/free操作 但是当我们对于内存的管理了解有能够帮助我们理解Java虚拟机的垃圾回收机制. ...

  4. (三)java虚拟机内存管理和线程独占区和线程共享区

    一.内存管理 二.线程独占区之程序计数器(Program Counter Register) 程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里,字节 ...

  5. 深入理解Java虚拟机内存模型

    前言 本文中部分内容引用至<深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)>第12章,如果有兴趣可自行深入阅读,文末放有书籍PDF版本连接. 一.物理机中的并发 物理机遇到的并 ...

  6. 面试常问的Java虚拟机内存模型,看这篇就够了!

    一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操作系统底层的硬件指令集是不同的. 同一个java代码在windows上生成的机器码可能是0101.......,在lin ...

  7. java 虚拟机内存模型

    [声明] 欢迎转载,但请保留文章原始出处→_→ 文章来源:[http://www.cnblogs.com/smyhvae/p/4748392.html] 文章来源:[http://www.cnblog ...

  8. Java虚拟机03(Java虚拟机内存模型)

    根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 其实最需要Java程序员关注的是堆,栈,还有方法区,因为啊: 如果代码又问题的话,可能回出现栈溢出 然后说 ...

  9. 作业1:java虚拟机内存模型图示

    看了很多篇文章,整理成一幅图,但仍然有许多不解的地方,以后再接着完善,哪位大神看到不正确的地方,请指出,谢谢.

随机推荐

  1. EF Core创建实体的Code First标准方法

    针对关系型数据库,实体之间的关系最常见的就是通过外键关联的一对一.一对多和多对多的关系,新的EF Core通过注释和Fluent API 能够做到接近于数据库通过DML创建模型的效果了.实际上,通过D ...

  2. Poetry

    1. Absence to love is what wind is to fire. It extinguishes the small; It inflames the great. 2. It ...

  3. Python大黑阔—url采集+exp验证,带你批量测试

    i春秋作家:大木瓜 前言: 最近几天在整理从各处收集来的各种工具包,大大小小的塞满了十几个G的硬盘,无意间发现了一个好几年前的0day.心血来潮就拿去试了一下,没想到真的还可以用,不过那些站点都已经老 ...

  4. 传输层两大协议:TCP和UDP

    1. UDP 1.1 发送方式(如何发送) 面向无连接. 无状态服务. 不保证不丢失,不保证按顺序到达. 1.2 发送形式(发送的是什么) 基于数据报. 一个一个的发送,一个一个的接收. 1.3 使用 ...

  5. 阿里云RDS数据库备份文件恢复到本地数据库

    参考这里:https://help.aliyun.com/knowledge_detail/41817.html 第4.2步要多注释掉一些(应该根据实际报错来注释): [mysqld] innodb_ ...

  6. SDK 上报信息 史上最全 持续更新

    SDK 上报信息 史上最全 持续更新 接入SDK总会遇到各种需求,有些SDK巴不得把玩家信息全部上报到他们服务器! 以下是我接SDK遇到的, 欢迎大家补全. 上报事件 注册(按道理这个应该是SDK的功 ...

  7. C++类和对象(一)&&实现offsetof宏&&this指针

    一.目录 1.对象的相关知识 2.类的定义 3.类的实例化 4.类对象模型 5.模拟实现offsetof宏 6.this指针 二.正文 1.对象的相关知识 C语言是面向过程的,关注的是过程,分析求解问 ...

  8. POJ 2590

    #include<iostream> #include<algorithm> #define MAXN 1000000 using namespace std; unsigne ...

  9. (转)python通过paramiko实现,ssh功能

    python通过paramiko实现,ssh功能 1 import paramiko 2 3 ssh =paramiko.SSHClient()#创建一个SSH连接对象 4 ssh.set_missi ...

  10. JMP地址公式推导

    以上有个问题:为什么同样的汇编指令JMP 12345678却对应不同的机器码呢? 首先,机器码E9表明这是一个近跳转(Near Jmp) 这里需要补充下相关知识: JMP分3种: ①短跳转(Short ...