当前位置: 首页 > news >正文

final修饰符不可变的底层

final修饰符的底层原理

在 Java 中,final 修饰符的底层实现涉及 编译器优化JVM 字节码层面的约束

其核心目标是保证被修饰元素的【不可变性】或 【不可重写 / 继承性】


一、final 修饰类:禁止继承的底层约束

当一个类被 final 修饰时,例如 String 、Integer

JVM 在字节码层面会通过 访问标志(access flags) 标记该类为 ACC_FINAL

  • 编译器在编译时会检查:如果子类试图继承被 final 修饰的类,会直接抛出编译错误
    【无法继承最终类】
  • JVM 在类加载阶段也会验证这一约束,确保没有非法继承行为

本质

  1. 通过字节码标记禁止继承
  2. 属于编译期和类加载期的静态约束,不涉及运行时的特殊处理

二、final 修饰方法:禁止重写的底层机制

final 修饰方法时,字节码中该方法的访问标志会被标记为 ACC_FINAL

  • 编译器在编译子类时,若发现子类试图重写被 final 修饰的父类方法,会直接报错
  • JVM 在字节码验证阶段也会检查方法重写的合法性,拒绝非法重写的类加载


private 方法的区别
隐式final:private 方法默认被隐式视为 final,但字节码中不会标记 ACC_FINAL->方法无法被重写,因为子类不可见

显式 final :方法会明确标记,且可见性可以是 public/protected


三、final 修饰变量:保证不可变的底层实现

final 修饰变量(局部变量、成员变量、静态变量)的核心是 【一旦赋值就不能被修改】
其底层实现涉及编译期约束和运行期优化
分两种情况:

1. 基本类型变量(如 final int a = 10)
  • 编译期约束:编译器会检查变量是否只被赋值一次。若在编译期能确定赋值(如直接赋值字面量),则会将其视为 【编译期常量】,并可能触发 常量折叠 优化(如将代码中所有引用 a 的地方直接替换为10)
  • 运行期保障:若变量在编译期无法确定值(如通过方法返回值赋值,final int a = getValue())
    JVM 会在字节码中通过 putfield(成员变量)或 astore(局部变量)指令赋值后,禁止后续对该变量的写操作(编译器会拦截所有二次赋值的代码,直接报错)

2. 引用类型变量(如 final List<String> list = new ArrayList<>())

final 对引用类型的约束是 【引用不可变】但对象本身的内容可以修改(如 list.add("a") 是允许的)

  • 底层通过字节码标记 ACC_FINAL 实现:编译器会检查引用变量是否被二次赋值(如list = new LinkedList<>()),若有则编译报错
  • 与基本类型不同,引用类型的 final 变量不会触发常量折叠,因为其指向的对象内容可能在运行时变化,仅保证引用本身不变

3. final 与多线程: happens-before 规则的底层支持

final 变量在多线程环境中具有特殊的内存语义:final 修饰的变量,一旦在构造方法中初始化完成,且构造方法没有 “逸出”(即 this 引用未被其他线程获取),则其他线程看到的 final 变量一定是初始化后的值,无需额外同步


这一特性的底层依赖 JVM 的内存屏障

  • 在构造方法中对 final 变量赋值后,JVM 会插入 StoreStore 屏障,禁止该赋值操作与构造方法外的操作重排序,确保 final 变量的初始化对其他线程可见
  • 其他线程读取 final 变量时,JVM 会插入 LoadLoad 屏障,禁止读取操作与之前的操作重排序,确保读取到的是初始化后的值

四、final 与 JIT 优化:常量传播与不可变分析

JIT(即时编译器)在运行时会对 final 变量进行额外优化:

  • 常量传播:若 final 变量是编译期常量(如 final int MAX = 100),JIT 会将代码中所有引用 MAX 的地方直接替换为 100,减少变量访问开销
  • 不可变分析:对于 final 引用类型(如 final String s),JIT 可以假设其引用不会变化,从而进行更激进的优化(如避免重复计算、减少锁竞争等)

总结:final 底层的核心逻辑

修饰对象

底层实现核心

典型场景

字节码标记 ACC_FINAL,禁止继承

String、Integer

等不可变类

方法

字节码标记 ACC_FINAL

,禁止重写

工具类中的固定逻辑方法(如 Objects.requireNonNull

变量

编译期禁止二次赋值 + 运行期内存屏障(多线程可见性)

常量定义、多线程共享的不可变引用

final 的底层机制本质是 通过编译期约束和运行期优化,保证 【不可变】或 【不可继承 / 重写】

同时为多线程环境提供了安全的内存语义,是 Java 中实现不可变性和线程安全的重要手段

 


final是如何保证多线程可见的

核心目标: 确保在多线程环境下,当一个对象被构造完成后,其他线程看到的该对象中被 final 修饰的字段的值,一定是构造方法中设置的那个值,不会看到未初始化的默认值(如 0, null 等)


关键前提条件:

  1. final 变量在构造方法中初始化完成
  2. 构造方法没有“逸出”(this 引用未逃逸): 在构造方法执行结束之前,对象的 this 引用没有被其他任何线程获取到。这是安全发布的基础

JVM 如何保证(底层依赖内存屏障):

为了达到这个目标,JVM 在编译和运行时会插入特定的内存屏障指令

内存屏障可以简单理解为阻止 CPU 或编译器对指令进行重排序的栅栏,确保屏障前后的指令执行顺序符合预期


  1. 写屏障:

禁止对final字段赋值操作与构造方法结束后发生的所有的对final变量的写入操作进行重排序

final字段的写入一定发生在对象【引用发布】之前,也就是保证对象构建好后外部线程才能访问

  1. 读屏障 :

禁止对读取final字段的操作与该读取操作前的任何读取操作进行重排序

强制要求读取final字段时必须先去检查最新的内存值,保证不丢失修改,保证读取的数据值最新值


Happens-Before 规则的体现:

JMM 的 final 语义建立了一个 happens-before 关系:

  • 构造方法中对 final 字段的赋值操作 happens-before 于构造方法的结束(return)
  • 由于 StoreStore 屏障的保证,构造方法的结束 happens-before 于后续任何线程通过一个正确发布的引用对该对象的 final 字段的读取操作
  • 因此,构造方法中对 final 字段的赋值 happens-before 于任何线程对该 final 字段的读取。 这就是为什么读取线程一定能看到正确初始化的值

简单来说:

JVM 通过在写 final 字段后加写屏障,在读 final 字段前加读屏障

配合构造方法不逸出的前提,巧妙地利用了内存屏障阻止了可能导致看到未初始化值的指令重排序

从而让 final 字段成为多线程环境下一种安全、无需额外同步就能保证可见性的常量发布机制

这就是 final 字段 happens-before 语义的底层实现基础


final底层-简单总结

类:字节码标记 ACC_FINAL,禁止继承。编译期和类加载期会检查约束

方法:字节码标记 ACC_FINAL,禁止重写。编辑期和字节码验证期拒绝重写

变量:底层通过字节码标记 ACC_FINAL禁止二次赋值 + 运行期内存屏障保证多线程可见性

-基本类型变量:若在编译期能确定赋值则会将其视为 【编译期常量】,并可能触发 常量折叠 优化

-引用类型变量:final 对引用类型的约束是 【引用不可变】但对象本身的内容可以修改(如 list.add("a") 是允许的)


final的内存屏障如何保证多线程可见:

利用happen-before规则结合写屏障和写屏障

写屏障:

禁止对final字段赋值操作与构造方法结束后发生的所有的对final变量的写入操作进行重排序

final字段的写入一定发生在对象【引用发布】之前,也就是保证对象构建好后外部线程才能访问

读屏障 :

禁止对读取final字段的操作与该读取操作前的任何读取操作进行重排序

强制要求读取final字段时必须先去检查最新的内存值,保证不丢失修改,保证读取的数据值最新值

http://www.lryc.cn/news/596695.html

相关文章:

  • SpringBoot PO VO BO POJO实战指南
  • Pycharm下载、安装及配置
  • 力扣 hot100 Day52
  • RabbitMQ03——面试题
  • 为什么要微调大语言模型
  • 论文笔记 | Beyond Pick-and-Place: Tackling Robotic Stacking of Diverse Shapes
  • 解决pip指令超时问题
  • 数据结构 堆(2)---堆的实现
  • LeetCode 热题100:42.接雨水
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(1)
  • 业务流逻辑如何搭建?为何橙武平台选用了 LogicFlow?​
  • day19 链表
  • 程序是如何生成的-以c语言为例
  • 信息学奥赛一本通 1553:【例 2】暗的连锁
  • 前端_CSS复习
  • 【React 入门系列】React 组件通讯与生命周期详解
  • 高可用架构模式——数据集群和数据分区
  • 单细胞转录组学+空间转录组的整合及思路
  • OneCode3.0 UI组件注解详解手册
  • 【vscode】vscode中python虚拟环境的创建
  • 回调地狱及解决方法
  • error C++17 or later compatible compiler is required to use ATen.
  • 【coze扣子】第1篇:coze快速入门
  • 威胁情报:Solana 开源机器人盗币分析
  • 以Java程序员角度理解MCP
  • 学习游戏制作记录(战斗系统简述以及击中效果)7.22
  • [c++11]std::function/bind
  • 基于SpringBoot+Vue的班级管理系统(Echarts图形化分析)
  • 101.对称二叉树
  • ubuntu 20.04 安装 cmake 3.26