引言

在计算机系统的发展过程中,由于CPU的运算速度和计算机存储速度之间巨大的差距。为了解决CPU的运算速度和计算机存储速度之间巨大的差距,设计人员在CPU和计算机存储之间加入了高速缓存来做为他们之间的桥梁,在运算时,先将数据拷贝到高速缓存中,计算完成后再将结果写入计算机存储,这样大大提高了计算效率,避免重复多次访问计算机存储造成的cpu资源浪费。

尽管这样,CPU还是存在很多空闲的时间段,为了压榨CPU的性能,多任务处理诞生了,同时多任务处理导致任务之间共享资源的争抢,从而引发了并发问题。

在Java应用程序中,为了更好的解决并发问题,就必须深入理解Java内存模型。Java内存模型是Java虚拟机非常重要的一部分,它用来指导Java虚拟机是如何与计算机硬件内存之间协同工作的。在了解Java内存模型之前,我们先来看看计算机硬件内存模型是怎么样的。

硬件内存模型



现代计算机通常有2个或以上的CPU,单个CPU可能有多个内核。每个CPU内核中都包含一组寄存器,CPU在寄存器中执行操作比在计算机主存储器中快的多。

同时每个CPU之上还存在高速缓存,但高速缓存的层级和位置是不固定的,现代计算机的缓存层级很多达到了三级,未来可能更多。缓存的位置也各有不同,有的集成了部分缓存到CPU中。 同样,缓存的读写速度也大大快于计算机主存储器,在寄存器和主存储器之间,这样的CPU--缓存--主存储的三层结构就构成了硬件内存模型。

CPU在程序的执行过程中,经常会频繁的调用相同的数据,比如在一个循环内调用了位于另外一个物理地址的函数,这个函数可能与当前指令的物理位置相距甚远,因为程序使用的物理内存并不是连续的,这就导致了需要花费很多不必要的时间在物理寻址上。但如果在CPU计算之前会将所需要用到的数据先读到缓存中,计算完成之后再一次性写入计算机主存储器,就可以避免频繁访问计算机主存储器造成的资源浪费。

Java内存模型

上面说了计算机的硬件内存模型,Java内存模型和硬件内存模型有很多类似的地方。由于存在不同的计算机操作系统类型和硬件类型,导致各种平台下物理内存模型的不一致。为了让Java上层开发有一个统一的内存访问操作,保证多线程对共享数据的读写一致性,JVM规范定义了Java内存模型(Java Memory Model JMM)。



JMM通过happens-before语义(篇幅有限,后面的文章再详细解说)定义了Java对数据的统一访问规则。这些数据主要包括实例字段,静态字段和构成数组的元素,但不包括局部变量、方法参数和异常处理参数,因为局部变量和方法参数是线程私有的,不存在数据竞争问题。

引用类型比较特殊,引用本身是线程私有的,但它引用的对象是可被共享的。

JMM还规定了所有的变量都存储在主内存中(Main Memory),同时每个线程有自己的本地内存(Local Meory,也叫工作内存),本地内存中保存了所需要用到的主内存数据的拷贝。线程对变量的读和写都在本地内存中进行。

是不是发现JMM和硬件内存模型存在很多相似之处?主内存对应计算机主存储,本地内存对应高速缓存。但要知道它们虽然可以类比,却并不是相同的东西。

本地内存仅仅是JMM的一个抽象概念,实际上JVM中并不存在这样一个区域来对应,这个区域在广义上可以包括缓存、寄存器以及其他的硬件和编译器优化等等。这句话可能听起来比较难懂,我们只需要知道线程对共享变量的操作并不会直接访问主内存,而是访问一个中间层,这个中间层包含了主内存中变量的拷贝,同时中间层的访问速度大大快于访问主内存的速度,在一定的操作之后将结果统一写回主内存,这样就大大提高了程序的性能。

同时也会产生另外一个问题,同一个共享变量在每一个线程之中都会有一份拷贝(对引用类型,并不是拷贝全部数据),产生的线程越多,缓存开销也就越大。

JVM内存模型

JVM内存模型定义的是线程堆栈和堆之间的内存划分,它和Java内存模型是有区别的,参照《深入理解Java虚拟机》中的解释:

这两者本没有关系。如果一定要勉强对应,那从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次上说,主内存就是物理内存,而为了获取更好的执行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为运行时主要访问——读写的是工作内存。

所有的原始类型(boolean,byte,short,char,int,long,float,double)局部变量都存储在线程堆栈中,不对其他线程共享。堆中则包含了Java程序中创建的对象。

举个例子:

public class MemoryModel {

	public int i = 0;

	public void methodOne() {

		int localVarOne = 1;

		SharedObject localVarTwo = SharedObject.sharedObject;

		Integer localVarThree = new Integer(1);
}
} public class SharedObject { pubic static SharedObject sharedObject = new SharedObject(); public int sharedVarOne = 1;
}

代码中局部变量localVarOne存储在线程堆栈中。局部变量localVarTwo的引用存储在线程堆栈中,但对象本身存储在堆上。局部变量localVarThree同localVarTwo一样,引用存储在线程堆栈中,但对象本身存储在堆上。不同的是多线程执行methodOne方法时,localVarTwo由于是静态类型,在堆中只有一份数据,而localVarThree在堆和堆栈中都有多份数据。局部变量对象的成员变量sharedVarOne也存储在堆上,无论sharedVarOne是基本类型还是引用类型都是如此。




参考资料:

《深入理解Java内存模型》

《深入理解Java虚拟机》

《Java并发编程的艺术》

http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

Java并发(1)- 聊聊Java内存模型的更多相关文章

  1. Java并发编程:Java的四种线程池的使用,以及自定义线程工厂

    目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...

  2. Java并发编程(1)-Java内存模型

    本文主要是学习Java内存模型的笔记以及加上自己的一些案例分享,如有错误之处请指出. 一 Java内存模型的基础 1.并发编程模型的两个问题 在并发编程中,需要了解并会处理这两个关键问题: 1.1.线 ...

  3. 【Java并发系列】--Java内存模型

    Java内存模型 1 基本概念 程序:代码,完成某一个任务的代码序列(静态概念) 进程:程序在某些数据上的一次运行(动态) 线程:一个进程有一个或多个线程组成(占有资源的独立单元) 2 JVM与线程 ...

  4. 【Java并发基础】Java内存模型解决有序性和可见性

    前言 解决并发编程中的可见性和有序性问题最直接的方法就是禁用CPU缓存和编译器的优化.但是,禁用这两者又会影响程序性能.于是我们要做的是按需禁用CPU缓存和编译器的优化. 如何按需禁用CPU缓存和编译 ...

  5. Java并发编程:Java内存模型JMM

    简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...

  6. 深入理解java虚拟机(6)---内存模型与线程 & Volatile

    其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...

  7. 深入理解Java虚拟机读书笔记8----Java内存模型与线程

    八 Java内存模型与线程   1 Java内存模型     ---主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.     ---此处的变量和J ...

  8. Java原子性、可见性、内存模型

    原子性: 原子性就是指该操作是不可再分的.不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作.简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性.比如 a = ...

  9. Java并发编程--7.Java内存操作总结

    主内存和工作内存 工作规则 Java内存模型, 定义变量的访问规则, 即将共享变量存储到内存和取出内存的底层细节  所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中用到的变量, 是从主 ...

  10. java核心技术-多线程之线程内存模型

    对于每一种编程语言,理解它的内存模型是理所当然的重要.下面我们从jvm的内存模型来体会下java(不限java语言,严格来讲是JVM内存模型,所有JVM体系的变成语言均适用)的内存模型. 堆: 就是我 ...

随机推荐

  1. nginx虚拟主机搭建

    nginx [engine x]是 Igor Sysoev 编写的一个 HTTP 和反向代理服务器,另外它也可以 作为邮件代理服务器. 它已经在众多流量很大的俄罗斯网站上使用了很长时间,这些网站包括 ...

  2. tp5 使用技巧(持续更新中...)

    tp5 使用技巧(持续更新中...) 1.自动写入时间 create_time和update_time 使用save方法才行,insert方法不生效,不知为何 2.过滤字段 allowfield和st ...

  3. 数据分析处理库Pandas——对象操作

    Series结构 索引 修改 旧数据赋值给新数据,旧数据不变. 对某一数值进行修改,可以选择保留修改前或修改后的数值. 替换索引 修改某一个索引 添加 在数据1后添加数据2,数据1不改变. 添加一个数 ...

  4. python -- configparse读取配置文件

    在开发过程中,有的时候需要将一些参数写入到配置文件中,这样在改动一些相关信息时,可以直接在配置文件中进行修改. 而在python中,可以通过内置模块configparse对标准的配置文件进行读取. 配 ...

  5. scala初体验-02

    上一节,我们讲了scala的安装的即一些初步方法,今天,我们来介绍一下scala里面的一些基本操作 1.对于map的的编写,这个是广泛用于Array里面的 val arr = Array(1,2,3, ...

  6. Python文章推荐1

    Table of Contents 1. 分享最近看到的python相关的几篇好文(我只是想偷懒) 1.1. 形象解释了什么是GIL 1.2. 知乎上 Pythonic 相关 1.3. evil &q ...

  7. MethodTrace 生成的trace文件为空

    今天我准备生成一个trace文件,看看程序卡在哪里. 一般: Debug.startMethodTracing("yuge"); Debug.stopMethodTracing() ...

  8. 《Cracking the Coding Interview》——第14章:Java——题目5

    2014-04-26 19:06 题目:Java中的对象反射机制是什么?有鼠么用? 解法:完全不了解,因为java编程经验太少,完全没用过.查了一些资料后,感觉反射机制是个强大并需要边用边体会的强大工 ...

  9. python学习笔记六:内置函数

    一.数学相关 1.绝对值:abs(-1) 2.最大最小值:max([1,2,3]).min([1,2,3]) 3.序列长度:len('abc').len([1,2,3]).len((1,2,3)) 4 ...

  10. 【Python】python内置函数、列表生成式、生成器

    一.内置函数 1 print(all([1,2,3,4]))#判断可迭代的对象里面的值是否都为真 2 print(any([0,1,2,3,4]))#判断可迭代的对象里面的值是否有一个为真 3 pri ...