码迷,mamicode.com
首页 > 其他好文 > 详细

jvm-简介

时间:2017-07-28 23:38:44      阅读:178      评论:0      收藏:0      [点我收藏+]

标签:logs   java程序   执行   共享变量   部分   外包   范围   tom   本地   

这里有3个概念可能需要强调:

jvm:java virtual machine,即java虚拟机,可以看成是一个抽象的物理计算机。jvm运行时数据区又分为heap、stack、native method stack、method area、pc五大部分,jvm执行引擎负责执行由classloader加载进方法区的class字节码序列。

jre:Java Runtime Environment,即java运行环境,是一个软件包。jre提供了运行java程序时所必须的环境,比如jvm、java基础类库等。

jdk:Java Development Kit,即java开发套件。jdk是jre的超集,额外包含了一些利于开发的工具,比如javac、javap等。

下面我来说jvm

 JVM

 jdk7中的jvm架构大体如下图:

技术分享

其中与我们关系最密切莫过于heap和stack了,也就是我们常说的堆栈

堆为共享数据的存储提供了空间,栈为程序的执行了空间(程序执行是需要空间的,比如局部变量)。共享数据和多线程就可能产生线程安全问题,比如我们通过单例模式构造出的实例可以被不同线程使用,如果该共享实例存在线程安全问题(比如有未被保护的可修改的实例变量),就引出了线程同步的问题。

 

JMM

多线程问题在java内存模型(java memory model)中有所体现,如同物理机有高速缓存、内存、缓存一致性协议(用于保证多线程的缓存中数据一致),java虚拟机也有工作内存、主内存、一致性协议(load、store)。

如下图所示,线程A与线程B并发访问共享变量S,在本地内存(工作内存,注意这里不是jvm运行时数据区中的栈)中会存有共享变量的副本(引用)S1,当线程A首次访问S时,会先从主内存中得到S的引用S1,在首次对S1中变量进行写入时,亦会从主内存中获取(可能还会缓存于本地内存),当再次对S1中相同变量进行写入时,可能就只是对缓存做出相应的操作了。如果A利用了缓存(本地内存A),那么线程B在读取S中线程A修改过的变量时就会出现问题,因为此时线程A只更新了其自己的缓存(本地内存A),还未更新在主内存中对应的值,B读到的就是旧值。

技术分享

 

如何避免这种情况呢?一种方式就是将S中的共享属性修饰为volatile,该关键字表示对被修饰字段的修改必须同步回主内存(而不能只写入缓存),对被修饰字段的读取必须访问主内存(而不能从缓存中读取),即volatile的可见性(volatile还有一个禁止指令重排序的特性)。

 说到JMM就不得不提一下jvm中的八个原子操作:

lock、unlock,作用于主内存,对共享变量加锁以实现线程同步(synchronized就是通过lock、unlock实现线程同步的);

read、load,前者作用于主内存,将变量传递给工作内存,后者作用于工作内存,用于接收主内存传来的变量,并将变量存于工作内存的变量副本中;

store、write,前者作用于工作内存,用于将变量传给主内存,后者作用于主内存,用于接收工作内存传来的变量,并将值更新置主内存;

assign、use,二者都作用于工作内存,前者用于接收执行引擎传来的值,并赋给工作内存中相应的变量,后者用于将工作内存中变量值传给执行引擎。(线程内更新共享变量后,可能只是进行了assign操作,而没有执行store、write操作,即只更新了工作内存中的变量值,而没有同步到主内存中)

由于有一致性协议的存在,store、load不会单独执行,必然会与write、read顺序执行(并不保证连续执行,但相对顺序是保证的,比如read a,read b,load a,load b,而不会read a,read b,load b,load a)。这里就有一个疑问了,在jvm看来一个简单的赋值操作int a = 1;需要这么多个原子操作,而赋值操作本身不是原子操作了?(原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断)待解决

类似的疑问:https://stackoverflow.com/questions/4756536/what-operations-in-java-are-considered-atomic

JMM的三大主题

JMM对于并发的处理,主要就是围绕着3个主题:原子性、可见性、有序性,原子性和有序性是针对操作的,可见性是针对数据的。

原子性,大体可以认为java中对数据的读写是原子操作(long和double未必),如果有更大范围的原子性要求,可以使用synchronized、jdk中的ReentrantLock、Atomic类。

可见性,可见性是指线程A对数据的更新,线程B立即可见。由于缓存的存在,一般操作都是不满足可见性的,但可以通过volatile(读写都会刷新缓存,直接从主内存中取)、final(在this没有逃逸的情况下保证可见性)、synchronized(unlock时会将缓存数据刷回主内存)实现。

有序性,volatile通过禁止指令重排序实现,synchronized通过临界区只允许一个线程访问实现。

这里解释一下final的可见性,比如下段代码,线程A正在MyObject.getInstance,到new MyObject()后线程B开始执行,B调用MyObject.getInstance().getD(),问题就在这里。线程A此时并未unlock,也就是说此时obj和d都未必同步回主内存,假设此时obj同步回主内存而d没有(这是一种可能的情况),那么B就可以通过调用MyObject.getInstance()返回MyObject实例,但是由于d还未同步回主内存,故MyObject.getInstance().getD()会返回null。

 1 public MyObject{
 2     private static MyObect obj;
 3     private Date d = new Data();
 4     public Data getD(){return this.d;}
 5     public static MyObect  getInstance(){
 6         if(obj == null){
 7             synchronized(MyObect .class){
 8                 if(obj == null)
 9                     obj = new MyObject();
10                     //出让cpu控制权
11             }
12         }
13         return obj;
14     }
15 }    

 

jvm-简介

标签:logs   java程序   执行   共享变量   部分   外包   范围   tom   本地   

原文地址:http://www.cnblogs.com/holoyong/p/7251524.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!