JVM的简单入门

一、JVM的概念

JVM(Java Virtual Machine,Java虚拟机) 是运行Java字节码(.class文件)的抽象计算机,是Java跨平台("一次编写,到处运行")的核心。它屏蔽了底层操作系统和硬件的差异,通过以下机制实现:


1. 核心功能

  • 字节码执行:将Java源码编译成的.class文件解释为机器码(或JIT编译为本地代码)。

  • 内存管理:自动分配/回收内存(垃圾回收/GC),避免手动管理内存泄漏。

  • 安全沙箱:通过字节码验证器、类加载器等机制防止恶意代码破坏系统。


2. 关键组成

组件作用
类加载器动态加载类文件(如AppClassLoaderExtClassLoader)。
运行时数据区划分内存区域:

 
执行引擎解释字节码或JIT编译为本地机器码(HotSpot JVM的C1/C2编译器)。
垃圾回收器自动回收无用对象(如G1、ZGC、Shenandoah等算法)。

3. 跨平台原理

  • 编译阶段:Java源码(.java)→ 平台无关的字节码(.class)。

  • 运行阶段:不同平台的JVM(如Windows/Linux的HotSpot)将字节码转换为对应机器码。


4. 常见实现

  • HotSpot(Oracle/OpenJDK默认):支持JIT和自适应优化。

  • OpenJ9(IBM):低内存占用,适合云原生。

  • GraalVM:多语言支持(Java、Python、JS),原生镜像(AOT编译)。


5. 调优场景示例

  • 内存溢出:通过-Xmx调整堆大小,用jmap分析堆转储。

  • GC优化:选择低延迟收集器(如ZGC)或吞吐量优先(Parallel GC)。


总而言之JVM是Java的"操作系统",通过字节码+虚拟化技术实现跨平台,同时提供自动内存管理和安全隔离。

二、JVM体系结构

注意:PC寄存器(程序计数器)内存非常小,接近可以忽略不计

三、类加载器以及双亲委派机制

java早期称为C++--:在C++的机制上去除繁琐的东西,如指针、内存管理等

3.1、类加载器

类加载器(Class Loader) 是 JVM 的一个子系统,负责 在运行时动态加载 .class 文件(字节码)到内存中,并为其在方法区中创建对应的 java.lang.Class 对象。它是 Java 动态性(如运行时加载、热部署)和安全沙箱机制的核心。


1. 核心职责

  • 加载:读取字节码(从文件、网络、数据库等)。

  • 验证:确保字节码符合 JVM 规范(防止恶意代码)。

  • 初始化:为静态变量赋初值,执行静态代码块。


2. 双亲委派模型(Parent Delegation Model)

JVM 采用双亲委派机制避免重复加载和核心类被篡改:

Bootstrap Class Loader(启动类加载器)
    ↑
Platform Class Loader(平台类加载器,Java 9+)
    ↑
Application Class Loader(应用程序类加载器)
    ↑
Custom Class Loader(用户自定义类加载器)
工作流程
  1. 子类先委托父类加载:当一个类需要被加载时,子加载器会先请求父加载器尝试加载。

  2. 父类无法加载时才自己加载:如果父加载器找不到类(如非核心类),子加载器才会自己加载。


3. 三种默认类加载器(应用加载器、扩展加载器、根加载器)

类加载器加载范围实现语言
Bootstrap%JAVA_HOME%/lib/rt.jar(核心类库,如 java.lang.*C++(Java无法查询到C或C++,所以通过getClassLoader()方法时,获取结果为null)
Platform%JAVA_HOME%/lib/ext/*.jar(扩展库)Java
Application用户 CLASSPATH 下的类(项目代码、第三方库)Java

4. 自定义类加载器

  • 场景:实现 热部署(Tomcat 的 WebAppClassLoader)、加密类文件加载从数据库/网络加载类

  • 关键方法

    public class MyClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] bytes = loadFromCustomSource(name); // 从自定义来源读取字节码
            return defineClass(name, bytes, 0, bytes.length);
        }
    }

5. 破坏双亲委派模型(百度的,我也不太懂)

  • SPI(Service Provider Interface)机制(如 JDBC):

    • 核心类(如 java.sql.Driver)由 Bootstrap 加载,但实现类(如 com.mysql.Driver)由 AppClassLoader 加载,需要反向委托(通过 Thread.setContextClassLoader())。

  • OSGi 模块化(如 Eclipse):每个模块有自己的类加载器,打破双亲委派,实现类隔离。


6. 面试高频问题

  • 如何证明双亲委派?

    System.out.println(String.class.getClassLoader()); // null(Bootstrap 加载)
    System.out.println(MyClass.class.getClassLoader()); // AppClassLoader
  • Tomcat 如何隔离不同 Web 应用的类?

    • 每个 Web 应用有自己的 WebAppClassLoader优先加载自己的类,而不是委托给父加载器(防止类冲突)。


一句话总结:类加载器是 JVM 的“搬运工”,通过双亲委派模型确保类的唯一性和安全性,同时支持自定义扩展实现动态加载。

3.2、双亲委派机制

双亲委派模型(Parent Delegation Model)是 类加载器之间的一种协作规范,而不是一个简单的“父子继承”关系。它的核心目标只有两句话:

“先让爸爸找,爸爸找不到儿子再找”
“绝对不允许任何人篡改核心类”

1、加载顺序

代码:    new com.example.User()
        │
        │ ① 应用类加载器(AppClassLoader)
        │    收到加载请求
        │          ↓ ② 先“向上抛”
        │    平台类加载器(PlatformClassLoader)
        │          ↓ ③ 继续向上抛
        │    启动类加载器(BootstrapClassLoader)
        │          ↓ ④ 真正开始找
        │    在 rt.jar 里没找到 User.class
        │          ↑ ⑤ 回到下一层
        │    平台类加载器也没找到
        │          ↑ ⑥ 回到最下层
        │    应用类加载器最终在 classpath 里找到并加载

2. 一张表:三类加载器的行为差异

加载器类型负责范围是否遵守双亲委派破坏委派的典型场景
Bootstrapjava.*sun.* 等核心类是(最顶层,没有父亲)
Platform$JAVA_HOME/lib/ext/*.jar
Application业务代码、三方库SPI 反向委派(见下)
自定义热部署、加密类、网络类可破坏重写 loadClass() 跳过父类

3、破坏双亲委派的现实需求

  • JDBC 4.0java.sql.Driver 由 Bootstrap 加载,但实现类(如com.mysql.cj.jdbc.Driver)在应用 jar 中。
    Thread.getContextClassLoader() 反向委派,让 Bootstrap 委托应用类加载器去加载 MySQL 驱动。

  • Tomcat 隔离:每个 Web 应用优先用自己的 WebAppClassLoader 加载类,如果先委派给父级就会和宿主 Tomcat 的类冲突


4、代码验证

// 证明 java.lang.String 一定由 Bootstrap 加载
System.out.println(String.class.getClassLoader()); // null(Bootstrap 的标记)

// 证明业务类由 AppClassLoader 加载
System.out.println(MyClass.class.getClassLoader()); // sun.misc.Launcher$AppClassLoader@...

// 证明双亲委派:自定义类加载器加载 java.lang.String 会失败
try {
    new MyClassLoader().loadClass("java.lang.String");
} catch (SecurityException e) {
    System.out.println("禁止自定义类加载器覆盖核心类!");
}

5、记忆口诀

“爸爸没找过,儿子绝不碰;核心类防篡改,反向委派有特例。”

四、沙箱安全机制(具体可以参考java中的安全模型(沙箱机制)_propertypermission-CSDN博客

Java 的 沙箱安全机制(Security Sandbox) 是一组层层设卡的“防护网”,让不可信的代码(如 Applet、用户上传的 JAR、第三方库)即使被 JVM 加载,也 只能在被批准的“安全领地”内活动,无法触碰宿主机关键资源。

核心思路:

“先验身、再授权、后审计,全程可撤销。”


1、四层防护结构(从外到内)

层级机制作用典型配置/代码
① 类加载器双亲委派 + 命名空间隔离防止类伪造(如自定义 java.lang.String自定义 ClassLoader 重写 findClass
② 字节码校验器静态校验 + 运行时检查防止非法字节码(栈溢出、类型转换错误)JVM 自动执行,-Xverify:none 可关闭(危险)
③ 安全管理器(SecurityManager)运行时权限检查文件/网络/反射等敏感操作需授权System.setSecurityManager(new SecurityManager())
④ 访问控制器(AccessController)基于策略的细粒度权限代码签名 + 策略文件 (*.policy)grant codeBase "file:/app/*" { permission java.io.FilePermission "/tmp/-", "read,write"; };

2、实战:限制用户上传的 JAR 只能读 /tmp

// 1. 启动时加载策略文件
java -Djava.security.manager -Djava.security.policy=my.policy -jar user-upload.jar

// 2. my.policy 文件内容
grant codeBase "file:/uploads/user-code.jar" {
    permission java.io.FilePermission "/tmp/-", "read,write";
    permission java.lang.RuntimePermission "getFileSystemAttributes";
    // 禁止网络、系统属性、反射等
};

3、现代演进(Java 9+ 模块化)

  • 模块系统(JPMS)
    module-info.java 中声明 显式导出/开放包,取代部分安全管理器职责:

    module com.example.app {
        requires java.base;
        exports com.example.api;  // 其他模块只能访问导出的包
        opens com.example.internal to trusted.module; // 反射开放
    }
  • JEP 411:SecurityManager 已标记 弃用,未来沙箱更多依赖 容器/操作系统级隔离(如 Docker、Seccomp)。


4、一句话总结

Java 沙箱 = 类加载隔离 + 字节码校验 + 策略授权 + 审计追踪,让 不可信代码只能做“被允许的最小操作”,即使漏洞出现也能把损失锁在笼子里。

五、native关键字以及方法区

5.1、native关键字

在 Java 中,native 是一个 关键字,用于声明一个方法的实现是由 本地代码(通常是 C 或 C++) 提供的,而不是由 Java 代码实现的。这些方法被称为 本地方法(Native Methods)。在JVM中,它们会被调入到本地方法栈中,通过本地方法接口,查找本地方法库,去执行本地方法。


✅ 使用场景

  • 需要调用 操作系统底层 API(如文件系统、硬件访问)。

  • 使用 已有的 C/C++ 库(如 OpenCV、TensorFlow 的 C++ 接口)。

  • 对性能要求极高的代码,用 C/C++ 实现更高效。


✅ 基本语法

public class NativeExample {
    // 声明一个本地方法
    public native void sayHello();

    public static void main(String[] args) {
        // 加载本地库
        System.loadLibrary("NativeLib");

        new NativeExample().sayHello();
    }
}

✅ 实现步骤(JNI 流程)

  1. 声明 native 方法(如上)。

  2. 生成头文件(使用 javahjavac -h

    javac NativeExample.java
    javah -jni NativeExample

    会生成 NativeExample.h

  3. 实现 C/C++ 代码(实现头文件中的函数)

    #include <jni.h>
    #include "NativeExample.h"
    #include <stdio.h>
    
    JNIEXPORT void JNICALL Java_NativeExample_sayHello(JNIEnv *env, jobject obj) {
        printf("Hello from C!\n");
    }
  4. 编译为共享库(Linux/macOS 用 .so,Windows 用 .dll

    gcc -shared -fPIC -o libNativeLib.so NativeExample.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux"
  5. 运行 Java 程序(确保库路径正确)

    java -Djava.library.path=. NativeExample

✅ 注意事项

  • 库名匹配System.loadLibrary("NativeLib") 会加载 libNativeLib.so(Linux)或 NativeLib.dll(Windows)。

  • 线程安全:本地代码中需要手动处理线程安全问题。

  • 异常处理:本地代码崩溃会导致 JVM 崩溃(Fatal error),需谨慎。


✅ 示例项目结构

project/
├── NativeExample.java
├── NativeExample.h
├── NativeExample.c
├── libNativeLib.so

✅ 总结

特性描述
关键字native
实现语言C/C++(或其他本地语言)
加载方式System.loadLibrary()
技术名称

JNI(Java Native Interface)

5.2、方法区(存储stastic、final、类模板、常量池...)

方法区(Method Area)是 JVM 规范中定义的 逻辑区域,用于存储 类元数据运行时常量池
从 JDK 8 开始,HotSpot 用 Metaspace 实现方法区,并把它从 永久代(PermGen) 移到了 本地内存(Native Memory)


✅ 一句话定义

方法区是 JVM 为 每个类 保存“静态结构”的地方,包括类信息、常量、静态变量、即时编译后的代码等。


✅ 存储内容(JDK 8+)

内容项说明
类元信息类的全限定名、父类、接口、字段、方法、访问标志(如 ACC_PUBLIC)等
运行时常量池字面量(字符串、数字)、符号引用(类名、方法名、字段描述符)
静态变量static 修饰的字段(类变量)
JIT 编译后的代码HotSpot 的 Code Cache(位于本地内存,但逻辑上属于方法区)
运行时常量池类加载后,class 文件中的常量池被解析为运行时常量池

✅ 实现演进

JDK 版本实现方式内存位置溢出异常
≤ JDK 7永久代(PermGen)JVM 堆内存java.lang.OutOfMemoryError: PermGen space
≥ JDK 8Metaspace本地内存java.lang.OutOfMemoryError: Metaspace

✅ 与 Native 关键字的关系

  • 类加载 时,本地方法的 ACC_NATIVE 标记存储在 方法区

  • JNI 调用 时,JVM 从方法区获取 jclass / jmethodID


✅ 常见误区

  • ❌ “方法区 = 堆” → 错误,方法区是逻辑区域,JDK 8 后物理上在 本地内存

  • ❌ “静态变量在堆” → 部分正确,静态变量在方法区(Metaspace),但对象实例在堆。


✅ 示例

public class Example {
    static int count = 0; // 存储在方法区(Metaspace)
    native void callC();   // 方法的元信息(ACC_NATIVE)也在方法区
}

✅ 总结

特性描述
作用存储类结构、常量、静态数据
位置JDK 8 前:永久代(堆内)
JDK 8 后:Metaspace(本地内存)
GC类卸载时回收(条件严格)
OOMOutOfMemoryError: Metaspace

方法区就像 JVM 的“类仓库”,而 Metaspace 是它在新版本中的具体实现。

六、栈

6.1、栈

在 JVM 中,“栈”通常指 Java 虚拟机栈(JVM Stack),它与 本地方法栈(Native Method Stack) 是两块不同的内存区域。

名称用途线程私有?溢出异常
Java 虚拟机栈执行 Java 方法(字节码)StackOverflowError / OutOfMemoryError
本地方法栈执行 native 方法(C/C++)同上
C 运行时栈操作系统层面的线程栈由 OS 决定(段错误等)

✅ Java 虚拟机栈(JVM Stack)细节

1. 组成:由 栈帧(Stack Frame) 构成

每调一次 Java 方法就压入一个栈帧,方法返回后弹出。

2. 栈帧内部结构(HotSpot)
+-----------------------------+
| 局部变量表(Local Variable Array)| 保存参数、局部变量
| 操作数栈(Operand Stack)        | 字节码指令的工作区
| 动态链接(Dynamic Linking)      | 指向运行时常量池的方法引用
| 方法返回地址(Return Address)   | 调用者 PC
| 附加信息(调试、锁记录等)        |
+-----------------------------+
3. 生命周期
  • 线程创建时 JVM 为其分配一块栈内存(默认 1 MB,可 -Xss 调整)。

  • 方法调用时 压帧;方法返回 时弹帧;异常抛出 时做栈展开(stack unwinding)。

4. 常见异常
  • StackOverflowError:递归太深,栈帧数 > 栈深度。

  • OutOfMemoryError:线程太多,无法为新线程申请栈内存。


✅ 本地方法栈(Native Method Stack)

  • 当 Java 调用 native 方法时,JVM 通过 JNI wrapper 进入 C/C++,本地方法栈 就是 C/C++ 使用的栈。

  • HotSpot 把 Java 栈本地方法栈 合并在同一块物理内存(同一条线程栈),因此:

    • Java → native:C 帧直接长在 Java 帧之上。

    • native → Java:通过 JNIEnv 回调 Java 方法时,会再压入新的 Java 帧。


✅ 示例:一次跨栈调用

class Demo {
    public static void main(String[] a) { foo(); }
    static void foo() { bar(); }
    static native void bar();   // C 实现
}

运行时栈(从底向上):

| 线程启动帧         |  ← OS 分配栈底
| main() Java 帧    |
| foo() Java 帧     |
| bar() JNI stub 帧 |  ← Java→native 边界
| bar() C 帧        |  ← 本地方法栈

✅ 调优与诊断

选项/工具作用示例
-Xss1m设置每条线程 JVM 栈大小
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps观察线程创建失败时的 OOM
jstack <pid>打印所有线程的栈帧(Java + JNI)
jcmd <pid> Thread.print同上,支持更多诊断信息

✅ 一句话总结

Java 栈 存 Java 方法帧,本地方法栈 存 native 方法帧;HotSpot 把两者放在同一条 OS 线程栈 上,只是视角不同。

6.2、Java对象在内存中的实例化的过程

6.3、三种JVM(学的都是基于HotSpot)

  1. HotSpot(Oracle/OpenJDK 默认):最常用,JIT、GC、Profiler 最全。

  2. OpenJ9(IBM/Eclipse): 启动快、内存省,适合云/容器。

  3. GraalVM(Oracle Labs):多语言(Java/JS/Python/R),AOT 原生镜像(Spring Native 就靠它)。

  4. 总而言之,言而总之,日常开发默认 HotSpot;想省内存用 OpenJ9;要原生或混语言用 GraalVM

七、堆

堆是 JVM 里最大的一块内存,唯一目的就是“放对象”——所有 new 出来的实例、数组都在这儿,由 GC 统一管理。

八、新生代、老年代、永久代、堆内存调优

九、使用Jpofiler工具分析OOM原因

使用JPofiler工具分析OOM原因_oom问题结合jprofiler排查-CSDN博客

jvm调优命令

JVM调优命令大全-CSDN博客

十、GC(垃圾回收机制)

10.1、GC所管理的区域

10.2、GC算法

1. 引用计数(Reference Counting)

  • 原理:每个对象维护一个引用计数器,当引用增加(如赋值)时计数器+1,引用失效(如变量离开作用域)时-1。计数器为0时立即回收。

  • 优点:实时回收,无暂停(无STW,Stop-The-World)。

  • 缺点:无法处理循环引用(如A→B→A),计数器更新开销大。

  • 应用:Python(CPython辅助使用)、Objective-C(ARC优化)。


2. 标记-清除(Mark-Sweep)

  • 原理

    1. 标记阶段:从根(GC Roots,如全局变量、栈中引用)出发,遍历所有可达对象并标记。

    2. 清除阶段:回收未标记的对象。

  • 优点:解决循环引用,算法简单。

  • 缺点:产生内存碎片,清除阶段可能暂停应用(STW)。

  • 应用:早期JVM(已淘汰)、Lua。


3. 标记-整理(Mark-Compact)

  • 原理:在标记-清除基础上,将存活对象向内存一端移动,消除碎片后清理边界外内存。

  • 优点:无碎片,适合老年代(长期存活对象)。

  • 缺点:对象移动成本高,暂停时间较长。

  • 应用:JVM老年代(Serial Old、Parallel Old收集器)。


4. 复制算法(Copying)

  • 原理:将内存分为两块(From/To),存活对象从From复制到To后交换角色,直接清理整块From。

  • 优点:无碎片,复制高效(适合短生命周期对象)。

  • 缺点:内存利用率低(50%),复制长存活对象成本高。

  • 应用:JVM新生代(Serial、ParNew收集器的Eden/Survivor区)。


5. 分代收集(Generational Collection)

  • 原理:基于“弱分代假说”(多数对象朝生夕死),将堆分为新生代(复制算法)和老年代(标记-清除/整理),分别优化。

  • 优化点:新生代频繁GC(Minor GC),老年代低频Full GC。

  • 应用:现代JVM(G1、ZGC、Shenandoah)、.NET CLR。


6. 增量收集(Incremental GC)

  • 原理:将GC过程拆分为多个小步骤,与应用线程交替执行,减少单次暂停时间。

  • 挑战:需解决GC过程中对象关系变化(如写屏障Write Barrier)。

  • 应用:V8引擎(早期)、部分实时系统。


7. 并发/并行收集(Concurrent/Parallel GC)

  • 并发(Concurrent):GC线程与应用线程同时运行(如CMS、G1的并发标记)。

  • 并行(Parallel):多GC线程并行加速回收(如Parallel Scavenge)。

  • 应用

    • CMS(Concurrent Mark-Sweep):低延迟,但产生碎片。

    • G1(Garbage First):区域化分代+并发标记+整理,平衡吞吐与延迟。

    • ZGC/Shenandoah:亚毫秒级暂停,支持TB级堆(JDK11+)。


8. 引用类型优化

  • 强/软/弱/虚引用:允许开发者控制对象生命周期(如Java的SoftReference缓存、WeakHashMap)。


对比表

算法内存碎片暂停时间吞吐量适用场景
引用计数极低实时系统(无循环引用)
标记-清除老年代(低频GC)
标记-整理老年代(大对象)
复制算法新生代(小对象)
G1/ZGC无/低极低大堆、低延迟应用

十一、JMM

JMM(Java Memory Model,Java内存模型) 是Java虚拟机规范中定义的多线程内存访问规则,用于解决并发编程中的可见性、有序性、原子性问题。它屏蔽了不同硬件/操作系统的内存差异,确保多线程程序在不同平台下行为一致。


1. 核心目标

  • 可见性:一个线程对共享变量的修改,其他线程能否立即看到。

  • 有序性:禁止指令重排序(编译器/CPU优化)导致的执行顺序问题。

  • 原子性:保证基本操作的不可分割性(如long/double以外的读写)。


2. 主内存与工作内存

  • 主内存(Main Memory):所有共享变量的存储区域(物理内存或堆内存)。

  • 工作内存(Working Memory):每个线程的私有内存(CPU缓存/寄存器),存储共享变量的副本。

  • 交互规则:线程对变量的读写必须通过工作内存,不能直接操作主内存。


3. 内存间的交互操作(8种原子操作)

操作作用(线程→内存)
lock将主内存变量标记为线程独占(锁)。
unlock释放锁,将变量写回主内存。
read从主内存读取变量值到工作内存。
loadread的值放入工作内存的变量副本。
use将工作内存变量值传递给执行引擎。
assign将执行引擎结果赋值给工作内存变量。
store将工作内存变量值传回主内存。
writestore的值写入主内存变量。

4. 三大特性规则

(1)可见性(Visibility)
  • 问题:线程A修改变量后,线程B可能仍使用旧值(因工作内存未同步)。

  • 解决

    • volatile:强制读写直接操作主内存,禁止缓存优化。

    • synchronized:解锁前必须将工作内存变化刷新到主内存。

    • final:初始化完成后不可变,其他线程可见。

(2)有序性(Ordering)
  • 问题:编译器/CPU可能重排序指令(如a=1; b=2;可能先执行b=2)。

  • 解决

    • volatile:禁止指令重排序(通过内存屏障)。

    • synchronized:同一锁的代码块内保持串行执行。

(3)原子性(Atomicity)
  • 基本类型:除long/double外,读写天然原子(32位JVM需volatile保证64位原子性)。

  • 复合操作:需synchronizedAtomicInteger等原子类。


5. Happens-Before规则(先行发生原则)

JMM定义的天然有序性关系,无需同步即可保证可见性:

  1. 程序顺序规则:单线程内,代码顺序执行。

  2. 锁规则:解锁unlock先于后续加锁lock

  3. volatile规则:写操作先于读操作。

  4. 线程启动/终止Thread.start()先于线程内操作,线程内操作先于Thread.join()返回。

  5. 传递性:若A→B,B→C,则A→C。


6. 示例:volatile的可见性

class Example {
    volatile boolean flag = false;

    void writer() {
        flag = true; // 写操作立即刷回主内存
    }

    void reader() {
        if (flag) { // 读操作直接从主内存获取
            System.out.println("可见");
        }
    }
}
  • flagvolatile,线程B可能永远看不到true


7. JMM vs 运行时内存区域

  • JMM:逻辑模型,定义线程如何交互共享变量。

  • 运行时内存区域(JVM内存结构):物理划分(堆、栈、方法区等)。

    • 例如:工作内存可能对应CPU缓存,主内存对应堆。


8. 常见误区

  • volatile不能保证原子性:如volatile i++仍非线程安全(需AtomicInteger)。

  • synchronized不仅保证原子性:也确保可见性(解锁时刷新内存)。


总结

JMM通过抽象的主内存/工作内存模型Happens-Before规则,屏蔽底层硬件差异,使开发者无需关心缓存一致性,只需正确使用volatilesynchronized等关键字即可编写线程安全代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值