一、 jvm内存模型
在描述jvm线程模型以前,咱们先深刻的理解下,jvm内存模型。在jvm1.8以前,jvm的逻辑结构和物理结构是对应的。即Jvm在初始化的时候,会为堆(heap),栈(stack),元数据区(matespace)分配指定的内存大小,Jvm线程启动的时候会向服务器申请指定的内存地址空间进行分配。在jdk1.8以后,使用了G1垃圾回收器,逻辑上依然存在堆,栈,元数据区。可是在物理结构上,G1采用了分区(Region)的思路,将整个堆空间分红若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。所以,在堆的使用上,G1并不要求对象的存储必定是物理上连续的,只要逻辑上连续便可;每一个分区也不会肯定地为某个代服务,能够按需在年轻代和老年代之间切换。启动时能够经过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。java
1.一、 jvm内存模型介绍编程
1.二、 Jvm堆
Java堆是和Java应用程序最密切的内存空间,几乎全部的对象都放到堆中。而且堆彻底由Jvm管理,经过垃圾回收机制,垃圾对象会被自动清理,而不需显式的释放。
根据垃圾回收机制的不一样,Java堆一般被分为如下的集中不一样的结构。数组
构成 | 描述 |
New Generation | 由 Eden + Survivor (From Space + To Space)组成 |
Eden | 因此的new出来的新对象都存放到Eden区 |
Survivor Space | Eden每次垃圾清理事后,任然没又被清理的对象,会转移到交换区中 |
Old Generation | 在交换区中未被清理的对象(默认清理18次标记),将转移到老年代。 |
1.三、 Jvm栈
Java栈是一块线程私有的内存空间,Java栈和线程执行密切相关。线程的执行基本单位就是函数调用,每次函数调用的数据就会经过Java栈传递。
Java栈与数据结构上的栈有着相似的含义,它是一块先进后出的数据结构,只支持出栈和入栈的两种操做。在Java栈中保存的主要内容为栈帧。每次调用一个函数,都会有一个对应的栈帧被压入Java栈。每个函数调用结束,都会有一个栈帧被弹出Java栈。例如:缓存
如图所示,每次调用一个函数都会被当作栈帧压入到栈中。其中每个栈帧对应一个函数。因为每次调用函数都会生成一个栈帧,从而占用必定的栈空间。若是线程中存在大量的递归操做,会频繁的压栈,致使栈的深刻过于深刻,当栈的空间被消耗殆尽的时候,会抛出StackOverflowError栈溢出错误。
当函数执行结束返回时,栈帧从Java栈中被弹出。Java方法有两种返回的方式,一种是正常函数返回,即便用 return; 另一种是抛出异常。无论哪一种方式,都会致使栈帧被弹出。安全
1.3.一、 局部变量表
局部变量表示栈帧的重要组成部分之一。它用于保存函数已经局部变量。局部变量表中的变量只有在当前的函数中调用有效,当调用函数结束之后,随着函数栈帧的销毁,局部变量表也随之销毁。服务器
1.3.二、 操做数栈
操做数栈也是栈帧中重要的内容之一,它主要保存计算过程当中的结果,同事做为计算过程临时变量的存储空间。
操做数栈也是一个先进后出的数据结构,只支持入栈和出栈的两种操做,Java的不少字节码指令都是经过操做数栈进行参数传递的。好比iadd指令,它就会在操做数栈中弹出两个整数进行加法计算,计算结果会被入栈。入下图所示:数据结构
1.3.三、 帧数据区
每一个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另一部分将在每一次的运行期期间转化为直接引用,这部分称为动态链接并发
1.四、 Jvm方法区(jdk1.8元数据区)
它主要存放一些虚拟机加载的类信息,常量,静态变量,即便编译器后的代码等数据。根据Java虚拟机规范的规定,当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。jvm
1.4.一、 运行时常量池
运行时常量区是方法区的一部分。用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入方法区的运行时常量池中存放。还会有一些符号引用转换的直接引用一保存在运行时常量池中。
运行时常量池具有动态性,也就是运行期间也能够将新的常量放入池中,例如String.intern()方法。当常量池没法再申请到内存时,会抛出OutOfMemoryError异常函数
二、 jvm线程模型
经过Jvm内存模型,咱们能够发现,Jvm其实就是操做系统的一种镜像。是软件层次的虚拟机。其中咱们队Jvm内存模型分析可知:堆,方法区是线程共有的;栈是每一个线程私有的。
讨论Java内存模型和线程以前,先简单介绍一下硬件的效率与一致性
因为计算机的存储设备与处理器的运算能力之间有几个数量级的差距,因此现代计算机系统都不得不加入一层读写速度尽量接近处理器运算速度的高速缓存(cache)来做为内存与处理器之间的缓冲:将运算须要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中没这样处理器就无需等待缓慢的内存读写了。
基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,可是引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每一个处理器都有本身的高速缓存,而他们又共享同一主存,以下图所示:多个处理器运算任务都涉及同一块主存,须要一种协议能够保障数据的一致性,这类协议有MSI、MESI、MOSI及Dragon Protocol等。Java虚拟机内存模型中定义的内存访问操做与硬件的缓存访问操做是具备可比性的,后续将介绍Java内存模型。
除此以外,为了使得处理器内部的运算单元能竟可能被充分利用,处理器可能会对输入代码进行乱起执行(Out-Of-Order Execution)优化,处理器会在计算以后将对乱序执行的代码进行结果重组,保证结果准确性。与处理器的乱序执行优化相似,Java虚拟机的即时编译器中也有相似的指令重排序(Instruction Recorder)优化。
2.一、 Java内存模型
定义Java内存模型并非一件容易的事情,这个模型必须定义得足够严谨,才能让Java的并发操做不会产生歧义;可是,也必须得足够宽松,使得虚拟机的实现能有足够的自由空间去利用硬件的各类特性(寄存器、高速缓存等)来获取更好的执行速度。通过长时间的验证和修补,在JDK1.5发布后,Java内存模型就已经成熟和完善起来了。
2.1.一、 主内存与工做内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不同,指包括了实例字段、静态字段和构成数组对象的元素,可是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了全部的变量都存储在主内存中,每条线程还有本身的工做内存(能够与前面将的处理器的高速缓存类比),线程的工做内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的全部操做(读取、赋值)都必须在工做内存中进行,而不能直接读写主内存中的变量。不一样线程之间没法直接访问对方工做内存中的变量,线程间变量值的传递均须要在主内存来完成,线程、主内存和工做内存的交互关系以下图所示,和上图很相似
2.1.二、 java内存变量的交互操做
主内存变量和工做内存变量之间的交互过程以下:
三、 线程安全
经过上面分析,能够发现,每一个线程都拥有本身的工做内存,工做内存是线程私有的。因此每一个线程对堆中的共享变量进行修改对其余的线程而言是不可见的。
Java内存模型中,程序(进程)拥有一块内存空间,能够被全部的线程共享,即MainMemory(主内存:堆);而每一个线程又有一块独立的内存空间,即WorkingMemory(工做内存:栈)。普通状况下,当线程须要对某一共享变量进行修改时,一般会进行以下的过程:
1.从主内存中拷贝变量的一份副本,并装载到工做内存中;
2.在工做内存中执行代码,修改副本的值;
3.用工做内存中的副本值更新主存中的相关变量值。
所谓“线程安全”,即多个线程同时执行同一段代码时,不会出现不肯定的或者与单线程条件下不一致的结果。一般,下列三种条件居其一的并发访问被JVM认为是线程安全的:
有final关键字修饰且已被赋值;有volatile关键字修饰;有锁保护(synchronized、ReentrantLock等)。