Java多线程-带你认识Java内存模型,内存分区,从原理剖析Volatile关键字

写在前面
读完本篇文章你将知道:
Java的内存模型。

Java的内存分区。

全局变量、局部变量、对象、实例再内存中的位置。

JVM重排序机制。

JVM的原子性、可见性、有序性。

彻底了解Volatile关键字。

一. Java的内存模型
Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。想要掌握Java并非线程JMM一定要了解。Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。这里的涉及到共享内存区域,稍后会在Java的内存分区中介绍到。简单的说就是解释了一个问题:当多个线程再访问同一个变量的时候,其中一个线程改变了该变量的值但是并未写入主存中,那么其他线程就会读取到旧值,无法获取到最新的值。好了接下来看看什么是内存模型:
Java内存模型定义了线程和主存(可以理解为java内存分区中的共享区域,稍后将介绍)之间的抽象关系:线程之间的共享变量存贮再主存中,每个线程都会拥有属于自己的私有工作内存(这个内存分配再栈里面),再工作内存中只会存储该线程使用到的共享变量的副本。这里的私有工作内存其实是一个抽象的概念,它包括了缓存、写缓冲区、寄存器等区域。Java内存模型控制线程间的通信,它决定一个线程对主存共享变量的写入何时对另一个线程可见。这是Java内存模型抽象图:



从图中我们能分析出:
1.每个线程再执行的时候都会有自己的工作内存,其中包括了方法里面所包含的所有变量等。
2.每个线程的私有工作内存是不能相互访问的,这也就解释了为什么我们不能再一个方法中访问另一个方法的局部变量。
3.当线程想要访问共享变量的时候,需要从主存中获取,再自己的方法区中只是保存的变量的副本。
4.当我们修改完共享变量的时候,需要把改过的变量写入主存中,这样才能让其他线程获取到正确的值。
简单一点就是:
(1)线程A把线程A本地内存中更新过的共享变量刷新到贮存中去。
(2)线程B到主存中去读取线程A之前已更新过的共享变量的的值。
也就是:
int i= 1;
也就是说,这句代码被线程执行的时候是这样的,执行线程先把变量i的值的一个副本存放到自己的工作内存中,然后再写入主存中,而不是直接写入到主存中。
这样是不是就可以说明同一个不同的变量作为标记去打断线程是不严谨的,大家可以移步到我的上一篇文章如何正确的打断线程。
二. Java的内存分区
一般来说,Java程序在运行时会涉及到以下内存区域:
寄存器:

JVM内部虚拟寄存器,存取速度非常快,程序不可控制。
Java虚拟机栈(通俗就是我们常说的“栈”):

它是线程私有的,它的生命周期与线程相同。每个方法被执行的时候都会同时创建一个栈帧(StackFrame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。它存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型),它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
堆:

ava堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。 Java堆是垃圾收集器管理的主要区域。
方法区:

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来,但它还是属于堆里面的。
常量池(其实是方法区的一部分):

JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用(1)。池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。常量池存在于堆中.



需要注意的一些:
对于一个对象的成员方法,这些方法中包含本地变量的话,仍需要存储在栈区,即使它们所属的对象还在堆区里面.等于说是对象所拥有的方法里面涉及创建的变量存储在栈里面,方法里面使用到的全局变量是随着对象实例一起存储在堆里面,再方法中使用的时候也是通过对象的引用到堆里面的相对位置去访问的.

对于一个对象的成员变量,不管他是原始类型还是包装类型,都会被存贮在堆区.

方法区和堆是一样,是各个线程共享的区域,里面存放java虚拟机加载的类信息,常亮,静态变量,即使编译器编译后的代码等数据.

当调用一个对象的方法时会在java(虚拟机栈)栈里面创建属于自己的栈空间,方法走完即被释放

分清什么是实例什么是对象。Class a = new Class();此时a叫实例,而不能说a是对象。实例在栈中,对象在堆中,操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

那么我们通过代码来进一步的认识每个分区:

public class Persion{

privite String name = “Wang”;

privite static String love = “eat”;

public void init(int age){

if(age < 0){

age = 0;

}

Log.e(TAG,"Name is "+ name+"Age is "+ age);

}

}

首先我们知道 当我用 Persion p = new Perison()的时候,Persion p 这个引用存贮再栈里面,new Perison()的对象保存再堆里面,包括name成员变量都在堆里面;love这个静态变量存贮在常量池里面。当我们调用 p.init(10) 的时候,会在该线程所在的栈里面开创该线程私有的栈内存,用来保存age变量和name共享变量的副本。这里要说一下,堆、方法区被称为共享区域,这里面的数据才能被多线程所共享。

三. JVM重排序机制

在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte[1024*1024],那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。我们看下简单的例子:

public void execute(){
**java学习群669823128**
int a=0;

int b=1;

int c=a+b;

}

这里a=0,b=1两句可以随便排序,不影响程序逻辑结果。所以程序再运行的时候会选择先运行int b = 1 ;然后再运行 int a=0;但是我们是无法观察到的,这确是可能发生的,这句c=a+b这句必须在前两句的后面执行,所以在他的前后不会出现重排序。这里我们就简单的了解下就可以啦.

四. JVM的原子性、可见性、有序性

原子性
定义:对基本类型变量的读取和赋值操作是原子性操作,即这些操作是不可中断的,要么执行完毕,要么就不执行。

x =3;    //语句1

y =4    //语句2
**java学习群669823128**
z = x+y ;//语句3

x++;    //语句4

这里面的操作只有语句1和语句2是原子性的操作,语句3,4不是原子性的操作;因为再语句3中包括了三个操作,1是先读取x的值,2读取y的值,3将z的值写入内存中。语句4的解释是一样的。一般的一个语句含有多个操作该语句就不是原子性的操作,只有简单的读取和赋值才是原子性的操作。

可见性
就是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改结果,另一个线程马上就能看到。

有序性
Java内存模型允许编译器和处理器对指令进行重排序,虽然重排序不会影响到单线程的正确性,但是会影响到多线程的正确性。

五. Volatile关键字

这里呢Volatile的三个条件:

1.不保证原子性。

2.保证有序性。

3.保证可见性。

当用volatile修饰共享变量的时候,线程访问到该变量的时候都回去主存中去取该变量的值,它的工作内存中的缓存将失效,这样就保证了每个线程访问该变量的时候都是从主存中读写的。这就是为什么使用Volatile关键字来修饰线程间共享变量。

六. 结束语

这些也是对JVM的一些小的探索,希望能给大家带来一点小的帮助,如果喜欢的话请点个赞再走吧,感兴趣的话就点这里这个关注吧,之后我会继续给大家带来一下新的见解,或者把通俗易懂的语言来描述苦涩难懂的原理~
来了就喜欢一下吧
java学习群669823128

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容