JAVA内存模型与JVM内存结构

注意区分Java内存模型(Java Memory Model,简称JMM)与Jvm内存结构,前者与多线程相关,后者与JVM内部存储相关。本文会对两者进行简单介绍。

一、JAVA内存模型(JMM)

1. 概念

说来话长,由于在不同硬件厂商和不同操作系统之间内存访问有一定差异,所以会使得相同代码在不同平台上运行结果可能不一致。为了使java程序在各种平台下达成一致的运行效果,所以JMM屏蔽掉各种硬件和操作系统的内存访问差异。
JMM规定除局部变和方法参数以外的所有变量都存储在主内存中。从线程角度,其基本工作方式是:工作内存保存了线程用到的变量和主内存的副本,只能修改工作内存的值然后刷回主存,不能直接读写主内存中的变量。

一般问到Java内存模型都是想问多线程,Java并发相关的问题。

2. 内存屏障

现代计算机CPU多为多核,每核有自己的高速缓存,易导致内存数据读写不一致,产生指令乱序和不可见性问题。内存屏障确保指令顺序执行和内存操作的全局可见性,防止重排序,并即时更新和展示内存数据给其他CPU核,解决读写延迟问题。读屏障清除缓存,确保后续读取最新数据;写屏障刷新缓存数据到内存,使其对其他核可见。JMM针对读load写store提出了针对这两个操作的四种组合来覆盖度读写的所有情况。
LoadLoad 屏障:确保所有之前的读操作都完成后再执行之后的读操作。
StoreStore 屏障:确保所有之前的写操作都完成后再执行之后的写操作。
LoadStore 屏障:确保所有之前的读操作都完成后再执行之后的写操作。
StoreLoad 屏障:确保所有之前的写操作都完成并对其他处理器可见后,再执行之后的读操作。

3.原子性 可见性 有序性

3.1原子性

原子性指的是一个操作是不可分割,不可中断的,一个线程在执行时不会被其他线程干扰。i++不是原子操作,因为它是先读取到i,再加1,是两步操作不保证原子性。代表性的是synchronized关键字,该关键字修饰的方法或代码块可保证原子性。

3.2 可见性

可见性是指一个线程修改了某个变量的值,这个改动能立即被其他线程感知。volatile关键字可以保证变量的可见性,当变量被该关键字修饰时,这个变量的改动会被立即刷新到内存,其他线程会在主内存中读取该变量的新值。final和synchronized也可保证可见性。
<happens-before>
happens-before是指前一个操作的结果对后续操作是可见的,并不是指前面一个操作一定发生在后面一个操作的前面。在不改变程序执行结果的前提下,编译器和处理器可以自由优化程序执行顺序,因为程序员只关心程序执行的语义是否正确。

3.3 有序性

在Java中,volatile和synchronized都能维护多线程操作的有序性。volatile通过内存屏障禁止指令重排,而synchronized则通过锁定机制,确保同一时间只有一个线程可以执行被其保护的代码块,从而实现有序性。

4. synchronezid volatile关键字

4.1 synchronezid 
4.1.1 基本使用

synchronezid可以修饰方法、类和代码块。修饰实例方法锁住的是对象,即对象锁;修饰静态方法锁住的是类,即类锁;修饰代码块,指定加锁对象,对给定对象加锁,也是对象锁。
对象锁可以有多个,new几个对象就有几个对象锁,但是类锁只有一把。

//修饰方法
public synchronized void add(){i++;
}
//修饰类
public static synchronized void add(){i++;
}
//修饰代码块
public void add() {synchronized (this) {i++;}
}
4.1.2 底层原理

查看上面代码的字节码

//修饰代码块
public void add();Code:0: aload_01: dup2: astore_13: monitorenter    // synchronized关键字的入口4: getstatic     #2                  // Field i:I7: iconst_18: iadd9: putstatic     #2                  // Field i:I12: aload_113: monitorexit  // synchronized关键字的出口14: goto          2217: astore_218: aload_119: monitorexit // synchronized关键字的出口20: aload_221: athrow22: return

通过字节码文件看出synchronized修饰代码块使用monitorenter和monitorexit指令。monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,设置计数器值为1。执行monitorexit指令,将释放 monitor(锁)并设置计数器值为0。monitor存储于对象头信息中,每个对象都存在一个monitor与之关联。

//修饰方法
public synchronized void add();descriptor: ()Vflags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 5: 0line 6: 10

synchronized修饰实例方法对应的字节码没有 monitorenter和monitorexit ,却额外多了 ACC_SYNCHRONIZED。因为整个方法都是同步代码,因此就不需要标记同步代码的入口和出口了。当线程线程执行到这个方法时会判断是否有这个ACC_SYNCHRONIZED标志,如果有的话则会尝试获取monitor对象锁。如果有异常发生,线程自动释放锁。

4.2 volatile

能保证变量的可见性,禁止指令重排序。
可见性原理
每个线程都有一个Jvm栈,栈内保存线程运行时的变量信息。当线程访问对象的属性时,首先会找到堆内对象存的变量值,再将其保存为栈内的一个副本,之后会直接修改副本中属性的值。修改完后不会立即将修改的值更新到堆中,这就导致某些线程读取到的还是旧值。volatile就是当副本中属性的值被修改后保证其能立即同步到堆中,从而其他线程读取到该值,也是新的值。


禁止指令重排序原理
通过插入内存屏障禁止指令重排序。插入内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。volatile写操作的前面插入一个StoreStore屏障,后面插入一个SotreLoad屏障。


<volatile不能保证线程安全,可见性不能保证原子操作>
 

二、JVM内存结构

1. 组成

JVM的内存划分为5部分,Java栈,本地方法栈,堆,程序计数器和方法区。
1-JAVA栈 即虚拟机栈
根据线程创建而创建,所以每个线程都有一个虚拟机栈。虚拟机栈存储的是栈帧,每个栈帧对应一个方法,且都有自己的局部变量表,操作数栈、动态链接和返回地址等。
局部变量表存放了编译器可知的各种基本数据类型(int、short、byte、char、double、float、long、boolean)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一跳字节码指令的地址)。
JVM规范中,Java虚拟机栈部分规定了两种异常:StackOverflowError发生在递归调用过深时,由于程序设计的错误,如递归无终止条件;OutOfMemoryError发生在JVM内存不足或设置过小,导致无法为新线程分配栈空间。
2-本地方法栈
java虚拟机栈为虚拟机执行Java方法服务。本地方法栈则为虚拟机使用的native方法服务。native方法是用C语言实现的底层方法。
3-堆
生命周期与进程相同,被所有线程所共享的内存区域。该区域存放的是对象实例。堆同时也是GC的主要区域。通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间;堆的内存空间既可以固定大小,也可运行时动态地调整,通过参数-Xms设定初始值、-Xmx设定最大值。
4-程序计数器
它是一块极小的内存空间。记录了当前线程执行到的字节码行号。每个线程都有自己的程序计数器,互不影响。native方法计数器为空。
5-方法区
被线程共享,储存已被虚拟机加载的类信息、常量、静态变量、jit编译后的代码等数据。

Java源代码编译成Java Class文件后通过类加载器ClassLoader加载到JVM中
类存放在方法区
类创建的对象存放在
堆中对象的调用方法时会使用到虚拟机栈,本地方法栈,程序计数器
方法执行时每行代码由解释器逐行执行
热点代码由JIT编译器即时编译
垃圾回收机制回收堆中资源
和操作系统打交道需要调用本地方法接口

2. 类加载过程

2.1 加载

加载指的是将类的class文件读入到内存中,并为之创建一个java.lang.Class对象。 类加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以使用用户自定义的类加载器(继承ClassLoader)完成。

2.2 连接

2.2.1 验证

验证被加载的类文件符合JVM规范,保证载入的类不会危害JVM。

文件格式验证→元数据验证→字节码验证→符号引用验证

2.2.1.1 文件格式验证

2.2.1.2 元数据验证

2.2.1.3 字节码验证

2.2.1.4 符号引用验证

2.2.2 准备

在方法区中为类变量(被static修饰的变量)分配内存,并将其初始化为默认值。

对于 public static int value = 123;变量value在准备阶段过后的初始值为0而不是123,初始化时才会将value值赋为123。 如果类字段的字段属性表中存在ConnstantValue属性,那在准备阶段value就会被初始化为ConstantValue属性所指定的值,如:public static final int value = 123;编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。

2.2.3 解析

将类中的符号引用转化为直接引用。编译的时候每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替。符号引用以一组符号来描述所引用的目标。直接引用可以是直接指向目标的指针。

2.3 初始化

执行类的初始化方法(<clinit>()方法)来初始化类的静态变量(程序设置值)和执行静态代码块。

2.4 使用

2.5 卸载

3. 类加载机制

1、全盘负责 类加载器加载某个类时,该类所依赖和引用其它的类也由该类加载器载入。

2、双亲委派 先让父加载器加载该类,父加载器无法加载时才考虑自己加载。 如果父加载器还存在其父加载器,则进一步向上委托,如果父类加载器可以完成父加载任务,就成功返回,如果父加载器无法完成加载任务,子加载器才会尝试自己去加载,可避免重复加载。

3、缓存机制 缓存机制保证所有加载过的class都会被缓存,当程序中需要某个类时,先从缓存区中搜索,如果不存在,才会读取该类对应的二进制数据,并将其转换成class对象,存入缓存区中。 这就是为什么修改了class后,必须重启JVM,程序所做的修改才会生效的原因。

4. 反射

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。

4.1 实例化方式

Date date=new Date();
//方式1
Class<?> date =Class.forName("java.util.Date");
//方式2
System.out.println(date.getClass());
//方式3   
System.out.println(Date.class);

4.2 实例化对象

//通过反射机制,获取Class,通过Class来实例化对象
Class<?>  cl=Class.forName("java.util.Date");
//newInstance() 这个方法会调用Date这个类的无参数构造方法,完成对象的创建。
// 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
Object object=cl.newInstance();

5.GC

(待施工)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/719128.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

No matching version found for @babel/traverse@^7.24.0.

问题&#xff1a; npm安装 依赖失败&#xff0c;找不到所需依赖。 原因&#xff1a; npm镜像源中没有该依赖。&#xff08;大概率是因为依赖最近刚更新&#xff0c;当前镜像源没有同步&#xff09; 解决&#xff1a; 查看自己的npm镜像&#xff1a;npm config get registry…

机器学习-面经(part2)

3. 验证方式 3.1什么是过拟合?产生过拟合原因? 定义:指模型在训练集上的效果很好,在测试集上的预测效果很差 数据有噪声 训练数据不足,有限的训练数据 训练模型过度导致模型非常复杂3.2 如何避免过拟合问题? 3.3 什么是机器学习的欠拟合?产生原…

D4890可应用在对讲机上,采用 SOP8/MSOP8两种封装形式

D4890 目前客户主要使用在对讲机上&#xff0c;电压范围2.2V &#xff5e; 5.5V之间&#xff0c;输出功率&#xff08;THDN1%&#xff09;1.0W/8Ω 5.0V。采用 SOP8/MSOP8两种封装形式。 2、推荐的应用线路图如下&#xff1a; 3、实际测试输出波形如下&#xff08;VCC4.5V&…

解决android studio build Output中文乱码

1.效果如下所示&#xff1a; 代码运行报错的时候&#xff0c;Build Output报的错误日志中中文部分出现乱码&#xff0c;导致看不到到底报的什么错。 2.解决办法如下&#xff1a; 点击Android studio开发工具栏的Help-Edit Custom VM Options....&#xff0c;Android studio会…

AutoGPT实现原理

AutoGPT是一种利用GPT-4模型的自动化任务处理系统&#xff0c;其主要特点包括任务分配、多模型协作、互联网访问和文件读写能力以及上下文联动记忆性。其核心思想是通过零样本学习&#xff08;Zero Shot Learning&#xff09;让GPT-4理解人类设定的角色和目标&#xff0c;并通过…

端口号被占用时的解决办法

1、查看端口占用的进程号 netstat -ano |findstr 8080 2、 找到占用端口的程序 tasklist |findstr 2264 3、kill端口 taskkill /pid 2264 /f

文物预防性保护方案整体结构及软件介绍

​文物预防性保护监测与调控系统整体是构架在商业级技术平台上的多层综合性应用,采用分布式部署的模块化设计,以智能监测终端及高精传感器为核心的感知系统。系统通过以下的层次结构协同工作完成全面的监控与调控功能&#xff1a; 1)系统依靠文物监测调控模型作为运行核心&…

基于springboot+vue的校园爱心捐赠互助管理系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 三、库表设计 四、论文 前言 随着经济水平和生活水平的提高在校大学生在校需要处理的物品也在不断增加&#xff0c;同时校园内还存在很多贫困生&#xff0c;可以通过线上平台实现资源的整合和二次利用&#xff0c;通过线上平台求助信…

护眼灯有效果吗怎么样?推荐五款值得入手的护眼台灯

随着护眼台灯被越来越多的人解锁新的护眼攻略&#xff0c;它的产品热度也越来越高&#xff0c;而且光线柔和&#xff0c;是一款非常不错的照明用具。但是也有不少用户反馈买到的护眼台灯效果不好&#xff0c;有时候还会觉得刺眼&#xff0c;有些不合格的台灯使用时间一久还会散…

动态IP代理技术在网络爬虫中的实际使用

目录 一、动态IP代理技术概述 二、动态IP代理技术的优势 三、动态IP代理技术的实际应用 四、注意事项 五、案例分析 六、结论 随着互联网的迅猛发展&#xff0c;网络爬虫成为了获取信息、分析数据的重要工具。然而&#xff0c;在进行大规模爬取时&#xff0c;爬虫常常面临…

gin gorm学习笔记

代码仓库 https://gitee.com/zhupeng911/go-advanced.git https://gitee.com/zhupeng911/go-project.git 1. gin介绍 Gin 是使用纯 Golang 语言实现的 HTTP Web框架&#xff0c;Gin接口设计简洁&#xff0c;提供类似Martini的API&#xff0c;性能极高&#xff0c;现在被广泛使用…

指针习题二

使用函数指针实现转移表 #include <stdio.h> int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } int main() {int x, y;int input 1;int ret 0;int(*p[…

学习python时一些笔记

1、winr 命令提示符的快捷键 输入cmd进入终端 2、在终端运行桌面上的python文件 cd desktop(桌面) cd是进入该文件夹的意思。 cd .. 回到上一级 运行python时一定要找到文件的所在地 输入python进入&#xff0c;exit()退出%s字符串占位符%d数字占位符%f浮点数占位符input输…

Linux速览(1)——基础指令篇

在上一章对Linux有了一些基础了解之后&#xff0c;本章我们来学习一下Linux系统下一些基本操作的常用的基础指令。 目录 1. ls 指令 2. pwd&&whoami命令 3. cd 指令 4. touch指令 5.mkdir指令&#xff08;重要&#xff09;&#xff1a; 6.rmdir指令 && …

带大家做一个,易上手的水煮牛肉

今天带大家做川菜系中的 水煮牛肉 这个菜是比较费辣椒的 制作成本相对一般菜来说 会高一些 一块牛肉 泡水划冰 从超时买的干腐竹 切成小片 温水浸泡五分钟 泡软它 然后捞出来 去干水分 牛肉切片 尽量切薄一点 三瓣左右蒜 一块生姜 去皮切末 牛肉中下入 一个鸡蛋 小半勺…

装修必看干货|入户玄关设计进门就是客厅应该怎么设计?福州中宅装饰,福州装修

入户玄关设计在进门就是客厅的情况下&#xff0c;想要拥有单独的玄关空间&#xff0c;以下是五点设计建议&#xff1a; ①隔断屏风 使用隔断屏风是传统而常见的一种空间分割方法。可以选用木制、金属或玻璃等材质的屏风&#xff0c;根据需要进行灵活搭配和定制。 屏风的款式和…

Python爬虫——Urllib库-1

这几天都在为了蓝桥杯做准备&#xff0c;一直在刷算法题&#xff0c;确实刷算法题的过程是及其的枯燥且枯燥的。于是我还是决定给自己找点成就感出来&#xff0c;那么Python的爬虫就这样开始学习了。 注&#xff1a;文章源于观看尚硅谷爬虫视频后笔记 目录 Urllib库 基本使…

【C++】字符串 1478 - 出现次数最多的小写字母 1475 - 字符串对比 1098 - 判断是否构成回文 1102 - 字符串中的空格移位

文章目录 问题一&#xff1a;1478 - 出现次数最多的小写字母问题二&#xff1a;1475 - 字符串对比问题三&#xff1a;1098 - 判断是否构成回文问题四&#xff1a;1102 - 字符串中的空格移位五、感谢 问题一&#xff1a;1478 - 出现次数最多的小写字母 类型&#xff1a;字符串 …

什么是杠杆?WeTrade众汇这样举例,大家都明白

杠杆是投资交易者一定要知道的一个金融术语。那么什么是杠杆呢?下面WeTrade众汇就用苹果进行举例&#xff0c;大家就都会明白&#xff0c;原来如此简单。 发挥我们投资者的想象&#xff0c;我们现在要进行一场苹果的买卖&#xff0c;能够赚钱的本质就是高买低卖&#xff0c;所…

【在巴厘岛学点印尼语】日常篇

BINTANG BIR 槟棠啤酒 今天不写代码&#xff0c;在巴厘岛休养&#xff0c;顺便聊点印尼语。 印尼语&#xff0c;Bahasa Indonesia&#xff0c;是印度尼西亚的官方语言&#xff0c;也即印尼化的马来语廖内方言&#xff0c;其变种包括 爪哇语&#xff08;岛民方言&#xff09; 等…