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
(待施工)
相关文章:
JAVA内存模型与JVM内存结构
注意区分Java内存模型(Java Memory Model,简称JMM)与Jvm内存结构,前者与多线程相关,后者与JVM内部存储相关。本文会对两者进行简单介绍。 一、JAVA内存模型(JMM) 1. 概念 说来话长,由于在不同硬件厂商和…...
双导师的中国社科院与英国斯特灵大学创新与领导力博士
自1978年恢复高考之后,很长一段时间里我国的高校系统处于人才很匮乏的状态,那个时候很多高校招聘了大量硕士学历教师(其中很多人在留校后又读了在职博士),而且都是事业编制。那么接下来小编与中国社科院与英国斯特灵大…...
OpenXR 超详细的spec--API初始化介绍
3.API 初始化 3.1 Exported Functions 实现API loader的动态链接库(so/dll)必须export all core OpenXR API functions。然而application可以通过使用xrGetInstanceProcAddr()来获取指向extension函数的指针。 3.2 Function Pointers OpenXR所有函数的指针都可以通过函数xr…...
认识通讯协议——TCP/IP、UDP协议的区别,HTTP通讯协议的理解
目录 引出认识通讯协议1、TCP/IP协议,UDP协议的区别2、HTTP通讯协议的讲解 Redis冲冲冲——缓存三兄弟:缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 认识通讯协议——TCP/IP、UDP协议的区别,HTTP通讯协议的理解 认识通讯协议 …...
谈一谈工作中的前后端功能开发范围
在BS开发中,往往都是团队开发,分为前端和后端,往往经常会遇到此处功能是前端进行功能开发还是后端进行功能开发的讨论,本文以我自己的观点进行论述。 笔者的观点是: 功能实现的优先性:您强调,无…...
Kubernetes 学习总结(46)—— Pod 不停重启问题分析与解决
我们在做性能测试的时候,往往会发现我们的pod服务,频繁重启,通过kubectl get pods 命令,我们来逐步定位问题。 现象:running的pod,短时间内重启次数太多。 定位问题方法:查看pod日志 kubectl get event …...
Vulnhub靶机:Bellatrix
一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.4) 靶机:Bellatrix(10.0.2.9) 目标:获取靶机root权限和flag 靶机下载地址:https://www.vulnhub.com/entry/hogwa…...
深入探讨 AutoGPT:彻底改变游戏的自主 AI
原文地址:Deep Dive into AutoGPT: The Autonomous AI Revolutionizing the Game 2023 年 4 月 24 日 AutoGPT 是一个功能强大的工具,它通过 API 使用 GPT-4 和 GPT-3.5,通过将项目分解为子任务并在自动循环中使用互联网和其他工具来创建完…...
Java Web之网页开发基础复习
tomcat之网页开发基础复习 **声明** :HTML标准规范 </!doctype> <html> : 根标签 <head>: 头部标签 内含<title><meta><link><style> <body>: 主体 <body></body> html标签 单标签: <标签名 \> 双标…...
华容道问题求解第一部分_详细设计(一)之棋子和游戏类_初始化部分
按:因为自控力和能力的原因,这个其实是在和代码同时进行的。 主要 类 说明 这一层是整个项目的基础,将对未来的算法的效率产生重要影响。为了和界面隔离,以及自身逻辑的清晰,下面的两个类是必须的,棋子类…...
【框架】Spring 框架重点解析
Spring 框架重点解析 1. Spring 框架中的单例 bean 是线程安全的吗? 不是线程安全的 Spring 框架中有一个 Scope 注解,默认的值是 singleton,即单例的;因为一般在 Spring 的 bean 对象都是无状态的(在生命周期中不被…...
js中的内存泄漏
理解 内存泄漏是计算机,中由于疏忽或者错误造成程序未能释放已经不在使用的内存,知道浏览器结束 垃圾回收机制 js具有自动的垃圾回收机制,垃圾收集器会定期(周期性)的找出那些不在继续使用的变量,然后释放内存 常见的内存泄漏 意外的全局变量 function foo(){bar 123123…...
营业执照年报申报
姿势:营业执照年报申报 借鉴文章:个体工商户年报申报流程(不要再花冤枉钱) 1、国家企业信用信息公示系统 地址:https://www.gsxt.gov.cn/index.html 2、登录(重庆的方式二简单)...
Springboot教程(五)——单元测试
idea中一般使用JUnit进行单元测试 基本使用 我们可以在idea的test文件夹下的XXXXApplicationTests内进行单元测试: 可以在Test标注的方法上写测试代码: SpringBootTest class C0101ApplicationTests {Testfun contextLoads() {println("Hello …...
【Kotlin】函数
1 常规函数 1.1 无参函数 fun main() {myFun() }fun myFun() {println("myFun") // 打印: myFun } 1.2 有参函数 1)常规调用 fun main() {myFun("myFun") // 打印: myFun }fun myFun(str: String) {println(str) } 2)形参指定默…...
Unity生命周期函数解析
本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com Unity生命周期函数解析 Unity 生命周期函数是在游戏对象的不同阶段被调用的方法,通过这些函数,我们可以在不同的时刻执行特定的代码。在这篇文章中,我们将一步步解析 Unit…...
【Qt】QTextEdit/QPlainTextEdit 实现 Tab 键多行缩进与反缩进
【Qt】QTextEdit/QPlainTextEdit 实现 Tab 键多行缩进与反缩进 文章目录 I - 主要原理II - 代码实现2.1 - 自定义类2.2 - 实现 Tab 缩进2.3 - 实现反缩进 III - 参考链接 I - 主要原理 由于 QTextEdit 和 QPlainTextEdit ,都无法实现多行选中缩进与反缩进ÿ…...
C++缺陷与思考
数组隐式转换为指针 size_t func(int a[10]) {return sizeof(a); }int a[100]; func(a); // 指针大小 sizeof(a); // 数组大小函数的参数看似是一个数组形式,但事实上他已经退化为指针了,也就是等价于size_t func(int* a),而数组作为参数传…...
无公网ip环境使用DS file软件远程访问内网群晖NAS中储存的文件
文章目录 1. 群晖安装Cpolar2. 创建TCP公网地址3. 远程访问群晖文件4. 固定TCP公网地址5. 固定TCP地址连接 DS file 是一个由群晖公司开发的文件管理应用程序,主要用于浏览、访问和管理存储在群晖NAS(网络附加存储)中的文件。这个应用程序具有…...
软件工程基础
本博客地址:https://security.blog.csdn.net/article/details/136446772 一. 软件工程 1、软件危机。具体表现为:软件开发进度难以预测、软件开发成本难以控制、软件功能难以满足用户期望、软件质量无法保证、软件难以维护和软件缺少适当的文档资料。 …...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
