Java内存结构/Java内存模型

类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区(或者叫java内存结构)的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口(下面的图只是一个大概,后面说到java的对象模型会进一步说明)

image.png

image.png

说到java内存结构(或者叫运行时数据区),不得不说其他的两个概念:Java内存模型/Java对象模型

Java内存模型(java memory model--JMM)

JMM是和多线程相关的,它描述了一组规则或者规范,这个规范定义了一个线程对共享变量的写入时对另一个线程是可见的;java的多线程通信是通过共享内存进行通信的,由于采用共享内存进行通信,在通信过程中会存在一系列问题,例如可见性,原子性,顺序性等。
所以说到JMM,就要展开说说可见性,原子性,顺序性
首先说说我们写代码时,操作变量的规则:

1.Java内存模型规定了所有的变量都存储在主内存
2.线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
3.线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量

可以来张图看看:


image.png

好了接下来我们继续说说可见性,原子性,顺序性


image.png

原子性

所谓“原子”操作,是指一组不可分割的操作:操作者对目标对象进行操作时,要么完成所有操作后其他操作者才能操作;要么这个操作者不能进行任何操作

1.基本数据类型(除了long,double64位除外,他们不是原子操作)

  int a,b;
  a=12;//原子操作
  b=++a;//非原子操作,因为“++a”有三步:++,取a,复制给b

2.JDK1.5的版本中提供了java.util.concurrent.atomic原子操作包:AtomicBoolean,AtomicIntegerArray,AtomicReference
3.CAS(Compare And Swap)--sun.misc.Unsafe---Doug Lea
4.更大范围的原子操作:使用lock和unlock原子操作,在jvm以更高层次的指令monitorenter和monitorexit指令开放给我们使用,反应到java代码中就是---synchronized关键字

备注:
1.java.util.concurrent.atomic原子操作包和AQS(AbstractQueuedSynchronizer)都用到了CAS
2.所谓CAS操作: CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
U.compareAndSwapInt(this, WAITSTATUS, expect, update)

有序性

可以总结为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

3个重要概念:

1.指令重排序
2.先行发生原则(happens-before)
3.as-if-serial语义

1.指令重排序

什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理

① 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
② 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
③ 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

image.png

下面看一段代码

int a = 1;
int b = 2;
int c = a+b;
a,b的赋值可以重排序,但是c不可以,因为c依赖a,b
也就是说,可以变成这样:
int b = 2;
int a = 1;
int c = a+b;

上面的例子是编译器和处理器会自动重排序,我们总有一些情况不希望重排序,例如使用volatie,final等关键字,后面会逐一说说这些关键字!
已经说了,总有不希望重排序的情况

可以通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序。
内存屏障,又称内存栅栏,是一个CPU指令
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序

java内存屏障

java 的内存屏障通常所谓的四种即LoadLoad、StoreStore、LoadStore、StoreLoad
1.LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
2.StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
3.LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
4.StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

2.先行发生原则(happens-before)

image.png

注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

3.as-if-serial语义

不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不会改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,
因为这种重排序会改变执行结果。
但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序

可见性

一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的,无论是普通变量还是volatile变量都是如此

说说3个关键字

1.synchronized
2.volatile
3.final

1.synchronized

说到synchronized,就是下面两种使用方式:

image.png

在dos命令行使用javap -v 类名.class查看如下,结果如下
image.png

大家应该知道synchronized关键字,是基于每个对象都是monitor锁,现在了解大概!synchronized关键字使用在方法前和方法内部是不一样的原理,后面在java的对象模型中会详细说明monitor的原理

备注:
在JDK1.6之前synchronized关键字使用起来还是有很多弊端的,所以在JDK1.5的时候,大神Doug Lea提供了线程并发库java.util.concurrent,其中包含了java.util.concurrent.atomic和java.util.concurrent.lock,上面关于原子性提到过!
不过在JDK1.6版本中,Java官方对从JVM层面对synchronized较大优化,也就是你可能听说过的
偏向锁 、轻量级锁 、自旋锁、锁消除 、锁粗化
所以现在到底使用synchronized或者lock,可以根据自己的使用情况决定

2.volatile

当一个变量定义为 volatile 之后,将具备两种特性

1.保证此变量对所有的线程的可见性,这里的“可见性”,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
2.禁止指令重排序优化。有volatile修饰的变量,相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile语义中的内存屏障
1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;

image.png

说说我们常用的单利模式:直接上图


image.png

我们通常写单例,并且已经知道肯定会多线程操作,一般会这种写法,但是官方建议,由于synchronized在JDK1.6之后已经有了优化,这里不建议再使用volatitle!(为啥Glide的单例使用了呢)

3.final

我们都知道final关键字用在类,方法,字段都代表什么意思!这里说说不一样的东西!


image.png

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

推荐阅读更多精彩内容