深入理解JVM线程模型
1、 jvm内存模型
在描述jvm线程模型之前,我们先深入的理解下,jvm内存模型。在jvm1.8之前,jvm的逻辑结构和物理结构是对应的。即Jvm在初始化的时候,会为堆(heap),栈(stack),元数据区(matespace)分配指定的内存大小,Jvm线程启动的时候会向服务器申请指定的内存地址空间进行分配。在jdk1.8之后,使用了G1垃圾回收器,逻辑上依然存在堆,栈,元数据区。但是在物理结构上,G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
1.1、 jvm内存模型介绍
1.2、 Jvm堆
Java堆是和Java应用程序最密切的内存空间,几乎所有的对象都放到堆中。并且堆完全由Jvm管理,通过垃圾回收机制,垃圾对象会被自动清理,而不需显式的释放。
根据垃圾回收机制的不同,Java堆通常被分为以下的集中不同的结构。
构成 | 描述 |
New Generation | 由 Eden + Survivor (From Space + To Space)组成 |
Eden | 所以的new出来的新对象都存放到Eden区 |
Survivor Space | Eden每次垃圾清理过后,任然没又被清理的对象,会转移到交换区中 |
Old Generation | 在交换区中未被清理的对象(默认清理18次标记),将转移到老年代。 |
1.3、 Jvm栈
Java栈是一块线程私有的内存空间,Java栈和线程执行密切相关。线程的执行基本单位就是函数调用,每次函数调用的数据就会通过Java栈传递。
Java栈与数据结构上的栈有着类似的含义,它是一块先进后出的数据结构,只支持出栈和入栈的两种操作。在Java栈中保存的主要内容为栈帧。每次调用一个函数,都会有一个对应的栈帧被压入Java栈。每一个函数调用结束,都会有一个栈帧被弹出Java栈。例如:
如图所示,每次调用一个函数都会被当做栈帧压入到栈中。其中每一个栈帧对应一个函数。由于每次调用函数都会生成一个栈帧,从而占用一定的栈空间。如果线程中存在大量的递归操作,会频繁的压栈,导致栈的深入过于深入,当栈的空间被消耗殆尽的时候,会抛出StackOverflowError栈溢出错误。
当函数执行结束返回时,栈帧从Java栈中被弹出。Java方法有两种返回的方式,一种是正常函数返回,即使用 return; 另外一种是抛出异常。不管哪种方式,都会导致栈帧被弹出。
1.3.1、 局部变量表
局部变量表示栈帧的重要组成部分之一。它用于保存函数已经局部变量。局部变量表中的变量只有在当前的函数中调用有效,当调用函数结束以后,随着函数栈帧的销毁,局部变量表也随之销毁。
1.3.2、 操作数栈
操作数栈也是栈帧中重要的内容之一,它主要保存计算过程中的结果,同事作为计算过程临时变量的存储空间。
操作数栈也是一个先进后出的数据结构,只支持入栈和出栈的两种操作,Java的很多字节码指令都是通过操作数栈进行参数传递的。比如iadd指令,它就会在操作数栈中弹出两个整数进行加法计算,计算结果会被入栈。入下图所示:
1.3.3、 帧数据区
每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接
1.4、 Jvm方法区(jdk1.8元数据区)
它主要存放一些虚拟机加载的类信息,常量,静态变量,即使编译器后的代码等数据。根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
1.4.1、 运行时常量池
运行时常量区是方法区的一部分。用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。还会有一些符号引用转换的直接引用一保存在运行时常量池中。
运行时常量池具备动态性,也就是运行期间也可以将新的常量放入池中,例如String.intern()方法。当常量池无法再申请到内存时,会抛出OutOfMemoryError异常
2、 jvm线程模型
通过Jvm内存模型,我们可以发现,Jvm其实就是操作系统的一种镜像。是软件层次的虚拟机。其中我们队Jvm内存模型分析可知:堆,方法区是线程共有的;栈是每个线程私有的。
讨论Java内存模型和线程之前,先简单介绍一下硬件的效率与一致性
由于计算机的存储设备与处理器的运算能力之间有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中没这样处理器就无需等待缓慢的内存读写了。
基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享同一主存,如下图所示:多个处理器运算任务都涉及同一块主存,需要一种协议可以保障数据的一致性,这类协议有MSI、MESI、MOSI及Dragon Protocol等。Java虚拟机内存模型中定义的内存访问操作与硬件的缓存访问操作是具有可比性的,后续将介绍Java内存模型。
除此之外,为了使得处理器内部的运算单元能竟可能被充分利用,处理器可能会对输入代码进行乱起执行(Out-Of-Order Execution)优化,处理器会在计算之后将对乱序执行的代码进行结果重组,保证结果准确性。与处理器的乱序执行优化类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Recorder)优化。
2.1、 Java内存模型
定义Java内存模型并不是一件容易的事情,这个模型必须定义得足够严谨,才能让Java的并发操作不会产生歧义;但是,也必须得足够宽松,使得虚拟机的实现能有足够的自由空间去利用硬件的各种特性(寄存器、高速缓存等)来获取更好的执行速度。经过长时间的验证和修补,在JDK1.5发布后,Java内存模型就已经成熟和完善起来了。
2.1.1、 主内存与工作内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似
2.1.2、 java内存变量的交互操作
主内存变量和工作内存变量之间的交互过程如下:
3、 线程安全
通过上面分析,可以发现,每个线程都拥有自己的工作内存,工作内存是线程私有的。所以每个线程对堆中的共享变量进行修改对其他的线程而言是不可见的。
Java内存模型中,程序(进程)拥有一块内存空间,可以被所有的线程共享,即MainMemory(主内存:堆);而每个线程又有一块独立的内存空间,即WorkingMemory(工作内存:栈)。普通情况下,当线程需要对某一共享变量进行修改时,通常会进行如下的过程:
1.从主内存中拷贝变量的一份副本,并装载到工作内存中;
2.在工作内存中执行代码,修改副本的值;
3.用工作内存中的副本值更新主存中的相关变量值。
所谓“线程安全”,即多个线程同时执行同一段代码时,不会出现不确定的或者与单线程条件下不一致的结果。通常,下列三种条件居其一的并发访问被JVM认为是线程安全的:
有final关键字修饰且已被赋值;
有volatile关键字修饰;
有锁保护(synchronized、ReentrantLock等)。
深入理解JVM线程模型的更多相关文章
- 如何从编程的本质理解JVM内存模型
如何从编程的本质理解JVM内存模型 一般聊JVM内存模型都是把图截出来,然后对着图,解释上面堆.栈之类的概念.这篇将分享下,如何从编程的本质上理解,JVM内存模型是什么样子,为什么是这个样子,不再死记 ...
- 理解RxJava线程模型
RxJava作为目前一款超火的框架,它便捷的线程切换一直被人们津津乐道,本文从源码的角度,来对RxJava的线程模型做一次深入理解.(注:本文的多处代码都并非原本的RxJava的源码,而是用来说明逻辑 ...
- java 深入理解jvm内存模型 jvm学习笔记
jvm内存模型 这是java堆和方法区内存模型 参考:https://www.cnblogs.com/honey01/p/9475726.html Java 中的堆也是 GC 收集垃圾的主要区域.GC ...
- 深入理解JVM - 线程安全与锁优化 - 第十三章
线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对 ...
- 深入理解JVM内存模型
1.程序计数器在虚拟机的概念模型里字节码解释器工作时就是通过改变 这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理. Java 虚拟机的多线程是通过线程轮流切换并分配处理器执 ...
- quartz源码分析——执行引擎和线程模型
title: quartz源码分析--执行引擎和线程模型 date: 2017-09-09 23:14:48 categories: quartz tags: [quartz, 源码分析] --- - ...
- 深入理解JVM - Java内存模型与线程 - 第十二章
Java内存模型 主内存与工作内存 Java内存模型主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量(Variable)与Java编程中 ...
- 深入理解JVM(二)——内存模型、可见性、指令重排序
上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...
- 深入理解JVM(一)——JVM内存模型
JVM内存模型 Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区. ...
随机推荐
- leetcode 115不同的子序列
滚动数组: /***** 下标从1开始 dp[i][j]:= numbers of subseq of S[1:j] equals T[1:i] if(s[j]==t[i]):(那么之后的子串可以是是 ...
- influxDB使用小结
在集群中安装influxdb influxdb提供了官方镜像,因此在集群中安装influxdb十分方便,只需要指定镜像名为influxdb即可自动下载运行,只需要配置环境变量就可以进行初始化设置 以下 ...
- <table>表格与jqGrid
第一次写博客比较生涩.接下来进入正题:...... 普通表格前端的增删改查. <%@ page language="java" contentType="text/ ...
- fiddler配置会话框菜单栏
1.添加会话框菜单:鼠标移至会话框菜单的左上角“#”位置,右键>Customize column,Collection下拉菜单选择"Miscellaneous",Field ...
- 使用Python创建AI比你想象的轻松
使用 Python 创建 AI 比你想象的轻松 可能对AI领域,主要开发阶段,成就,结果和产品使用感兴趣.有数百个免费源和教程描述使用Python的AI.但是,没有必要浪费你的时间看他们.这里是一个详 ...
- Vmware centos 虚拟机 磁盘扩容
一,lvm ext4 扩容 1,首先关闭虚拟机,在vSphere Client 将硬盘大小增加或者新增一块硬盘 (从原来10G增加到50G) 2,开机 此时,df -hT只显示原来的磁盘大小 使用 f ...
- video标签在移动端的一些属性值设置
<video x5-video-orientation="portraint" src="" loop x-webkit-airplay="al ...
- 关于liunx 机器脱机环境(netcore)Nuget包迁移的问题
首先nuget脱机环境是没办法加载第三方nuget包的,我这里的做法是使用nuget缓存文件(正确的做法还是推荐使用自己搭建的nuget服务器然后正常发布,这里只是做应急之需) 我们都知道项目的dot ...
- python 并发编程 多线程 线程queue
线程queue 线程之间已经是共享数据的,为什么还使用线程queue? 线程需要自己加锁,线程queue帮我们处理好加锁的问题 有三种不同的用法 第一种方法: class queue.Queue(ma ...
- CF498B Name That Tune(动态规划dp)
CF498B 动态规划f[i][j]表示前i秒时间听了j首歌的概率,则有: \(f[i][j]=∑f[i-k][j-1]*(1-p_j)^{k-1}*p_j\) k枚举i秒前的每一秒,要求前i-1秒都 ...