深入理解Java内存模型,java内存模型和内存结构

  深入理解Java内存模型,java内存模型和内存结构

  如何解决写爬虫IP受阻的问题?立即使用。

  1. 概述

  多任务和高并发是衡量计算机处理器能力的重要指标之一。一般来说,衡量一个服务器的性能,每秒事务数(TPS)这个指标就能说明问题。它代表了服务器在一秒钟内能够响应的平均请求数,TPS值与程序的并发性密切相关。在讨论Java内存模型和线程之前,先简单介绍一下硬件的效率和一致性。(推荐:java视频教程)

  2.硬件的效率与一致性

  由于计算机的存储设备和处理器的计算能力之间存在几个数量级的差距,现代计算机系统不得不在内存和处理器之间增加一层读写速度尽可能接近处理器计算速度的缓存作为缓冲:将操作所需的数据复制到缓存中,这样操作就可以快速进行,操作完成后再从缓存中同步回内存。这样,处理器就不必等待缓慢的内存读写。

  基于缓存的存储交互很好地解决了处理器和内存的速度矛盾,但是引入了一个新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,它们共享同一个主存。

  如下图所示,多个处理器的计算任务都涉及同一个主存,所以需要一个协议来保证数据的一致性,比如MSI、MESI、MOSI、Dragon协议。虚拟机的Java内存模型中定义的内存访问操作与硬件的缓存访问操作相当,后面会介绍Java内存模型。

  此外,为了充分利用处理器内部的运算单元,处理器可能会对输入代码的乱序执行进行优化,处理器会对乱序执行代码计算后的结果进行重组,以保证结果的准确性。类似于处理器的乱序执行优化,Java虚拟机的即时编译器中也有类似的指令重排序优化。

  3.Java内存模型

  定义Java内存模型并不是一件容易的事情。这个模型必须定义得足够严谨,这样Java的并发操作才不会模棱两可。但是,它必须足够宽松,以便虚拟机可以有足够的空闲空间来利用硬件的各种功能(寄存器、缓存等)。)来实现更好的执行速度。经过长时间的验证和修复,JDK1.5发布后,Java内存模型已经成熟和完善。

  3.1 主内存与工作内存

  Java内存模型的主要目标是定义程序中变量的访问规则,也就是虚拟机中将变量存入内存、从内存中取出变量等底层细节。这里的变量不同于Java编程中提到的变量。它们包括实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,它们是线程私有的,不会被共享。

  根据Java内存模型,所有变量都存储在主存中,每个线程都有自己的工作内存(可以和前面提到的处理器的缓存相比)。线程的工作内存将该线程使用的变量的副本保存到主存中,线程对变量的所有操作(读取和赋值)都必须在工作内存中进行,而不是直接在主存中读写变量。

  不同线程不能直接访问彼此工作内存中的变量,线程间变量值的传递需要在主存中完成。线程、主存、工作内存之间的交互如下图所示,与上图非常相似。

  Java内存区中的主内存、工作内存和Java堆、栈、方法区不在同一个级别的内存划分中。

  3.2 内存间交互操作

  关于主存和工作内存之间的具体交互协议,即变量如何从主存复制到工作内存,以及如何从工作内存同步到主存的实现细节,Java内存模型定义了以下八个操作来完成:

  1.锁:作用于主存的变量,将变量标识为线程的独占状态。

  2.unlock:作用于主存变量释放一个被锁定的变量,然后被释放的变量可以被其他线程锁定。

  3.read:作用于主存变量,将一个变量值从主存转移到线程的工作内存,用于后续的加载操作。

  4.load:作用于工作内存的变量,它把读操作从主存中得到的变量值放入工作内存的变量副本中。

  5.use:作用于工作内存的变量,将工作内存中的变量值传递给执行引擎。每当虚拟机遇到需要使用变量值的字节码指令时,就会执行这个操作。

  6.赋值:作用于工作记忆的变量。它将从执行引擎接收的值赋给工作存储器中的变量,并且每当虚拟机遇到将值赋给变量的字节码指令时就执行该操作。

  7.存储:一个作用于工作存储器的变量,将工作存储器中的一个变量的值转移到主存储器中,供后续的写操作使用。

  8.写:作用于主存的一个变量,它将存储操作从工作存储器中的一个变量值转移到主存中的一个变量。

  如果你想把一个变量从主存复制到工作内存,你需要按顺序执行读取和加载操作。如果您将变量从工作内存同步回主内存,您需要依次执行存储和写入操作。

  Java内存模型只要求上述操作必须按顺序执行,但不保证必须连续执行。也就是说,可以在读取和加载之间以及存储和写入之间插入其他指令。比如在主存中访问变量A和B时,可能的顺序是read a,read b,load b,load A,Java内存模型还规定在执行上述八个基本操作时必须满足以下规则:

  1.读取和加载、存储和写入操作之一不允许单独出现。

  2.一个线程不允许放弃它最近的赋值操作,也就是说,变量在工作内存中改变后必须同步到主存中。

  3.不允许线程无缘无故地将数据从工作内存同步回主内存(没有任何赋值操作)。

  4.一个新的变量只能诞生在主存中。不允许在工作内存中直接使用未初始化的(load或assign)变量。也就是说,在使用和存储变量之前,必须先执行赋值和加载操作。

  5.同一时间只允许一个线程锁定一个变量。锁定和解锁必须成对出现。

  6.如果对变量执行锁定操作,工作存储器中该变量的值将被清除。在执行引擎使用这个变量之前,需要重新执行load或assign操作来初始化变量的值。

  7.如果一个变量没有被锁操作提前锁定,则不允许对其执行解锁操作;也不允许解锁被其他线程锁定的变量。

  8.在解锁一个变量之前,它必须与主存同步(存储和写操作)。

   3.3 重排序

  为了提高执行程序时的性能,编译器和处理器经常对指令进行重新排序。重新排序分为三种类型:

  1.编译器优化的重新排序。编译器可以重新安排语句的执行顺序,而不改变单线程程序的语义。

  2.指令级并行的重新排序。现代处理器采用指令级并行技术,以重叠的方式执行多条指令。如果没有数据依赖性,处理器可以改变对应于语句的机器指令的执行顺序。

  3.重新排列记忆系统。由于处理器使用了缓存和读写缓冲区,这使得加载和存储操作看起来是无序的。

  从Java源代码到实际执行的指令序列,将以以下三种方式重新排序:

  为了保证内存的可见性,Java编译器会在生成的指令序列的适当位置插入内存屏障指令,以禁止某些类型的处理器重排序。Java内存模型将内存屏障分为四种类型:LoadLoad、LoadStore、StoreLoad和StoreStore:

  更多java知识,请关注java基础课程专栏。以上是Java内存模型详细讲解的详细内容。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: