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_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

(待施工)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解动态规划(算法村第十九关青铜挑战)

不同路径 62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finis…

重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

大家好我是苏麟 , 今天开始又一个专栏开始了(又一个坑 哈哈) . 重载&#xff08;Overload&#xff09;和重写&#xff08;Override&#xff09;的区别。重载的方法能否根据返回类型进行区分&#xff1f; 方法的重载和重写都是实现多态的方式&#xff0c;区别在于前者实现的是编…

pyqt5怎么返回错误信息给页面(警告窗口)

在软件设计中&#xff0c;我们可能会遇到对异常的处理&#xff0c;有些异常是用户需要看到的&#xff0c;比如说&#xff0c;当我们登录出错的时候&#xff0c;后端需要给我们返回响应的错误信息&#xff0c;就像下图实现的这样。 类似这种效果&#xff0c;我们该如何实现&…

C++真题列表

题目解析&#xff1a;RAM是闪存&#xff0c;只要一关机一拔电&#xff0c;就会丢失数据 题目解答&#xff1a;A 题目解析&#xff1a;TXT格式是文本文档 题目解答&#xff1a;B 题目解析&#xff1a;IP地址中每一个字节的取值范围是[0~255]&#xff0c;是不可能有256的 题目…

2024最新算法:美洲狮优化算法(Puma Optimizar Algorithm ,POA)求解23个基准函数(提供MATLAB代码)

一、美洲狮优化算法 美洲狮优化算法&#xff08;Puma Optimizar Algorithm &#xff0c;POA&#xff09;由Benyamin Abdollahzadeh等人于2024年提出&#xff0c;其灵感来自美洲狮的智慧和生活。在该算法中&#xff0c;在探索和开发的每个阶段都提出了独特而强大的机制&#xf…

TDengine 在 DISTRIBUTECH 分享输配电数据管理实践

2 月 27-29 日&#xff0c;2024 美国国际输配电电网及公共事业展&#xff08;DISTRIBUTECH International 2024&#xff09;在美国-佛罗里达州-奥兰多国家会展中心举办。作为全球领先的年度输配电行业盛会&#xff0c;也是美洲地区首屈一指的专业展览会&#xff0c;该展会的举办…

干货!Python获取字典元素

1.访问字典中的元素 第一种方式&#xff1a;通过key访问 dict1 {"name":"中国医生", "author":"刘伟强", "person":"张涵予"} print(dict1["author"]) # 刘伟强 # print(dict1["price"…

八. 实战:CUDA-BEVFusion部署分析-分析BEVFusion中各个ONNX

目录 前言0. 简述1. camera.backbone.onnx(fp16)2. camera.backbone.onnx(int8)3. camera.vtransform.onnx(fp16)4. fuser.onnx(fp16)5. fuser.onnx(int8)6. lidar.backbone.xyz.onnx7. head.bbox.onnx(fp16)总结下载链接参考 前言 自动驾驶之心推出的《CUDA与TensorRT部署实战…

ArrayList集合源码分析

ArrayList集合源码分析 文章目录 ArrayList集合源码分析一、字段分析二、构造方法分析三、方法分析四、总结 内容如有错误或者其他需要注意的知识点&#xff0c;欢迎指正或者探讨补充&#xff0c;共同进步。 一、字段分析 //默认初始化容量。这里和Vector一样&#xff0c;只是…

Maven实战(2)之搭建maven私服

一, 背景: 如果使用国外镜像,下载速度比较慢; 如果使用阿里云镜像,速度还算OK,但是假如网速不好的时候,其实也是比较慢的; 如果没有网的情况下更加下载不了. 二, 本地仓库、个人/公司私服、远程仓库关系如下: 三, 下载安装nexus私服 略

Git 指令深入浅出【1】—— 文件管理

Git 指令深入浅出【1】—— 文件管理 一、新建仓库二、配置1. 基本指令2. 免密配置3. 简化指令 三、管理文件1. 常用文件管理指令&#xff08;1&#xff09;基本指令工作区暂存区版本库 &#xff08;2&#xff09;日志&#xff08;3&#xff09;查看修改 2. 版本回退&#xff0…

每日五道java面试题之mysql数据库篇(三)

目录&#xff1a; 第一题. 百万级别或以上的数据如何删除&#xff1f;第二题. 前缀索引第三题. 什么是最左前缀原则&#xff1f;什么是最左匹配原则?第四题. B树和B树的区别第五题. 使用B树和B树好处 第一题. 百万级别或以上的数据如何删除&#xff1f; 关于索引&#xff1a;…

奇酷网络董事长吴渔夫:以AI思维引领游戏制作,慢工出细活

文 | 大力财经 奇酷网络是一家以“AI游戏”为核心理念的创业公司&#xff0c;其独特的运营模式和理念备受瞩目。公司采用基于“AI思维”的运作方式&#xff0c;形成了与传统互联网思维鲜明对比的“超级个体公司”模式。尽管全职员工仅有两名&#xff0c;但公司CEO采取“以一打…

CPU漏洞之Spectre

一、前言 在过去的几十年里&#xff0c;一些微架构设计技术促进了处理器速度的提高。其中一个进步是推测执行(Speculative execution)&#xff0c;它被广泛用于提高性能&#xff0c;猜测CPU未来可能的执行方向&#xff0c;并提前执行这些路径上的指令。比如说&#xff0c;程序…

HarmonyOS—配置编译构建信息

在进行应用/服务的编译构建前&#xff0c;需要对工程和编译构建的Module进行设置。API Version 9、API Version 8与API Version 4~7的构建体系不同&#xff0c;因此在设置编译构建信息时也存在差异&#xff1a; API Version 9&#xff1a;需要对构建配置文件、构建脚本、应用依…

Cloud+Consul

Cloud整合Zookeeper代替Eureka-CSDN博客 Consul简介 Consul是一套开源的分布式服务发现和配置管理系统 What is Consul? | Consul | HashiCorp DeveloperConsul is a service networking solution that delivers service discovery, service mesh, and network security ca…

【C++航海王:追寻罗杰的编程之路】CC++内存管理你知道哪些?

目录 1 -> C/C内存分布 2 -> C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 3 -> C内存管理方式 3.1 -> new/delete操作内置类型 3.2 -> new和delete操作自定义类型 4 -> operator new与operator delete函数 4.1 -> operator ne…

ProxySQL实现mysql8主从同步读写分离

ProxySQL基本介绍 ProxySQL是 MySQL 的高性能、高可用性、协议感知代理。以下为结合主从复制对ProxySQL读写分离、黑白名单、路由规则等做些基本测试。 先简单介绍下ProxySQL及其功能和配置&#xff0c;主要包括&#xff1a; 最基本的读/写分离&#xff0c;且方式有多种&…

spring注解驱动系列--自动装配

Spring利用依赖注入&#xff08;DI&#xff09;&#xff0c;完成对IOC容器中中各个组件的依赖关系赋值&#xff1b;依赖注入是spring ioc的具体体现&#xff0c;主要是通过各种注解进行属性的自动注入。 一、Autowired&#xff1a;自动注入 一、注解介绍 1、默认优先按照类型去…

Geostationary statellites与polar-orbiting satellites区别

Geostationary statellitespolar-orbiting satellites周期24小时不定&#xff0c;高度决定轨道与赤道平行与赤道垂直高度赤道正上方、唯一不唯一具体计算 m v 2 R h G M m ( R h ) 2 m\frac{v^2}{Rh}G\frac{Mm}{(Rh)^2} mRhv2​G(Rh)2Mm​ m v 2 R h G M m ( R h ) 2 m\f…