欢迎光临散文网 会员登陆 & 注册

JVM内存模型和Java线程内存模型

2022-03-08 16:38 作者:房顶上的铝皮水塔  | 我要投稿

参考内容:京东架构师100分钟带你重新认识Java内存模型!让你面试无忧!

本文主要分成以下部分:

  1. 简单介绍JVM的内存模型,并且简要分析Java的Minor gc过程

  2. 介绍Java 线程内存模型,并且从汇编角度的层面了解volatile

以上的内容参考自站内视频。

JVM内存模型概述

如图所示,JVM内存模型分成堆、虚拟机、本地方法栈、方法区、程序计数器这几个部分。

首先来看看虚拟机栈

虚拟机栈

程序在执行过程中会产生线程,比如Main线程或者是其他的子线程。对于一个线程JVM就会给他分配一个虚拟机栈,同时每一个线程都有各自的程序计数器。在同一个线程中会调用方法,对于方法调用,JVM会给他分配【栈帧】。栈帧中包括四个方面:

  • 局部变量表

  • 动态链接

  • 操作数栈

  • 方法出口

局部变量表和操作数栈:

局部变量表表示创建的的名称,这些数字会被放到操作数栈中。比如上面图中的代码:

iconst_1表示将int类型的常量1放如操作数栈,然后i_store1表示将操作数栈中的1出栈,并且放到局部变量1,也就是a处。对于变量b的操作同理。在进行变量c的计算过程时,后续的逻辑会将局部变量1和2(a和b)中的结果放入操作数栈进行计算,最后结果返回。

动态链接:关联符号引用和内存地址

方法出口:和上下文切换有关

本地方法栈

本地方法栈和虚拟机栈类似,但是里面放的是本地方法的相关的变量

方法区

方法区中主要放常量,静态变量和类信息

new出来的对象。在虚拟机栈中,比如如下的代码:

user句柄是放在常量表中的,但是new 出来的对象放在堆上。同理在方法区中如果有静态变量是引用类型,new出来的对象也放在堆上。

堆的划分

堆空间主要分成以下几个部分:Eden,s0,s1,老年代。最开始new出来的对象放在Eden区,如果当Eden区存放满时,字节码执行引擎会启动一个线程做minor gc。

minor gc中使用的算法被称作可达性算法。可达性算法会从gc Root对象开始。

gc Root

这部分的对象指的是静态变量,本地方法栈引用的对象,虚拟机栈中的局部变量表引用的对象、方法区中静态属性引用的对象、常量引用的对象。

可达性算法

算法首先从gc Root出发,从这些节点向下搜索,走过的过程就是引用链。如果一个对象没有任何引用链和它相连,那么这个对象就是不可达的。对于不可达的对象存留在Eden区,可达的对象会在s0和s1 survivor区转移。如果这些对象的对象头中的分代年龄字段大于15,将会被放在老年区。

Java线程模型

java线程模型分成两个部分,一个是共享内存(主内存),一个是线程中的独立的内存。这部分的内存线程之间不能相互访问。如果同时有两个线程,从主内存中复制了一个对象并且进行修改,修改完成的结果不会立刻存回主内存,这就是并发中常出现的写错误的问题。

为了解决这个问题,我们通常会在变量之间加上volatile关键字。

volatile关键字的实现

volatile主要是解决的线程之间的共享变量的可见性的问题,它解决的思路是基于JVM定义的一系列硬件层面的原子操作:

举两个线程为例,在没有加volatile之前:

左边的线程只是对于一个变量执行了一个读取的过程,右边的线程除了读取还做了修改的操作。可以看到从工作内存到主内存需要经历store操作,然后修改值是经历write操作。正是因为这些操作不是原子操作,才会出现错误。

volatile关键字早期的实现方式是对于总线加锁的机制,操作一个共享变量的时候,其他的线程都不能对缓存了这个共享变量的地址的缓存。但是这样的开销比较大。

现在的volatile关键字是基于MESI协议的。volatile关键字在被修改时,会立刻会写到主内存,同时开启MESI协议,CPU会开启总线嗅探机制,使得自身的变量失效。

JVM内存模型和Java线程内存模型的评论 (共 条)

分享到微博请遵守国家法律