JVM内存模型 与 JMM内存模型

JVM内存模型(Java Virtual Machine,JVM)java

java虚拟机JVM = 类加载器(classloader) + 执行引擎(execution engine) + 运行时数据区域(runtime data area)程序员

** 1 、程序计数器(Program Counter Register)**面试

程序计数器是一块较小的内存空间,它的做用:算法

1.1. 能够看作是当前线程所执行的字节码的信号指示器。字节码解释器就是经过改变该计数器的值来选取下一条须要执行的字节码指令, 分支、循环、跳转、异常处理、线程恢复等基础功能都需依赖计数器来完成。注:可是,若是当前线程正在执行的是一个本地方法,那么此时程序计数器为空。编程

1.2. 在多线程的状况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候可以知道该线程上次运行到哪儿了。特色:线程私有的, 生命周期随着线程的建立而建立,随着线程的结束而死亡。 此内存区域是惟一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 状况的区域。数组

** 二、Java 虚拟机栈(Java Virtual Machine Stack)**缓存

Java虚拟机栈与程序计数器同样,也是线程私有的,其生命周期与线程相同。虚拟机 栈描述的是 Java 方法执行的内存模型:每一个方法被执行的时候都会同时建立一个栈帧(StackFrame)用于存储局部变量表、操做数栈、动态连接、方法出口等信息。每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存放了编译期可知的各类基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)。其中 64 位长度的 long 和 double 会占用 2个局部变量空间(Slot,一个 32 位),其他数据类型只占用 1个。局部变量表所需的空间在编译期间完成分配,当进入一 个方法时,其须要在帧中分配多大的局部变量空间是肯定的,方法运行期间不会改变局部变 量表的大小。局部变量表的建立是在方法被执行的时候,随着栈帧的建立而建立。并且,局部变量表的大小在编译时期就肯定下来了,在建立的时候只需分配事先规定好的大小便可。此外,在方法运行的过程当中局部变量表的大小是不会发生改变的。数据结构

Java 虚拟机规范中对该区域规定了两种异常状况:多线程

2.1. 如 线 程 请 求 的 深 度 大 于 虚 拟 机 所 允 许 的 深 度 , 栈 溢 出 , 如 递 归 时 , 抛 出StackOverflowError 异常。并发

2.2. 虚拟机栈动态扩展没法申请到足够的内存时,抛出 OutOfMemoryError 异常。当方法传递参数时其实是一个方法将本身栈帧中局部变量表的副本传递给另外一个方法栈帧中的局 部变量表(注意是副本,而不是其自己),无论数据类型是什么(基本类型,引用类型)

三、本地方法栈(Native Method Stack)

Java 虚拟机可能会使用到传统的栈来支持 native 方法(使用 Java 语言之外的其它语言 编写的方法)的执行。线程私有的,如 Sun HotSpot 虚拟机直接把本地方法栈和虚拟机栈合 二为一。

Java 虚拟机规范中对该区域规定了两种异常状况:

3.1.如线程请求的深度大于虚拟机所容许的深度,抛出 StackOverflowError 异常。

3.2.虚拟机栈动态扩展没法申请到足够的内存时,抛出 OutOfMemoryError 异常。

四、Java 堆(Java Heap)

Java堆是 Java 虚拟机管理内存中最大的一块,是全部线程共享的内存区域,随虚拟机的启动而建立。该区域惟一目的是存放对象实例,几乎全部对象的实例都在堆里面分配。Java 虚拟机规范规定,Java堆能够出于物理上不连续的内存空间中,只要逻辑上连续便可,如同磁盘空间同样,既能够实现成固定大小,也能够是扩展的,当前主流虚拟机都是按照扩展来实现的(经过-Xmx 和-Xms 控制)。Java堆是垃圾收集器管理的主要区域,所以也叫"GC堆",细分一点能够分为新生代和老年代;再细致一点新生代能够分为Eden空间、From Survivor空间、ToSurvivor空间。Java 虚拟机规范中对该区域规定了 OutOfMemoryError 异常:若是堆中没有 内存完成实例分配,而且堆没法再扩展则抛出 OutOfMemoryError 异常。(当 Ol d 区被放满的以后,进行 Full GC,Full GC 后,若 Survivor 及 old 区仍然没法存放 从 Eden 复制过来的部分对象,则出现 OOM 错误/或者直接存放大对象、大数组,致使老年代空间不足)

五、方法区(Method Area)

方法区与 Java 堆同样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信 息、常量、静态变量、即时编译器编译后的代码等数据。在 HotSpot 中用永久代来实现方法区,而其余虚拟机(如 BEA JRockit、IBM J9 等)是不存在永久代的。Java7 中已经将运行时常量池从永久代移除,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。而在 Java8 中,已经完全没有了永久代,将方法区直接放在一个与堆不相连的本地内存区域,这个区域被叫作元空间。元空间的本质和永久代相似,都是对 JVM 规范中方法区的实现。不过元空间与永久代 之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元 空间的大小仅受本地内存限制,但能够经过如下参数来指定元空间的大小:-XX:MetaspaceSize,初始空间大小。-XX:MaxMetaspaceSize,最大空间,默认是没有 限制的。Java 虚拟机规范中对方法区规定了 OutOfMemoryError 异常: 若是方法区的内存空间 不能知足内存分配请求,那 Java 虚拟机将抛出一个 OutOfMemoryError 异常。

六、运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。线程共享。Class 文件中除了有类的版本、字段、方 法、接口等信息外,还有一项信息是常量池,用于存放编译期生成的各类字面常量和符号引 用,这部份内容在类加载后存放到方法区的常量池中。static 修饰的静态变量也存放在方法区中,但不是在常量池中(不能修饰局部变量),不 能在一个方法内部定义 static 变量(final 能够),只能定义为成员变量。当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。并且在运行期间,能够向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。当运行时常量池中的某些常量没有被对象引用,同时也没有被变量引用,那么就须要垃圾收集器回收。Java 虚拟机规范中对该区域规定了 OutOfMemoryError 异常: 当常量池没法申请到内 存时抛出 OutOfMemoryError 异常。

七、直接内存

直接内存是除Java虚拟机以外的内存,但也有可能被Java使用。在NIO中引入了一种基于通道和缓冲的IO方式。它能够经过调用本地方法直接分配Java虚拟机以外的内存,而后经过一个存储在Java堆中的DirectByteBuffer对象直接操做该内存,而无需先将外面内存中的数据复制到堆中再操做,从而提高了数据操做的效率。直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。

八、栈帧

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区 的虚拟机栈的栈元素。栈帧存储了方法的局部变量表,操做数栈,动态链接和方法返回地址 等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈 的过程。在编译代码的时候,栈帧中须要多大的局部变量表,多深的操做数栈都已经彻底确 定了,而且写入到了方法表的 Code 属性中,所以一个栈帧须要分配多少内存,不会受到程 序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。一个线程中的方法调用链可能会很长,不少方法都同时处理执行状态。对于执行引擎来 讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。8.一、局部变量表

Java内存模型(Java Memory Model ,JMM)

就是一种符合内存模型规范的,屏蔽了各类硬件和操做系统的访问差别的,保证了Java程序在各类平台下对内存的访问都能获得一致效果的机制及规范。目的是解决因为多线程经过共享内存进行通讯时,存在的原子性、可见性(缓存一致性)以及有序性问题。

基础概念:

一、原子性

线程是CPU调度的基本单位。CPU有时间片的概念,会根据不一样的调度算法进行线程调度。因此在多线程场景下,就会发生原子性问题。由于线程在执行一个读改写操做时,在执行完读改以后,时间片耗完,就会被要求放弃CPU,并等待从新调度。这种状况下,读改写就不是一个原子操做。即存在原子性问题。

二、缓存一致性

在多核CPU,多线程的场景中,每一个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不一样的核心上执行,则每一个核心都会在各自的caehe中保留一份共享内存的缓冲。因为多核是能够并行的,可能会出现多个线程同时写各自的缓存的状况,而各自的cache之间的数据就有可能不一样。 在CPU和主存之间增长缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每一个核的本身的缓存中,关于同一个数据的缓存内容可能不一致。

三、有序性

除了引入了时间片之外,因为处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,好比load->add->save 有可能被优化成load->save->add 。这就是有序性问题。 多CPU多级缓存致使的一致性问题、CPU时间片机制致使的原子性问题、以及处理器优化和指令重排致使的有序性问题等,都硬件的不断升级致使的。那么,有没有什么机制能够很好的解决上面的这些问题呢? 最简单直接的作法就是废除处理器和处理器的优化技术、废除CPU缓存,让CPU直接和主存交互。可是,这么作虽然能够保证多线程下的并发问题。可是,这就有点因噎废食了。 因此,为了保证并发编程中能够知足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。 为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操做行为的规范。经过这些规则来规范对内存的读写操做,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等致使的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

针对上面的这些问题,不一样的操做系统都有不一样的解决方案,而Java语言为了屏蔽掉底层的差别,定义了一套属于Java语言的内存模型规范,即Java内存模型。 Java内存模型规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存,线程的工做内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存。不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量的传递均须要本身的工做内存和主存之间进行数据同步进行。 而JMM就做用于工做内存和主存之间数据同步过程。他规定了如何作数据同步以及何时作数据同步。 

Java内存模型的实现

了解Java多线程的朋友都知道,在Java中提供了一系列和并发处理相关的关键字,好比volatile、synchronized、final、concurren包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。 在开发多线程的代码的时候,咱们能够直接使用synchronized等关键字来控制并发,历来就不须要关心底层的编译器优化、缓存一致性等问题。因此,Java内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。 本文并不许备把全部的关键字逐一介绍其用法,由于关于各个关键字的用法,网上有不少资料。读者能够自行学习。本文还有一个重点要介绍的就是,咱们前面提到,并发编程要解决原子性、有序性和一致性的问题,咱们就再来看下,在Java中,分别使用什么方式来保证。

原子性: 在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现原理文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized。 所以,在Java中可使用synchronized来保证方法和代码块内的操做是原子性的。

可见性: Java内存模型是经过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存做为传递媒介的方式来实现的。 Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后能够当即同步到主内存,被其修饰的变量在每次是用以前都从主内存刷新。所以,可使用volatile来保证多线程操做时变量的可见性。 除了volatile,Java中的synchronized和final两个关键字也能够实现可见性。只不过实现方式不一样,这里再也不展开了。

有序性: 在Java中,可使用synchronized和volatile来保证多线程之间操做的有序性。实现方式有所区别: volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只容许一条线程操做。 好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可使用的关键字。读者可能发现了,好像synchronized关键字是万能的,他能够同时知足以上三种特性,这其实也是不少人滥用synchronized的缘由。 可是synchronized是比较影响性能的,虽然编译器提供了不少锁优化技术,可是也不建议过分使用。

面试如何回答

当面试官问你:能简单介绍下你理解的内存模型吗?

首先,先和面试官确认一下:您说的内存模型指的是JMM,也就是和并发编程有关的那一个吧? 在获得确定答复后,再开始介绍(若是不是,那可能就要回答JVM堆、栈、方法区哪些了....囧...): Java内存模型,实际上是保证了Java程序在各类平台下对内存的访问都可以获得一致效果的机制及规范。目的是解决因为多线程经过共享内存进行通讯时,存在的原子性、可见性(缓存一致性)以及有序性问题。 除此以外,Java内存模型还提供了一系列原语,封装了底层实现后,供开发者直接使用。如咱们经常使用的一些关键字:synchronized、volatile以及并发包等。 回答到这里就能够了,而后面试官可能会继续追问,而后根据他的追问再继续往下回答便可。

相关文章
相关标签/搜索