Java内存模型的深入讲解

目录
  • 内存模型
  • 硬件架构
  • Java内存模型与硬件关联
  • 对象的可见性
  • 竞争条件
  • 总结

Java内存模型展示了Java虚拟机是如何与计算机内存交互的,解决多线程读写共享内存时资源访问的问题。

内存模型

Java虚拟机中的内存模型将线程栈与堆划分开,下图描述了Java内存模型的逻辑图。

每个线程都要自己的线程栈,栈中存储着线程执行到当前位置所调用的方法信息,线程执行代码时,线程栈会不断执行入栈和出栈操作。

线程栈中会存储所有被调用的方法中定义的变量,并且自己访问自己栈中的变量,别的线程不可见。即使两个线程执行相同的代码,也会在线程自己的栈中重复创建变量。一个线程可能会传递变量副本给另一个线程,但不能共享变量本身。

在栈中变量存储形式也有所不同。属于基本变量类型(int,byte,long,boolean,char,double,float,short)的变量,会直接将变量值存储在栈中,而其余类型的变量的值被存储在堆中,线程栈中只保留指向堆中变量地址的指针。
堆中则存储Java程序中创建的所有对象,不管是什么线程创建的。创建对象并将其分配给局部变量,或者将其创建为另一个对象的成员变量都没有影响,该对象仍存储在堆中。

值得注意的是,Java中的静态类变量也会随着类初始化而存储在堆中。

有指向对象指针的所有线程都可以访问堆上的对象。当线程可以访问对象时,它也可以访问该对象的成员变量。如果两个线程同时在同一个对象上调用一个方法,则它们都将有权访问该对象的成员变量,但是每个线程将拥有自己的局部变量副本。

两个线程有一组局部变量,指向堆上的共享对象。这两个线程分别具有对同一对象的不同指针。它们的指针也是局部变量,因此存储在每个线程的线程栈中(在每个线程上)。但是,两个不同的指针指向堆上的同一对象。
下面的代码块就是上图的一个实际例子。

public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;
        MySharedObject localVariable2 = MySharedObject.sharedInstance;
        //...
        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);
        //...
    }
}
public class MySharedObject {

    //static variable pointing to instance of MySharedObject
    public static final MySharedObject sharedInstance = new MySharedObject();

    //member variables pointing to two objects on the heap
    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}

硬件架构

现代硬件的内存架构与Java内存模型还是有些不同的,了解硬件架构对理解Java内存模型也有帮助。简单的硬件架构图如下:

现代计算机一般是多核CPU,一般不止一个CPU,因此多个线程是可能在物理意义上并发运行的。这意味着,如果Java应用程序是多线程的,则每个CPU可能在Java应用程序中同时(并发)运行一个线程。

每个CPU包含一组寄存器,这些寄存器本质上是CPU内存储器。CPU在这些寄存器上执行操作的速度比对主存储器中的变量执行操作的速度快得多,这是因为CPU可以比访问主存储器更快地访问这些寄存器。

每个CPU可能还具有一个CPU高速缓存。实际上,大多数现代CPU都有一定大小的高速缓存。CPU可以比其主存储器更快地访问其高速缓存,但是通常不如其访问其内部寄存器的速度快。因此,CPU高速缓存存储器位于内部寄存器和主存储器之间的速度之间。某些CPU可能具有多个高速缓存层(L1和L2 Cache)。了解Java内存模型如何与内存交互并不是很重要,重要的是要知道CPU可以具有某种高速缓存层。

计算机还包含一个主存储区(RAM)。所有CPU都可以访问主存储器。主存储区通常比CPU的高速缓存大得多。

通常,当CPU需要访问主内存时,它将部分主内存读入其CPU缓存中。它甚至可以将缓存的一部分读入其内部寄存器,然后对其执行操作。当CPU需要将结果写回主存储器时,它将把值从其内部寄存器刷新到高速缓存,然后在某个时候将值刷新回主存储器。

当CPU需要将其他内容存储在高速缓存中时,通常会将高速缓存中存储的值刷新回主存储器。CPU高速缓存可以一次将数据写入其部分内存,并一次刷新其部分内存。它不必每次更新都读取/写入完整的缓存。通常,缓存在称为“缓存行”的较小存储块中更新,可以将一个或多个高速缓存行读入高速缓存存储器,并且可以将一个或多个高速缓存行再次刷新回主存储器。

Java内存模型与硬件关联

如前所述,Java内存模型和硬件内存体系结构是不同的,硬件内存体系结构不能区分线程堆栈和堆。在硬件上,线程堆栈和堆都位于主内存中。线程堆栈和堆的某些部分有时可能会出现在CPU缓存和内部CPU寄存器中。下图对此进行了说明:

当对象和变量可以存储在计算机的各种不同存储区域中时,可能会出现某些问题。 两个主要问题是:

  • 线程更新(写入)到共享变量的可见性。
  • 读取,检查和写入共享变量时的竞争条件。

对象的可见性

如果两个或多个线程共享一个对象,而没有正确使用volatile关键字,则一个线程对共享对象进行的更新可能对其他线程不可见。

每个线程都可以拥有自己的共享库副本,每个副本位于不同的CPU缓存中。想象一下,共享对象最初存储在主存储器中。然后,在CPU上运行的一个线程将共享对象读入其CPU缓存并进行修改。只要未将CPU缓存刷新回主存储器,在其他CPU上运行的线程就看不到共享对象的更改版本。

下图说明了这种情况,在左CPU上运行的一个线程将共享对象复制到其CPU缓存中,并将其count变量更改为2。在右CPU上运行的其他线程看不到此更改,因为尚未将count更新写回主内存。

当然这个问题可以使用volatile关键字来解决。

竞争条件

如果两个或多个线程共享一个对象,并且一个以上的线程更新该共享对象中的变量,则可能会发生竞争条件。

假如线程A将共享对象的变量count读入其CPU缓存中,而线程B执行同样操作,但是它位于不同的CPU缓存中。现在,线程A加一个要计数,线程B也执行相同的操作。现在count已增加两次,在每个CPU高速缓存中增加一次。

如果这些增加是顺序执行的,则变量计数将增加两次,并将原始值+2写回到主存储器中。

但是,这两个增量是在没有同步的情况下并发执行的。不管线程A和B中哪个线程将其更新后的版本写回主内存,尽管有两个增量,但更新后的值仅比原始值高1。

该图说明了如上所述的竞争条件问题的发生:

这个问题可以使用synchronized关键字来解决。

总结

到此这篇关于Java内存模型的文章就介绍到这了,更多相关Java内存模型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈Java并发中的内存模型

    什么是JavaMemoryModel(JMM)? JMM通过构建一个统一的内存模型来屏蔽掉不同硬件平台和不同操作系统之间的差异,让Java开发者无需关注不同平台之间的差异,达到一次编译,随处运行的目的,这也正是Java的设计目的之一. CPU和内存 在讲JMM之前,我想先和大家聊聊硬件层面的东西.大家应该都知道执行运算操作的CPU本身是不具备存储能力的,它只负责根据指令对传递进来的数据做相应的运算,而数据存储这一任务则交给内存去完成.虽然内存的运行速度虽然比起硬盘快非常多,但是和3GHZ,4GH

  • 学习Java内存模型JMM心得

    有时候编译器.处理器的优化会导致runtime与我们设想的不一样,为此Java对编译器和处理器做了一些限制,JAVA内存模型(JMM)将这些抽象出来,这样编写代码时就无需考虑那么多底层细节,并保证"只要遵循JMM的规则编写程序,其运行结果一定是正确的". JMM的抽象结构 在Java中,所有的实例.静态变量存储在堆内存中,堆内存是可以在线程间共享的,这部分也称为共享变量.而局部变量.方法定义参数.异常处理参数是在栈中的,栈内存不在线程间共享. 而由于编译器.处理器的优化,会导致共享变量

  • Java8内存模型PermGen Metaspace实例解析

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫"栈帧"的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用).操作数栈.方法出口等信息.栈的大小可以固定也可以动态扩展.当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值,我们通过下面这段程序可以测

  • 在Java内存模型中测试并发程序代码

    让我们来看看这段代码: import java.util.BitSet; import java.util.concurrent.CountDownLatch; public class AnExample { public static void main(String[] args) throws Exception { BitSet bs = new BitSet(); CountDownLatch latch = new CountDownLatch(1); Thread t1 = ne

  • 浅析Java内存模型与垃圾回收

    1.Java内存模型 Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示: 程序计数器:一块较小内存区域,指向当前所执行的字节码.如果线程正在执行一个Java方法,这个计数器记录正在执行的虚拟机字节码指令的地址,如果执行的是Native方法,这个计算器值为空. Java虚拟机栈:线程私有的,其生命周期和线程一致,每个方法执行时都会创建一个栈帧用于存储局部变量表.操作数栈.动态链接.方法出口等信息. 本地方法栈:与虚拟机栈功能类似,只不过虚拟机栈为虚拟机执行J

  • Java内存模型知识汇总

    为什么要有内存模型 在介绍Java内存模型之前,先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情.要说计算机的内存模型,就要说一下一段古老的历史,看一下为什么要有内存模型. 内存模型,英文名Memory Model,他是一个很老的老古董了.他是与计算机硬件有关的一个概念.那么我先给你介绍下他和硬件到底有啥关系. CPU和缓存一致性 我们应该都知道,计算机在执行程序的时候,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道.而计算机

  • Java内存模型JMM详解

    Java Memory Model简称JMM, 是一系列的Java虚拟机平台对开发者提供的多线程环境下的内存可见性.是否可以重排序等问题的无关具体平台的统一的保证.(可能在术语上与Java运行时内存分布有歧义,后者指堆.方法区.线程栈等内存区域). 并发编程有多种风格,除了CSP(通信顺序进程).Actor等模型外,大家最熟悉的应该是基于线程和锁的共享内存模型了.在多线程编程中,需要注意三类并发问题: ·原子性 ·可见性 ·重排序 原子性涉及到,一个线程执行一个复合操作的时候,其他线程是否能够看

  • Java 高并发三:Java内存模型和线程安全详解

    网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存.数据在主存中会有一份,在工作内存中也有一份.工作内存和主存之间会有各种原子操作去进行同步. 下图来源于这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变.本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰. 1. 原子性 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰

  • Java内存模型与JVM运行时数据区的区别详解

    首先,这两者是完全不同的概念,绝对不能混为一谈. 1.什么是Java内存模型? Java内存模型是Java语言在多线程并发情况下对于共享变量读写(实际是共享变量对应的内存操作)的规范,主要是为了解决多线程可见性.原子性的问题,解决共享变量的多线程操作冲突问题. 多线程编程的普遍问题是: 所见非所得 无法肉眼检测程序的准确性 不同的运行平台表现不同 错误很难复现 故JVM规范规定了Java虚拟机对多线程内存操作的一些规则,主要集中体现在volatile和synchronized这两个关键字. vo

  • Java内存区域和内存模型讲解

    一.Java内存区域 方法区(公有):用户存储已被虚拟机加载的类信息,常量,静态常量,即时编译器编译后的代码等数据.异常状态 OutOfMemoryError. 堆(公有):是JVM所管理的内存中最大的一块.唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配.Java堆是垃圾收集器管理的主要区域,因此很多时候也被称为"GC堆".异常状态 OutOfMemoryError. 虚拟机栈(线程私有): 描述的是java方法执行的内存模型:每个方法在执行时都会创建一个栈帧,用户存储局部变

随机推荐