JAVA内存模型与JVM内存结构

news2024/11/18 17:27:49

注意区分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_0
       1: dup
       2: astore_1
       3: monitorenter    // synchronized关键字的入口
       4: getstatic     #2                  // Field i:I
       7: iconst_1
       8: iadd
       9: putstatic     #2                  // Field i:I
      12: aload_1
      13: monitorexit  // synchronized关键字的出口
      14: goto          22
      17: astore_2
      18: aload_1
      19: monitorexit // synchronized关键字的出口
      20: aload_2
      21: athrow
      22: return

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

//修饰方法
public synchronized void add();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 5: 0
        line 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.coloradmin.cn/o/1488312.html

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

相关文章

Java多线程——如何控制线程顺序执行,如何控制线程同时执行

目录 引出如何控制线程执行顺序&#xff1f;多个线程在某一时刻同时开始执行&#xff1f; Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Java多线程——如何控制线程顺序执行&#xff0c;如何控制线程同时执行 如何控制线程…

JS正则03——js正则的贪婪匹配模式与非贪婪匹配模式➕正向预测匹配和负向预测匹配的详细使用及例子

JS正则03——js正则的贪婪匹配模式与非贪婪匹配模式➕正向预测匹配和负向预测匹配的详细使用及例子 1. 前言1.1 js正则的基本使用 2. 贪婪匹配模式 与 非贪婪匹配模式2.1 引例2.2 贪婪模式2.3 非贪婪模式2.4 原理——参考 3. 预测匹配方式3.1 正向预测匹配3.2 负向预测匹配3.2.…

试手一下CameraX(APP)

书接上回。 首先还是看谷歌的官方文档&#xff1a; https://developer.android.com/media/camera/camerax?hlzh-cn https://developer.android.com/codelabs/camerax-getting-started?hlzh-cn#1 注&#xff1a;这里大部分内容也来自谷歌文档。 官方文档用的是Kotlin&…

Window下编写的sh文件在Linux/Docker中无法使用

Window下编写的sh文件在Linux/Docker中无法使用 一、sh文件目的1.1 初始状态1.2 目的 二、过程与异常2.1 首先获取标准ubuntu20.04 - 正常2.2 启动ubuntu20.04容器 - 正常2.3 执行windows下写的preInstall文件 - 报错 三、检查和处理3.1 评估异常3.2 处理异常3.3 调整后运行测试…

不知道RAID/SAN/NAS的小可爱来看看这个吧!

RAID RAID&#xff08;冗余阵列的独立磁盘&#xff0c;Redundant Array of Independent Disks&#xff09;是一种将多个磁盘驱动器组合成一个或多个单元的技术&#xff0c;目的是在提高数据可靠性和/或提升性能的同时&#xff0c;对操作系统隐藏底层的复杂性。简而言之&#x…

Vue3中Vuex状态管理库学习笔记

1.什么是状态管理 在开发中&#xff0c;我们会的应用程序需要处理各种各样的数据&#xff0c;这些数据需要保存在我们应用程序的某个位置&#xff0c;对于这些数据的管理我们就称之为状态管理。 在之前我们如何管理自己的状态呢&#xff1f; 在Vue开发中&#xff0c;我们使用…

“比特币暴拉冲破6.5万美元”!超罕见指标揭示牛市信号,18万美元目标能否实现?

比特币&#xff08;BTC&#xff09;周末持续在6.2万美元反复震荡之后&#xff0c;今&#xff08;4&#xff09;晨再次强势拉涨&#xff0c;早上8&#xff1a;45左右最高触及64268美元&#xff0c;随后插针盘整&#xff0c;下午5点30分左右突破65500美元&#xff0c;再创今年新高…

第七届电路,系统与仿真国际会议(ICCSS 2024)即将召开!

2024年第七届电路&#xff0c;系统与仿真国际会议&#xff08;ICCSS 2024&#xff09;将于5月17-19日马来西亚博特拉大学举办。本届会议由马来西亚博特拉大学和中国东南大学协办&#xff0c;电机与电子工程系主办。ICCSS 2024 热烈欢迎所有来自电路、系统和仿真等相关领域的研究…

Java基于springboot的课程作业管理系统

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;课程作业管理系统当然也不能排除在外。课程作业管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法…

书生浦语全链路开源体系

推荐阅读论文 A Survey Of Large Language Models 书生浦语开源的模型 从模型到应用 书生浦语开源体系 书生万卷开源数据集 除此之外还有OpenDataLab国内数据集下载网站。 预训练框架InterLM-Train 微调框架XTuner 评测工具体系 国内外常见的大语言模型评测基准&#xff1a…

如何扫码查看图片信息?图片放到二维码展示的在线教学

现在通过扫码来查看物品图片是很常用的一种方式&#xff0c;将物品不同角度的图片存入一张二维码后&#xff0c;用户只需要扫描这张二维码图片&#xff0c;就可以了解物品预览图及其他信息。常用的图片格式比如jpg、png、gif都可以放到二维码中显示&#xff0c;那么具体该怎么做…

如何在飞书接入ChatGPT并结合内网穿透实现公网远程访问智能AI助手

文章目录 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话&#xff0c;在下面操作步骤中…

column ‘_id‘ does not exist

最近把 csv 导入 SQLite 给 CursorAdapter 使用出现了这个莫名其妙的错误。 java.lang.IllegalArgumentException: column _id does not exist-CSDN博客 查找资料才明白&#xff1a;CursorAdapter 使用的数据库中必须有 _id 这个字段。 好吧&#xff0c;导入的数据库增加 _i…

为何代理IP的稳定性不如有线IP?

代理IP与网线IP的稳定性之间存在差异的原因在于它们的工作机制和服务环境的不同。以下是代理IP不如网线IP稳定的一些主要原因&#xff1a; 1. 服务提供商的质量&#xff1a; - 动态分配&#xff1a;代理IP通常是动态分配的&#xff0c;这意味着每次请求或每隔一段时间&#xff…

2.00001《Postgresql内幕探索》走读 之 查询优化

文章目录 1.1 概述1.1.1 Parser1.1.2 分析仪/分析器1.1.3 Rewriter1.1.4 Planner和Executer 1.2 单表查询的成本估算1.2.1 顺序扫描1.2.2 索引扫描1.2.3 排序 1.3 .创建单表查询的计划树1.3.1 预处理1.3.2 获取最便宜的访问路径示例1示例二 1.3.3 创建计划树示例1例二 1.4 EXEC…

python学习27

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

Redis安装与数据类型及常用命令

一、关系数据库和非关系型数据库1、关系型数据库2、非关系型数据库3、非关系型数据库产生背景4、关系型数据库和非关系型数据库区别5、为什么非关系型数据库能提升访问速度&#xff1f; 二、Redis简介1、Redis 的优点2、使用场景3、哪些数据适合放入缓存中&#xff1f;4、Redis…

芯片ERP:应用广泛的领域及其影响

在现代科技快速发展的时代&#xff0c;芯片ERP(企业资源规划)已成为许多行业不可或缺的工具。这种集成了先进技术和先进管理理念的系统&#xff0c;极大地提高了企业的运营效率和竞争力。那么&#xff0c;芯片ERP主要应用在哪些领域呢?本文将为您一一揭晓。 一、电子制造行业 …

啤酒:精酿啤酒与烤串的夜晚滋味

夏日的夜晚&#xff0c;微风拂面&#xff0c;星光璀璨。此时&#xff0c;能抚慰人心的莫过于与三五好友围坐一起&#xff0c;享受烤串与Fendi Club啤酒的美味。这种滋味&#xff0c;不仅仅是味蕾的盛宴&#xff0c;更是心灵的满足。 Fendi Club啤酒&#xff0c;每一滴都蕴含着大…

python标识符、变量和常量

一、保留字与标识符 1.1保留字 保留字是指python中被赋予特定意义的单词&#xff0c;在开发程序时&#xff0c;不可以把这些保留字作为变量、函数、类、模块和其它对象的名称来使用。 比如&#xff1a;and、as、def、if、import、class、finally、with等 查询这些关键字的方…