Java线程泄漏
什么是线程泄露?线程泄露是指:应用程序中创建了线程,但在其任务完成后却无法被垃圾回收器(GC)回收,导致线程对象及其相关的资源(如内存、CPU 时间片)持续占用,且无法被重新使用的情况。 简单来说,就是只生不死的线程。它与内存泄露的概念非常相似,只不过泄露的核心对象是 Thread。 每个 Java 线程都对应一个操作系统级别的线程,其开销是相当大的(默认栈大小通常为 1MB)。因此,即使只泄露少量线程,也会快速消耗掉系统的可用内存和 CPU 调度能力。 线程泄露的常见原因及示例导致线程泄露的根本原因是:线程未能正常结束其生命周期(即未从 RUNNABLE 状态进入 TERMINATED 状态)。以下是几个典型场景: 线程池使用不当(最常见的原因)如果你使用了 ExecutorService,必须记得在不再需要时将其关闭。如果没有关闭,池中的核心线程会一直等待新的任务,从而阻止 JVM 正常退出,线程也无法被回收。 错误示例: 12345678910111213141516public class ThreadPoolLeak { public static v...
Java线程安全
在 Java 中,线程安全是一个核心概念,指的是当多个线程并发访问某个类、对象或方法时,这个类、对象或方法始终能表现出正确的行为,而无需额外的同步或协调机制。 核心问题:共享数据的并发访问 线程不安全的根源在于多个线程同时读写共享的可变状态(数据)。 如果数据是只读的(不可变),或者每个线程操作的都是自己私有的数据副本,那么自然就是线程安全的。 问题在于:当多个线程尝试同时修改同一个变量、同一个集合、同一个文件等共享资源时,如果访问操作不是原子的,或者线程间的修改对其他线程不可见,就会导致数据不一致、逻辑错误、程序崩溃等不可预测的后果。 导致线程不安全的三大根源 原子性缺失: 一个操作(可能由多条字节码或机器指令组成)需要作为一个不可分割的整体执行。如果执行到一半被其他线程打断,就可能导致中间状态被其他线程观察到或修改,从而破坏一致性。 经典例子:i++。 它实际上包含读取i的值、将值加1、将结果写回i三个步骤。两个线程同时执行i++可能导致只增加一次。 可见性问题: 一个线程对共享变量的修改,不能立即被其他线程看到。这主要是由现代计算机架构(多级缓存)和 Java 内...
JVM垃圾回收算法
JVM 中的垃圾回收算法是其自动内存管理的核心,随着 JVM 的发展,出现了多种算法,各有优缺点和适用场景。主要可以分为以下几类: 基础算法 (通常作为其他更复杂算法的基础组件) 标记-清除算法: 过程: 分为两个阶段。 标记: 从 GC Roots(如活动线程栈帧中的引用、静态变量、JNI 引用等)出发,遍历对象图,标记所有可达对象为存活。 清除: 遍历整个堆,回收所有未被标记为存活的对象所占用的内存空间。 优点: 实现相对简单。 缺点: 效率问题: 标记和清除两个过程的效率都不算高(需要遍历整个堆)。 空间问题: 回收后会产生大量不连续的内存碎片。这会导致以后需要分配较大对象时,无法找到足够的连续内存,从而触发另一次 GC。 标记-复制算法: 过程: 将可用内存划分为容量相等的两块(如 From 和 To)。只使用其中一块(From)。当这块内存用尽时,就将还存活的对象复制到另一块内存(To)上,然后一次性清理掉已使用的内存块(From)的全部空间。之后交换 From 和 To 的角色。 优点: 实现简单,运行高效。 解决了内存碎片化问题(复制后存活对象在 To...
Java对象生命周期
一个Java对象从被加载到JVM内存,到最终被垃圾回收器(GC)清除,会经历一个完整的生命周期,主要包含以下几个关键阶段: 类的加载与初始化(发生在对象创建之前) 加载: 当JVM第一次遇到 new 关键字、静态字段访问、静态方法调用、反射调用(如 Class.forName())、子类初始化触发父类初始化等场景时,如果目标类尚未被加载,JVM会通过类加载器查找并加载该类的字节码(.class 文件)到内存中。 验证: 确保被加载的字节码是符合JVM规范的、安全的,不会危害虚拟机自身。检查内容包括文件格式、元数据、字节码语义、符号引用等。 准备: 为类的静态变量分配内存(在方法区)并设置其初始零值(0, null, false 等)。final static 常量如果其值在编译期可知,通常在这个阶段就直接赋值了。 解析: 将常量池内的符号引用(以文本形式表示的引用,如类名、方法名、字段名)替换为直接引用(指向内存中具体位置的指针、偏移量或句柄)。解析可能在初始化之前或之后进行。 初始化: 执行类的初始化方法 <clinit>(),该方法由编译器自动收...
JVM问题排查
明确问题现象 常见问题类型: 内存溢出(OutOfMemoryError) CPU占用率飙升(100%) 线程阻塞/死锁(应用无响应) GC频繁(应用卡顿) 类加载异常(ClassNotFoundException/NoClassDefFoundError) 基础信息收集查看JVM参数12jps -lvm # 列出所有Java进程及其启动参数jinfo <pid> # 查看指定进程的详细配置 关键参数:堆大小(-Xms, -Xmx)、GC算法(-XX:+UseG1GC)、OOM行为(-XX:+HeapDumpOnOutOfMemoryError) 实时监控1jstat -gcutil <pid> 1000 # 每秒打印一次GC情况 输出关键指标: YGC/YGCT:Young GC次数/耗时 FGC/FGCT:Full GC次数/耗时 O:老年代使用率 M:元空间使用率 深度排查工具内存问题排查 堆内存分析: 12jmap -dump:format=b,file=heap.hprof <pid...
Java内存模型
Java内存模型(JMM)是Java虚拟机规范中定义的一种抽象内存模型,它规定了多线程环境下,线程如何与主内存及工作内存交互,以及线程间共享变量的可见性、有序性和原子性规则。其核心目标是解决多线程并发中的内存可见性、指令重排序等问题,为开发者提供一套可预测的并发编程语义。 JMM的核心组件 主内存(Main Memory) 存储所有共享变量(实例字段、静态字段等)。 所有线程均可访问,但线程不能直接操作主内存中的变量。 工作内存(Working Memory) 每个线程独享的私有内存区域。 存储该线程使用的共享变量的副本。 线程对变量的所有操作(读/写)必须在工作内存中进行,不能直接读写主内存。 JMM的关键规则 内存交互操作JMM定义了8种原子操作(已简化,实际由JVM实现): lock:锁定主内存变量,标识为线程独占。 unlock:解锁主内存变量。 read:从主内存读取变量到工作内存。 load:将read得到的值放入工作内存副本。 use:将工作内存变量值传递给执行引擎(如CPU)。 assign:将执行引擎结果赋给工作内存变量。 ...
JVM内存模型
JVM(Java虚拟机)内存模型(也称为运行时数据区)是Java程序运行时的核心基础,它定义了程序执行过程中数据存储和管理的逻辑结构。这种设计是为了实现平台无关性、内存自动管理(垃圾回收)、线程安全和高性能等目标。 JVM内存模型的核心区域 方法区(Method Area) 存储内容:类信息(Class元数据)、常量池(Constant Pool)、静态变量(Static Variables)、JIT编译后的代码等。 设计原因: 共享性:所有线程共享类元数据,避免重复加载,节省内存。 持久性:类信息在程序启动后长期存在,直到JVM关闭。 演进:在JDK 8之前称为永久代(PermGen),后改为元空间(Metaspace)(使用本地内存,避免OutOfMemoryError)。 堆(Heap) 存储内容:所有对象实例和数组。 设计原因: 动态内存分配:对象生命周期不确定,需要灵活的内存管理。 垃圾回收(GC)的核心区域:通过分代设计(年轻代、老年代)优化GC效率。 线程共享:减少对象复制的开销,但需通过同步机制保证线程安全。 虚拟机栈(JVM Stack) ...
Java双亲委派机制
Java 中的双亲委派机制(Parent Delegation Model) 是类加载器(ClassLoader)加载类的核心机制,其核心思想是:当一个类加载器收到加载类的请求时,它不会立即尝试加载,而是先将请求委派给父类加载器处理。只有在父类加载器无法完成加载时,子加载器才会尝试加载。 类加载器的层级结构Java 类加载器分为三层(实际为四层,包括自定义加载器): 启动类加载器(Bootstrap ClassLoader) 由 C++ 实现,是 JVM 的一部分。 负责加载 JAVA_HOME/lib 下的核心类库(如 rt.jar)。 唯一没有父加载器的加载器(顶级存在)。 扩展类加载器(Extension ClassLoader) Java 实现,继承自 ClassLoader。 负责加载 JAVA_HOME/lib/ext 目录或 java.ext.dirs 指定路径的类。 父加载器是 Bootstrap ClassLoader。 应用程序类加载器(Application ClassLoader) 也称为系统类加载器(AppClassLoader)...
Java非受检异常 VS 受检异常
在业务自定义异常的设计中,优先选择非受检异常(RuntimeException子类)通常是更优方案,但需根据具体场景权衡。以下是深度分析和决策指南: 核心对比:非受检异常 vs 受检异常 维度 非受检异常(RuntimeException) 受检异常(Exception) 强制处理 调用方不强制捕获或声明 编译器强制处理(try-catch/throws) 适用场景 业务逻辑错误、不可恢复错误 可恢复错误、需显式处理的场景 代码侵入性 低(不污染方法签名) 高(方法需声明throws,调用链需处理) 设计哲学 Fail-Fast 快速暴露问题 防御式编程 确保错误被处理 典型案例 IllegalArgumentException IOException 优先选择非受检异常的场景(推荐主流方案)**不可恢复的业务错误 如订单状态冲突、ID不存在等业务规则违反 示例:1234567891011121314// 非受检业务异常:调用方无法通过代码修复此错误class OrderStateException extends RuntimeExc...
Java异常体系
Java 的异常体系采用层次化设计,以 Throwable 为顶层基类,其下分为 Error 和 Exception 两大分支,而 Exception 又细分为 受检异常(Checked Exception) 和 非受检异常(Unchecked Exception)。以下是体系结构详解: 异常体系层次 Throwable(所有异常/错误的超类) 定义异常消息、堆栈跟踪等方法。 直接子类:Error 和 Exception。 Error(系统级错误) 表示 JVM 无法处理的严重问题(如内存耗尽)。 非受检:无需强制捕获(如 OutOfMemoryError)。 Exception(程序可处理的异常) 分为两类: 受检异常(Checked Exception)需显式处理(如 IOException、SQLException)。 非受检异常(Unchecked Exception)即 RuntimeException 及其子类(如 NullPointerException)。 设计原因与优势 强制处理可恢复错误(受检异常) 目的...
