Java内存模型与多线程并发编程培训 📅 2026/7/15 2:41:29 Java内存模型与多线程并发编程培训开篇为什么面试总问JVM内存模型面试Java岗位时“JVM内存模型”“进程与线程的区别”线程安全怎么保证几乎是必问题。这些问题考的不是背诵能力而是你能不能讲清楚一段Java代码从写出来到跑起来中间经历了什么内存里到底发生了什么多线程为什么会把数据改坏本次培训就围绕这三个问题展开。一、Java程序执行流程1.1 一段代码是怎么跑起来的写好的.java文件只是文本计算机读不懂。必须先编译成.class字节码再加载到JVM中运行。整个过程就像翻译一本书先由javac把中文源代码翻译成中间语言字节码再由java把中间语言交给本地解释器执行。1.2 javac与java的分工# 第一步编译javac HelloWorld.java# 第二步运行javaHelloWorldjavac负责把源代码编译成字节码文件。java负责启动JVM、加载类、找到main方法并执行。1.3 字节码里有什么.class文件里存储的是字节码指令和数据。指令告诉JVM做什么数据是操作对象。比如int a 10字节码里会有一条把10压入操作数栈的指令以及变量a在局部变量表中的位置信息。1.4 程序入口为什么是main方法JVM加载完类后会主动搜索public static void main(String[] args)方法。这个方法固定作为程序入口。为什么是static因为调用main时还没有创建对象必须不依赖实例就能执行。1.5 完整执行流程图HelloWorld.java ↓ javac编译 ↓ HelloWorld.class ↓ java命令启动JVM ↓ JVM加载字节码 ↓ 找到main方法 ↓ 创建main线程 ↓ 从main方法开始执行二、CPU执行原理2.1 CPU的两大核心部件CPU主要由运算器和控制器组成。运算器负责算术运算、逻辑运算、移位运算。控制器负责取指令、解析指令、指挥运算器工作。2.2 程序执行的本质循环CPU执行程序时不断重复三个步骤取指令从内存中读取下一条指令译码解析这条指令要做什么执行运算器完成具体计算这个循环非常快现代CPU每秒可以执行几十亿次。2.3 IO操作与系统调用当程序调用System.out.println或读写文件时CPU会发起系统调用交给操作系统内核完成IO操作。IO操作通常比CPU计算慢得多。单线程程序在等IO时CPU就空闲了。多线程可以在这个空档期让CPU去执行其他任务。三、JVM内存模型详解3.1 进程与JVM的关系操作系统为每个进程分配独立的内存空间。一个Java程序启动后操作系统会创建一个进程。JVM运行在这个进程内部再把进程内存划分成自己的几个区域。我们常说的JVM内存模型指的就是JVM内部的内存划分。3.2 方法区类的蓝图档案室方法区存储类元数据包括类的结构、常量池、静态变量、方法字节码等。可以把方法区理解成建筑图纸档案室。程序运行时JVM需要在这里找到类的定义才知道怎么创建对象、怎么执行方法。【面试高频】JDK8之前方法区叫永久代PermGenJDK8之后改为元空间Metaspace使用本地内存。3.3 堆对象的仓库堆是JVM中最大的一块内存区域所有通过new创建的对象实例都存放在这里。堆是所有线程共享的。多个线程可以同时访问堆中的同一个对象这也是线程安全问题产生的根源。3.4 虚拟机栈方法调用的舞台虚拟机栈是控制程序执行流程的关键。每个线程都有自己独立的虚拟机栈。方法调用时JVM会创建一个栈帧压入栈顶。栈帧里保存局部变量表、操作数栈、动态链接、方法返回地址等信息。方法执行结束栈帧出栈程序回到调用位置继续执行。栈采用后进先出结构。3.5 程序计数器线程的指南针程序计数器记录当前线程正在执行的字节码指令地址。每个线程独立拥有一个程序计数器。线程切换时程序计数器保证线程恢复执行时能从上次中断的位置继续。3.6 本地方法栈本地方法栈与虚拟机栈类似但它是为执行Native方法服务的。比如Thread.start()底层就调用了本地方法。3.7 直接内存直接内存不是JVM运行时数据区的一部分但被NIO频繁使用。它绕过JVM堆直接在操作系统内存中分配可以提高IO效率。3.8 JVM内存区域对比表区域存储内容线程共享典型异常方法区类信息、常量、静态变量共享Metaspace OOM堆对象实例共享OutOfMemoryError虚拟机栈栈帧、局部变量、返回地址私有StackOverflowError程序计数器当前指令地址私有无本地方法栈Native方法信息私有StackOverflowError3.9 线程共享 vs 线程私有线程共享线程私有方法区虚拟机栈堆程序计数器本地方法栈线程私有的区域不会互相干扰。线程共享的区域才需要考虑并发安全问题。四、虚拟机栈与方法调用4.1 栈帧结构每个方法调用对应一个栈帧栈帧包含四部分局部变量表存放方法参数和局部变量操作数栈存放运算过程中的中间结果动态链接指向运行时常量池的方法引用方法返回地址方法执行完后回到哪里4.2 方法入栈出栈过程publicclassStackDemo{publicstaticvoidmain(String[]args){methodA();}publicstaticvoidmethodA(){methodB();}publicstaticvoidmethodB(){System.out.println(执行B);}}执行流程的栈变化main 入栈 ↓ 调用 methodA methodA 入栈 ↓ 调用 methodB methodB 入栈 ↓ 执行 println println 入栈 ↓ println 执行完毕 println 出栈 ↓ methodB 执行完毕 methodB 出栈 ↓ methodA 执行完毕 methodA 出栈 ↓ main 执行完毕 main 出栈4.3 递归执行案例以斐波那契数列为例publicintfib(intn){if(n1||n2){return1;}returnfib(n-1)fib(n-2);}调用fib(5)的完整调用树fib(5) ├── fib(4) │ ├── fib(3) │ │ ├── fib(2) 1 │ │ └── fib(1) 1 │ └── fib(2) 1 └── fib(3) ├── fib(2) 1 └── fib(1) 1每一层调用都会创建新栈帧入栈遇到终止条件后返回值逐层出栈累加最终得到5。4.4 栈溢出的原因如果递归没有终止条件或者递归层次太深栈帧会不断入栈直到超过栈容量抛出java.lang.StackOverflowError// 错误示例没有终止条件publicvoidbadRecursion(){badRecursion();// 无限递归}五、进程与线程5.1 什么是进程进程是操作系统进行资源分配和调度的基本单位。一个运行中的Java程序就是一个进程。进程拥有独立的内存空间、文件描述符、句柄等资源。进程之间资源隔离一个进程崩溃通常不会影响另一个进程。5.2 什么是线程线程是程序执行过程的最小单位是CPU调度和执行的基本单位。一个进程可以包含多个线程。线程共享进程的资源包括内存空间、打开的文件等。但每个线程有自己独立的虚拟机栈和程序计数器。5.3 进程与线程的区别对比项进程线程资源分配是资源分配的基本单位不是CPU调度不是CPU调度单位是CPU调度单位内存空间独立共享进程内存切换开销大需要切换页表小只需切换栈和寄存器通信方式IPC、管道、Socket共享内存需同步机制崩溃影响通常不影响其他进程可能导致整个进程崩溃5.4 为什么Java更关注线程Java程序本身是一个进程但开发中我们更关心线程。因为线程更轻量能更细粒度地控制程序执行流程。多线程可以充分利用多核CPU提高程序吞吐量和响应速度。5.5 Java创建线程的三种方式// 方式一继承 Thread 类classMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(Thread running);}}// 方式二实现 Runnable 接口classMyRunnableimplementsRunnable{Overridepublicvoidrun(){System.out.println(Runnable running);}}// 方式三实现 Callable 接口有返回值classMyCallableimplementsCallableString{OverridepublicStringcall(){returnCallable result;}}【面试高频】推荐使用实现Runnable或Callable的方式因为Java是单继承继承Thread会限制扩展性。六、多线程与并发6.1 CPU调度时间片轮转CPU在同一时刻只能执行一个任务。操作系统采用时间片轮转算法让每个线程轮流执行一小段时间。时间片通常只有几十毫秒。线程在时间片用完后被挂起CPU切换到另一个线程执行。6.2 并发与并行的区别概念定义硬件要求并发宏观上同时微观上交替单核CPU即可并行真正同时执行需要多核CPU单核CPU只能实现并发多核CPU才能同时运行多个线程实现并行。6.3 多线程的优势与代价优势提高CPU利用率提升程序响应速度充分利用多核CPU代价线程创建和切换有开销需要处理线程安全问题增加程序复杂度和调试难度6.4 上下文切换线程切换时CPU需要保存当前线程的执行状态寄存器、程序计数器等再加载下一个线程的状态。这个过程叫上下文切换。频繁的上下文切换会消耗CPU资源。所以线程数量不是越多越好。七、线程安全问题7.1 线程安全三条件多个线程访问共享资源时只要同时满足以下三个条件就可能出现线程安全问题多线程环境存在共享资源对共享资源有写操作只要破坏其中任意一个条件就能避免线程安全问题。7.2 数据竞争当多个线程同时修改同一个变量时由于线程切换一个线程的修改可能被另一个线程覆盖导致结果不符合预期。例如count看起来只有一行代码实际包含三个步骤inttempcount;// 读取counttemp1;// 修改并写回7.3 经典案例两个线程各加100万次publicclassCounter{publicintcount0;publicvoidincrement(){count;}}publicclassUnsafeDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{CountercounternewCounter();Threadt1newThread(()-{for(inti0;i1000000;i){counter.increment();}});Threadt2newThread(()-{for(inti0;i1000000;i){counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);// 结果通常小于 200 万}}【面试高频】为什么结果小于200万因为count不是原子操作多线程下会丢失更新。7.4 synchronized关键字Java提供synchronized关键字解决线程安全问题。它可以修饰方法或代码块保证同一时间只有一个线程执行被锁住的代码。publicclassSafeCounter{privateintcount0;publicsynchronizedvoidincrement(){count;}publicsynchronizedintgetCount(){returncount;}}7.5 synchronized的三种用法用法锁对象示例同步代码块指定对象synchronized(obj)同步实例方法当前实例thispublic synchronized void method()同步静态方法类的Class对象public static synchronized void method()7.6 锁对象的选择多个线程必须使用同一个锁对象否则 synchronized 不起作用。// 错误示例每次new新对象当锁synchronized(newObject()){count;}上面的写法中每个线程锁的都是不同的Object对象等于没有锁。7.7 解决线程安全的其他方案方案原理适用场景AtomicIntegerCAS无锁机制单个变量计数ReentrantLockAQS显式锁需要灵活控制锁volatile保证可见性状态标志位不适合计数ThreadLocal线程本地变量每个线程独立副本【面试陷阱】volatile不能保证原子性不能解决i的线程安全问题。八、开发踩坑实录坑 1在循环里创建新对象当锁for(inti0;i100;i){synchronized(newObject()){// 每次锁都不同对象count;}}后果锁不住线程不安全。正确做法使用共享对象作为锁。坑 2锁对象被重新赋值privateObjectlocknewObject();publicvoidchangeLock(){locknewObject();// 锁对象变了}后果其他线程可能拿到新的锁对象导致多把锁同时存在。正确做法锁对象用final修饰。坑 3用Integer、String等常量当锁privateStringlocklock;后果字符串常量可能被其他类共享导致意外死锁或性能问题。正确做法使用private final Object lock new Object();坑 4递归没有终止条件publicvoidrecursion(){recursion();}后果StackOverflowError。正确做法递归必须有明确的终止条件。坑 5认为start()会立即执行t1.start();t2.start();后果调用start()只是把线程交给CPU调度具体什么时候执行由操作系统决定。正确做法不要对线程执行顺序做假设。九、面试速记卡9.1 核心概念速查概念一句话解释进程资源分配的基本单位拥有独立内存空间线程CPU调度的基本单位共享进程资源虚拟机栈每个线程私有控制方法调用执行流程堆所有线程共享存放对象实例方法区存储类信息、常量、静态变量程序计数器记录线程当前执行位置并发宏观同时、微观交替并行真正同时执行9.2 高频面试题Java程序从编译到执行的完整流程是什么JVM内存区域有哪些分别存储什么哪些线程共享进程和线程的区别是什么虚拟机栈的作用是什么栈帧包含哪些内容什么是线程安全出现线程安全问题需要满足哪些条件synchronized修饰普通方法和静态方法时锁对象分别是什么volatile能保证线程安全吗为什么i是线程安全的吗为什么递归调用过程中栈是如何变化的什么情况下会栈溢出并发和并行有什么区别9.3 口诀总结编译加载再执行main方法是入口 方法区里存类图堆里对象大家用 每个线程私有栈程序计数记行踪 多线程有共享写线程安全要同步 synchronized上把锁锁对象必须相同 volatile可见不原子计数还得用Atomic十、培训小结本次培训从Java程序执行流程讲起依次讲解了CPU执行原理、JVM内存模型、虚拟机栈与方法调用、进程与线程、多线程并发、线程安全问题。重点掌握Java程序从.java到执行的完整流程JVM五大内存区域及其作用线程共享区域和线程私有区域虚拟机栈后进先出的执行机制进程与线程的本质区别线程安全三条件与synchronized基础用法理解这些底层原理是读懂框架源码、通过技术面试、写出高质量并发代码的基础。