JVM 面试题
Java 虚拟机(JVM)是运行 Java 程序的引擎,它是 Java 语言 “一次编译,处处运行” 的核心技术。JVM 的主要任务是将 Java 字节码(Bytecode)解释成机器码并执行,负责内存管理、线程管理、垃圾回收等功能。JVM 主要由以下几个重要的结构组成:
类加载子系统(Class Loader Subsystem)
- 类加载:JVM 中的类加载是将 Java 字节码(即 .class 文件)动态加载到 JVM 内存中的过程,并为这些类分配内存、解析依赖、执行初始化并为类创建对应的 Java 类对象的步骤。具体包括如下步骤:
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
有些文档也会将 “使用” 和 “卸载” 视为后续步骤,但这两个阶段并非严格的类加载步骤的一部分。
加载(Loading)
这是类加载过程的第一步,类加载器根据类的全限定名(包括包名)找到对应的 .class 文件并加载到内存中。在加载过程中,JVM 会创建一个 java.lang.Class 对象,用来表示这个类的元数据。加载阶段会通过类加载器完成。加载过程涉及:
- 通过 双亲委派模型 请求父加载器加载类,若父加载器无法加载,再由当前类加载器加载。
- 查找 .class 文件的位置(通常从文件系统、JAR 包或者网络等路径中查找)。
验证(Verification)
验证阶段是为了确保加载的字节码是符合 JVM 规范的、安全的字节码。JVM 通过一系列的校验来确保字节码不会破坏 JVM 运行的稳定性或安全性。验证主要包括以下几个方面:
- 文件格式验证:检查 .class 文件是否符合 Class 文件格式规范。
- 元数据验证:检查类中的元数据信息是否合理。例如,类是否继承了非法的父类、类的方法签名是否正确等。
- 字节码验证:对类的方法中的字节码进行验证,确保指令序列是合法的、符合逻辑的。
- 符号引用验证:对符号引用进行验证,确保所有引用的类、方法、字段都存在并且可访问。
- 验证阶段保证了字节码不会对 JVM 运行时环境构成威胁,但它可能会导致 VerifyError 错误。
准备(Preparation)
在准备阶段,JVM 会为类的静态变量分配内存,并将其初始化为默认值(并非编写代码时的赋值)。此阶段主要是分配内存而不执行具体的初始化操作。类中的静态字段会被赋予默认值:
- 数字类型(如 int, long 等)初始化为 0。
- 布尔类型初始化为 false。
- 引用类型初始化为 null。
例如,假设类中有以下静态变量:
public static int a = 10;
public static boolean flag = true;
在准备阶段,a 被初始化为 0,flag 被初始化为 false。真正的值(10 和 true)将在初始化阶段赋值。非静态字段是在类被实例化时赋值的,类加载阶段不进行赋值。
解析(Resolution)
解析阶段是将类的符号引用替换为直接引用的过程。符号引用是指在字节码中通过字符串等符号来引用类、字段、方法,解析过程会将这些符号引用解析为内存地址的直接引用。解析过程会涉及以下几类引用:
- 类或接口解析:将符号引用的类或接口名解析为实际的 Class 对象。
- 字段解析:将符号引用的字段解析为实际的内存位置。
- 方法解析:将符号引用的方法解析为实际的可执行代码地址。
- 接口方法解析:针对接口中的方法引用进行解析。
解析阶段可能会导致 NoSuchFieldError 或 NoSuchMethodError 等错误,如果解析失败,类加载过程也会中断。
初始化(Initialization)
这是类加载的最后一个阶段,也是执行静态变量赋值和静态代码块的阶段。在这个阶段,JVM 会根据程序员的指令对类的静态变量进行显式初始化,并执行静态代码块。类初始化的具体顺序是:
- 父类静态初始化优先于子类静态初始化。
- 静态变量按照它们在类中的声明顺序进行初始化。
- 执行静态代码块。
类加载时机
类的加载不是在 JVM 启动时就加载所有的类,而是在类被首次主动使用时才加载。这种按需加载机制被称为 类的延迟加载(Lazy Loading)。类的主动使用场景包括:
- 创建类的实例(new 操作)。
- 调用类的静态方法。
- 访问类的静态字段。
- 通过反射调用类。
- 初始化子类时,先初始化父类。
- JVM 启动时,指定的启动类(包含 main 方法)自动初始化。
类加载器
- Bootstrap Class Loader(引导类加载器)
- 这是 JVM 自带的、最顶层的类加载器,负责加载核心类库(如 rt.jar 中的类)。
- 通过 C/C++ 代码实现,不继承自 java.lang.ClassLoader 类。
- 主要负责加载:
- 位于 JAVA_HOME/lib 目录下的类。
- 标准核心库(如 java.lang.、java.util. 等)。
- Platform Class Loader(平台类加载器)
- 从 Java 9 开始引入,用来加载 Java 平台类库(如 java.sql、java.xml 等)。
- 位于 JAVA_HOME/lib/ext 目录及 platform 模块中。
- 是引导类加载器与应用类加载器之间的一个中间层级,用于支持多种库和平台相关的类。
- Application Class Loader(应用类加载器)
- 负责加载用户类路径(classpath)下的类,包括用户自定义的类和第三方库。
- 使用 java.lang.ClassLoader 的默认实现,通常是系统类加载器。
- 加载的类包括应用程序所需的所有 .jar 文件和 .class 文件。
JVM 类加载器体系遵循 双亲委派模型,这是类加载器设计中的核心机制。它的工作原理是:当一个类加载器收到类加载请求时,它会首先将该请求委托给父加载器去处理,只有当父加载器无法加载时,才由当前加载器自己去加载。这种模型有以下特点:
- 安全性:防止核心类库(如 java.lang.String)被自定义类加载器加载,保证核心类库的唯一性和安全性。
- 类的唯一性:同一个类在 JVM 内存中只会被加载一次(除非使用不同的类加载器),保证了类的唯一性。
- 防止重复加载:通过父加载器优先机制,避免不同类加载器重复加载同一个类。
运行时数据区(Runtime Data Areas)
JVM 在运行时管理的内存区域,可以分为以下几个部分:
方法区(Method Area)
在 JVM 中,方法区(Method Area)是用于存储类结构信息的内存区域。它保存了每个类的元数据信息,例如类的名称、访问修饰符、字段、方法、常量池等。方法区是 JVM 规范中的一部分,是堆外内存的一部分,属于非堆区域。它与堆相对独立,主要存储与类和常量相关的数据。**它是线程共享的区域。**具有以下特点:
- 存储内容:
- 类元数据:每个类的结构信息,包括类名、父类名、访问修饰符、接口信息等。
- 字段和方法信息:包括字段的名称、类型和方法的名称、签名等。
- 常量池(Constant Pool):存储编译时生成的字面量和符号引用(如字符串字面量、方法和字段的符号引用)。
- 静态变量:存储类的静态字段。
- 类的静态方法和代码:类中的字节码和方法相关的信息也存储在这里。
- 生命周期:方法区的生命周期与 JVM 一致,JVM 运行时,方法区会随着类的加载不断扩展,当 JVM 关闭时,方法区也会被销毁。
- 大小:方法区可以设置大小,但如果装载的类太多,方法区可能会导致内存不足(出现 OutOfMemoryError: Metaspace 错误)。从 Java 8 开始,方法区被替换为 元空间(Metaspace),并且其内存存储在本地内存(Native Memory)中,而不是 JVM 的堆中。
在 Java 7 及之前,方法区的实现被称为 永久代(Permanent Generation,PermGen),它是 JVM 堆的一部分。永久代有以下特点:
- 固定大小:PermGen 有一个固定的大小,可以通过 JVM 参数(如 -XX:PermSize 和 -XX:MaxPermSize)来设置。
- 内存管理问题:因为 PermGen 的内存是有限的,如果应用程序动态生成大量类或者使用了大量字符串常量,容易导致内存不足的错误,比如 OutOfMemoryError: PermGen space。
从 Java 8 开始,永久代被移除,取而代之的是 元空间(Metaspace)。元空间的存储改为使用本地内存而不是 JVM 堆内存,有以下显著变化:
- 动态扩展:元空间的大小不再像 PermGen 那样受限,可以动态扩展。默认情况下,元空间的大小是由系统内存决定的。
- 配置灵活性:可以通过以下 JVM 参数来控制元空间的行为:
- -XX:MetaspaceSize:设置元空间的初始大小。当元空间使用达到这个值时,JVM 会触发垃圾回收来清理不再使用的类。
- -XX:MaxMetaspaceSize:设置元空间的最大大小。元空间可以动态扩展,但如果达到这个限制,就会抛出 OutOfMemoryError: Metaspace 错误。
- -XX:CompressedClassSpaceSize:指定类指针的压缩空间大小。通常默认大小为 1GB,适用于 64 位 JVM。
- -XX:+UseCompressedClassPointers:启用压缩类指针,节省内存空间。
元空间的引入使得类加载和卸载更加高效,避免了 PermGen 的内存管理问题。
方法区作为 JVM 中非常重要的内存区域,主要用于类的加载、运行时常量池的维护和类的相关信息存储。其作用包括:
- 支持类加载机制:JVM 加载的每一个类,其相关的元数据都会存储在方法区中。
- 支持常量的引用与解析:运行时常量池中的数据,如方法引用、字段引用、字符串常量,存储在方法区内。
- 支持静态变量存储:方法区中还存储类的静态变量,所有类的静态变量在内存中的唯一副本存放于方法区。
- 垃圾回收的影响:尽管方法区不属于堆内存,但 JVM 仍然对方法区的内存进行管理,并且可以对无用的类信息进行垃圾回收。
堆(Heap)
在 JVM(Java 虚拟机)中,堆(Heap)是用于存储所有 Java 对象和数组的主要内存区域。堆是 JVM 运行时数据区域中最大的一部分,所有通过 new 关键字创建的对象都会被分配在堆内存中。堆内存的管理和分配对于 Java 程序的性能有着直接的影响。
- 对象的生命周期由垃圾回收机制决定,堆上的对象不再被引用时,JVM 的垃圾回收器会自动回收这些对象所占的内存。
- 堆的生命周期与 JVM 相同,堆内存随着 JVM 启动而创建,并在 JVM 关闭时销毁。
- JVM 堆是所有线程共享的。每个线程都可以访问堆中的对象,多个线程也可能同时操作堆中的相同对象。
- JVM 堆由垃圾回收器(Garbage Collector, GC)进行管理。GC 负责清理无用对象、回收内存空间,从而使得开发人员不需要手动管理内存。
堆内存通常会被划分为多个区域,用于更高效的内存管理和垃圾回收策略。典型的划分方式如下:
-
年轻代(Young Generation):
- 年轻代主要用于存放新创建的对象,大多数的对象在这里分配内存。年轻代的回收频率较高,垃圾回收通常使用的是Minor GC。
- 年轻代又分为三个子区域:
- Eden 区:对象在首次创建时被分配到 Eden 区。当 Eden 区满时,触发 Minor GC。
- 两个 Survivor 区(S0 和 S1):当对象在 Eden 区存活过一次 GC 后,会被移动到 Survivor 区。两个 Survivor 区交替使用,即 S0 和 S1 中只有一个区会被使用,另一个为空,GC 时对象会从一个 Survivor 区复制到另一个区。
-
老年代(Old Generation):
- 老年代存储的是生命周期较长、在年轻代中经历过多次 GC 仍未被回收的对象。相比于年轻代,老年代的垃圾回收频率较低,但执行的是Full GC,且 Full GC 的开销要比 Minor GC 大得多。
-
JVM 的堆内存大小可以通过以下 JVM 参数来配置:
- -Xms:设置堆的初始大小。
- -Xmx:设置堆的最大大小。
- -XX:NewRatio:设置年轻代与老年代的比例。
- -XX:SurvivorRatio:设置 Eden 区与 Survivor 区的比例。
- -XX:MaxMetaspaceSize:设置元空间的最大大小(Java 8 及之后)。
堆内存分配和回收的过程
- 对象创建:
- 当程序中使用 new 关键字创建对象时,内存首先分配在 Eden 区。
- Minor GC:
- 当 Eden 区满了,JVM 触发 Minor GC,回收不再使用的对象。存活下来的对象会被移到 Survivor 区(S0 或 S1)。
- 当对象在 Survivor 区存活多次后(通常为 15 次 GC,但可以通过 -XX:MaxTenuringThreshold 调整),这些对象会被移动到老年代。
- Full GC:
- 当老年代被填满时,会触发 Full GC。Full GC 是对整个堆(年轻代和老年代)进行垃圾回收,回收那些不再被引用的对象。这是一个相对耗时的操作,可能会导致应用暂停(即STW:Stop-The-World),所以 Full GC 频率需要尽量减少。
虚拟机栈(JVM Stacks)
在 JVM(Java Virtual Machine)中,虚拟机栈(Java Virtual Machine Stack,简称 JVM 栈)是每个线程在执行 Java 程序时创建的私有内存区域。它负责管理 Java 方法的执行,存储方法调用时的局部变量、操作数栈、动态链接、方法出口等。每个线程都有自己独立的虚拟机栈,因此线程之间的栈空间是不共享的。
虚拟机栈是用来保存线程的 栈帧(Stack Frame)的,每个方法在执行时都会创建一个栈帧。每个线程的虚拟机栈由多个 栈帧 组成,一个栈帧对应一个正在执行的 Java 方法。当一个方法被调用时,JVM 会将栈帧压入当前线程的虚拟机栈。当方法执行完成后,对应的栈帧会从栈中弹出。
栈帧主要包括:
- 局部变量表:存储方法的局部变量,包括方法参数和在方法体内定义的变量。是一组用于存放方法参数和局部变量的数组,以槽(Slot)为单位存储。一个 Slot 可以存储一个 int、float 等基本类型,或者引用类型的变量。long 和 double 类型占用两个 Slot。
- 操作数栈:用于方法执行中的各种临时数据存储,是计算过程中的“工作区”。在方法执行过程中,用来存放中间计算的结果和参与运算的操作数。类似于一个后进先出的栈。操作数栈的大小是在编译期间确定的,每个栈帧的操作数栈容量也由编译器确定。
- 动态链接:用于支持方法调用时的动态连接,每个栈帧中包含了一个指向当前方法所属的类的运行时常量池的引用。这个引用用于实现 方法调用 时的动态连接,具体来说是将常量池中的符号引用转换为方法的实际调用地址。
- 方法返回地址:用来存放方法执行完毕后需要返回的地址,以便于返回到上一个栈帧继续执行。
特点
- 线程私有:每个线程在创建时,都会创建一个虚拟机栈。栈是线程私有的,不会在线程之间共享。
- 栈的生命周期与线程一致:线程创建时分配虚拟机栈,线程结束时虚拟机栈也会被销毁。
- 栈的大小:虚拟机栈的大小可以通过 JVM 参数设置,通常使用 -Xss 参数设置每个线程的栈大小(例如:-Xss1m 设置每个线程的栈大小为 1 MB)。栈的大小影响到线程的深度(即一个线程可以调用多少次方法,特别是递归调用的深度)。
- StackOverflowError:当线程的调用深度超过虚拟机栈的限制时,会抛出该异常。通常是因为方法调用过深(例如递归方法没有合适的退出条件)。因此需要合理设置虚拟机栈的大小。
程序计数器(PC Register)
在 JVM 中,程序计数器(Program Counter Register,简称 PC 寄存器)是每个线程私有的一个小内存区域,它记录了当前线程执行的字节码指令的地址。由于 JVM 是多线程的,每个线程都需要独立执行自己的指令,因此每个线程都有一个独立的程序计数器。它的主要作用是在线程切换时能够恢复到正确的执行位置。
程序计数器的作用
- 记录当前线程执行的字节码指令地址:程序计数器用来存放当前线程正在执行的 字节码指令的地址,每当一条字节码指令被执行完,程序计数器会更新为下一条即将执行的指令地址。
- 支持线程切换:JVM 采用 时间片轮转 的方式进行线程切换。为了保证线程恢复后可以继续正确执行代码,每个线程都有自己的程序计数器。当线程切换时,当前线程的执行状态(包括程序计数器的值)会被保存,切换回来时可以从该位置继续执行。
- 处理 Java 和 Native 方法:对于正在执行 Java 方法 的线程,程序计数器存储的是正在执行的字节码指令地址;而对于 本地方法(Native Method),程序计数器则为空(undefined),因为本地方法不通过字节码执行。
程序计数器的特点
- 线程私有:每个线程都有自己独立的程序计数器,彼此之间不共享。
- 生命周期与线程一致:程序计数器的生命周期与线程相同,线程创建时分配,线程结束时销毁。
- 唯一一个不会出现 OutOfMemoryError 的区域:与其他内存区域(如堆、方法区、栈)不同,程序计数器是一个非常小的区域,它不会发生内存溢出。
程序计数器的意义
- 线程隔离:程序计数器为每个线程提供了独立的指令记录机制,这对于多线程并发执行至关重要,确保每个线程能够独立执行自己的代码,而不影响其他线程。
- 线程调度的支持:程序计数器为线程调度提供了支持,当发生线程切换时,程序计数器记录了线程执行的具体位置,能够在线程恢复时继续从正确的位置执行。
本地方法栈(Native Method Stack)
在 JVM 中,本地方法栈(Native Method Stack)是为执行 本地方法(Native Methods)提供支持的内存区域。本地方法是使用其他编程语言(如 C 或 C++)编写的代码,这些代码可以直接与底层操作系统或硬件进行交互,通常通过 JNI(Java Native Interface)调用。它具有以下特点:
- 本地方法栈(Native Method Stack)用于支持本地方法的执行:存放 Native 方法调用时的局部变量、操作数栈、返回地址等。
- 与 Java 虚拟机栈类似,但专注于本地方法,例如通过 JNI 调用 C、C++ 等非 Java 代码。
- 线程私有:每个线程都有自己的本地方法栈。
- 栈溢出异常:可能抛出 StackOverflowError 或 OutOfMemoryError。
- 可选的存在:并非所有 JVM 实现都支持本地方法栈,一些 JVM 实现可能将本地方法栈与 JVM 栈合并。
- 生命周期与线程相同:本地方法栈的生命周期与线程一致,在线程创建时分配,线程结束时销毁。
本地方法栈与 JVM 虚拟机栈类似,但它为调用本地方法服务。它主要负责管理本地方法的调用状态和执行。其作用包括:
- 存储本地方法执行的上下文:在执行本地方法时,本地方法栈存储相关的局部变量和执行状态。
- 与 JNI 一起工作:Java 本地接口(JNI)用于调用非 Java 代码,本地方法栈在这其中负责管理与本地代码交互的细节。
- 桥接底层系统资源:通过本地方法,Java 程序可以调用操作系统提供的底层资源(如文件系统、网络设备、图形界面等)。
本地方法栈与 JVM 栈的区别
- JVM 栈 用于管理 Java 方法的执行,每个 Java 方法调用时都会在 JVM 栈中生成一个栈帧来存储局部变量和操作数栈等信息。
- 本地方法栈 用于管理本地方法的执行,执行本地方法时,本地方法栈记录本地方法的执行状态和局部变量。对于调用本地代码的场景,JVM 栈和本地方法栈会配合使用。
本地方法栈可能会遇到以下异常
- StackOverflowError:当本地方法栈的调用层次过深,栈空间不足时,会抛出此异常。这个与 JVM 栈的 StackOverflowError 类似,通常出现在递归调用或大量本地方法调用的情况下。
- OutOfMemoryError:如果本地方法栈无法申请到足够的内存,JVM 会抛出 OutOfMemoryError。这种情况通常是在栈的初始大小设置过小或系统内存不足时发生。
本地方法栈的工作流程如下:
- 当 JVM 调用 Java 方法时,会使用 JVM 栈进行栈帧管理;
- 当 Java 方法调用本地方法时,JVM 切换到本地方法栈,将控制权交给本地方法栈,负责管理本地方法的调用状态。调用结束后,返回到虚拟机栈,继续执行 Java 方法。
Java 可以通过 JNI 调用本地方法,以下是一个简单的本地方法调用示例:
public class NativeMethodExample {// 声明一个本地方法public native void nativeMethod();static {// 加载本地方法库System.loadLibrary("NativeLib");}public static void main(String[] args) {NativeMethodExample example = new NativeMethodExample();example.nativeMethod(); // 调用本地方法}
}
在上述代码中,nativeMethod() 是一个本地方法,通过 System.loadLibrary() 加载与之对应的本地方法库(例如,C 或 C++ 编写的动态链接库)。当 nativeMethod() 被调用时,JVM 将切换到本地方法栈进行执行。
JVM 提供了一些参数用于调整本地方法栈的大小,尽管不同的 JVM 实现可能略有不同。通过合理的参数调整,可以避免内存不足或栈溢出异常。
垃圾回收算法和垃圾回收器
在 JVM 中,垃圾回收(Garbage Collection,GC)是自动管理内存的机制,旨在回收不再使用的对象,释放内存资源。Java 提供了多种垃圾回收算法和垃圾回收器,以适应不同的应用场景和需求。
垃圾回收算法
- 标记-清除算法(Mark-Sweep)
- 原理:该算法分为两个阶段:
- 标记阶段:从根对象开始,遍历所有可达对象并标记它们。
- 清除阶段:扫描整个堆,回收未被标记的对象。
- 优点:简单有效,能够处理对象的循环引用。
- 缺点:清理后会产生内存碎片,可能导致后续的内存分配失败。
- 原理:该算法分为两个阶段:
- 标记-整理算法(Mark-Compact)
- 原理:与标记-清除算法类似,但在清除阶段会整理存活对象,将它们移动到堆的一端,并更新引用地址。
- 优点:避免了内存碎片问题,适合老年代。
- 缺点:移动对象需要更新引用,开销较大。
- 复制算法(Copying)
- 原理:将存活的对象从一块内存区域(源区)复制到另一块内存区域(目标区),清理源区。
- 优点:高效地回收内存,没有内存碎片。
- 缺点:需要分配两块内存,适用于年轻代,且内存利用率较低(通常只用到一半)。
- 分代收集算法(Generational Collection)
- 原理:基于对象生命周期的特点,将堆分为年轻代和老年代。年轻代中的对象经过多次垃圾回收后,晋升到老年代。
- 优点:提高了垃圾回收的效率,因为大多数对象都是短生命周期的。
- 缺点:需要处理对象晋升的逻辑,复杂度略高。
JVM 提供了多种垃圾回收器,适用于不同的场景和需求:
- Serial GC
- 垃圾回收算法:标记-清除(Mark-Sweep) 和 复制算法(Copying)
- 原理:
- 对于年轻代使用 复制算法,将存活对象从 Eden 区复制到 Survivor 区。
- 对于老年代使用 标记-清除算法,通过标记不再使用的对象并清除它们。
- 类型:单线程的垃圾回收器。
- 特点:在进行 GC 时,会暂停所有应用线程(STW,Stop-The-World),适合单处理器系统。
- 适用场景:适用于小型应用或内存较小的应用。
- Parallel GC
- 垃圾回收算法:标记-清除-整理算法(Mark-Compact) 和 复制算法(Copying)
- 原理:
- 对年轻代使用 复制算法,并行地回收对象,将存活的对象复制到 Survivor 区。
- 对老年代使用 标记-整理算法,在标记完成后整理堆内存,避免内存碎片。
- 类型:多线程的垃圾回收器。
- 特点:通过多线程并行进行垃圾回收,适合多核处理器系统。可以通过参数调整并行度。
- 适用场景:适用于高吞吐量的应用。
- CMS(Concurrent Mark-Sweep)GC
- 垃圾回收算法:标记-清除算法(Mark-Sweep)
- 原理:
- CMS 是针对老年代的回收器,分为四个阶段:初始标记(STW)、并发标记、重新标记(STW)和并发清除。
- 年轻代使用 复制算法 进行回收。
- 类型:并发标记清除垃圾回收器。
- 特点:在垃圾回收过程中,应用线程仍然可以运行。分为标记阶段和清除阶段,后续执行部分工作与应用线程并发进行。
- 适用场景:适合对响应时间敏感的应用,但可能导致内存碎片。
- G1(Garbage-First)GC
- 垃圾回收算法:标记-整理算法(Mark-Compact) 和 复制算法(Copying)
- 原理:
- G1 将堆分为多个区域(Region),每个区域可以存放年轻代或老年代对象。
- 对年轻代使用 复制算法。
- 对老年代使用 标记-整理算法,优先回收垃圾最多的区域。
- 在 Full GC 时,G1 使用全局的 标记-整理算法。
- 类型:分代垃圾回收器。
- 特点:将堆划分为多个区域(Region),优先回收垃圾最多的区域。适用于大内存应用,支持并行和并发回收。
- 适用场景:适合低延迟和大内存的应用,能够提供可预测的暂停时间。
- ZGC(Z Garbage Collector)
- 垃圾回收算法:标记-整理算法(Mark-Compact) 和 并发回收
- 原理:
- ZGC 的垃圾回收过程分为:并发标记、并发重新定位和并发清理。
- ZGC 的最大特点是大部分工作与应用线程并发执行,最大 GC 暂停时间一般不会超过 10 毫秒。
- 它主要使用标记-整理算法,通过颜色指针(Colored Pointers)来标记对象的状态,进行内存整理时对象会重新定位到新的内存区域。
- 类型:低延迟垃圾回收器。
- 特点:支持大堆和并发回收,极大减少了 GC 暂停时间(通常不超过 10ms)。
- 适用场景:适合对延迟非常敏感的大型应用。
- Shenandoah GC
- 垃圾回收算法:标记-整理算法(Mark-Compact) 和 并发回收
- 原理:
- Shenandoah 和 ZGC 类似,垃圾回收的主要阶段与应用线程并发执行。
- 与 G1 相比,Shenandoah 也将堆划分为多个区域,但它的目的是在最短的时间内回收任何区域的内存。
- Shenandoah 使用的是并发标记和并发整理算法。
- 类型:低延迟垃圾回收器,类似 ZGC。
- 特点:通过并发回收和混合空间管理,旨在降低 GC 暂停时间。
- 适用场景:适用于需要低延迟的应用,尤其是大堆内存。
选择合适的垃圾回收器可以显著提高 Java 应用程序的性能。以下是一些常用的 JVM 参数,用于选择和配置垃圾回收器:
- -XX:+UseSerialGC:使用 Serial GC。
- -XX:+UseParallelGC:使用 Parallel GC。
- -XX:+UseConcMarkSweepGC:使用 CMS GC。
- -XX:+UseG1GC:使用 G1 GC。
- -XX:+UseZGC:使用 ZGC。
- -XX:+UseShenandoahGC:使用 Shenandoah GC。
在 Java 中,GC Root(垃圾回收根对象)是垃圾回收器进行内存管理的重要起点,任何从 GC Root 可达的对象都不会被垃圾回收。Java 使用 可达性分析算法(Reachability Analysis Algorithm)来确定哪些对象可以被回收,而可达性的判断始于 GC Roots。
以下是 Java 中哪些对象可以充当 GC Root:
虚拟机栈中的引用对象
- 描述:方法执行时,局部变量表中的所有引用类型变量(局部变量、方法参数等)都可以作为 GC Root。
- 实例:当方法调用时,局部变量表中的对象引用始终可达,JVM 不会回收这些对象,直到方法结束后局部变量表被销毁。
public void exampleMethod() {Object obj = new Object(); // 局部变量 obj 是 GC Root// do something...
}
方法区中的静态变量
- 描述:类的静态属性(static 修饰的变量)会随着类的加载进入方法区,并且静态变量会一直存在于内存中,直到类被卸载。因此,所有的静态变量也是 GC Root。
public class ExampleClass {private static Object staticObject = new Object(); // staticObject 是 GC Root
}
方法区中的常量
- 描述:常量(如 final 修饰的常量)在类加载时就已经被初始化,它们存在于方法区中,可以作为 GC Root。
public class ExampleClass {private static final Object constantObject = new Object(); // constantObject 是 GC Root
}
本地方法栈中的 JNI 引用
- 描述:JNI(Java Native Interface) 是 Java 调用本地(非 Java)代码的机制。在 JNI 中使用的引用也是 GC Root。JVM 通过本地方法栈来管理 JNI 的本地引用。
- 例子:当 Java 调用 C/C++ 代码时,通过 JNI 持有的对象引用。
活跃的线程对象
- 描述:所有当前正在执行的线程对象也是 GC Root,线程不被垃圾回收器回收,直到它们运行结束。
Thread thread = new Thread(() -> {Object obj = new Object(); // thread 是 GC Root,持有 obj 的引用// do something...
});
thread.start();
Java 虚拟机内部的 GC Root
- 描述:JVM 内部的一些系统级对象,如类加载器(ClassLoader)等,也可以作为 GC Root,通常这些对象与应用的执行息息相关。
JMX Beans、JVMTI 中的注册对象
- 描述:通过 JMX(Java Management Extensions)管理的 MBeans 对象,以及通过 JVMTI(Java Virtual Machine Tool Interface)注册的对象,也可以作为 GC Root,因为 JVM 需要对这些对象进行管理和监控。
判断对象是否可以被回收的方法
引用计数法(Reference Counting)
- 每个对象都维护一个引用计数器,当有一个地方引用该对象时,计数器加一;当引用失效时,计数器减一。
- 当计数器的值为零时,说明该对象不再被引用,系统就会认为它是垃圾,可以被回收。
- 实现简单,效率较高,能快速判断对象是否可以被回收。
- 循环引用问题:如果两个对象互相引用(形成循环依赖),它们的引用计数器不会为零,即使它们都无法被访问,引用计数法也无法回收它们。
可达性分析法(Reachability Analysis)
- JVM 采用可达性分析算法来判断对象是否可以被回收。这个方法从一组称为 GC Roots 的根对象开始,沿着引用链进行遍历,能够到达的对象被认为是“存活”的,无法到达的对象被认为是不可达的,可以被回收。
- 如果某个对象在从 GC Root 的引用路径上是不可达的,说明它可以被回收。
- 没有循环引用问题:因为是通过可达性分析来判断对象是否可以回收,循环引用不会影响对象的回收。
- 更加准确,现代垃圾回收器大多基于这种方法。
引用类型
强引用(Strong Reference)
- 这是最常见的引用类型。通过正常的赋值创建的引用,只要有强引用指向一个对象,垃圾回收器就不会回收该对象。
Object obj = new Object(); // obj 是一个强引用
- 特点:
- 垃圾回收:强引用所指向的对象在任何情况下都不会被垃圾回收。只有当引用失效时,垃圾回收器才会考虑回收这个对象。
- 如果一个对象被强引用所引用,即使内存不足,JVM 也不会回收它。
- 适用场景:强引用适用于对必须存在的对象进行引用,比如大多数普通对象的引用方式。
软引用(Soft Reference)
- 用于描述一些还有用但并非必需的对象,内存不足时会回收这些对象。软引用可以通过 SoftReference 类来实现。
SoftReference<Object> softRef = new SoftReference<>(new Object());
- 特点:
- 垃圾回收:当 JVM 发现内存不足时,会回收软引用指向的对象,避免内存溢出(OOM)。
- 软引用常用于实现内存敏感的缓存。例如缓存中存储的数据在内存充足时保留,当内存不足时进行回收。
- 适用场景:适用于缓存设计。当系统内存充足时,缓存数据不会被回收,但在内存不足时,缓存数据会被回收以避免 OOM。
弱引用(Weak Reference)
- 用于描述非必需对象,GC 扫描时一旦发现只有弱引用指向的对象就会回收。弱引用可以通过 WeakReference 类来实现。它用于描述非必须的对象。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
- 特点:
- 垃圾回收:无论内存是否充足,垃圾回收器在进行可达性分析时,只要发现对象只被弱引用所引用,便会立即回收该对象。
- 弱引用通常用于实现规范化映射(canonicalizing mappings),例如 WeakHashMap,用来处理缓存或对象池中的弱引用对象。
- 适用场景:适用于那些希望对象在不被强引用时可以随时被回收的场景,比如弱引用缓存,避免对象长时间占用内存。
虚引用(Phantom Reference)
- 最弱的引用,不能通过虚引用获取对象实例,唯一的作用是能在对象被回收时收到系统通知。虚引用可以通过 PhantomReference 类来实现。
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue);
- 特点:
- 垃圾回收:虚引用的存在主要用于跟踪对象的生命周期,它不能阻止对象被回收。与虚引用关联的对象在垃圾回收时被标记为可回收,回收之前会将虚引用对象加入到一个 ReferenceQueue 队列中。
- 虚引用与 ReferenceQueue 联合使用,主要用于在对象被回收时进行一些后续处理,比如资源释放。
- 适用场景:虚引用适用于管理直接内存的回收、监控对象生命周期或执行对象销毁前的清理工作。例如,当使用 DirectByteBuffer 时,可以通过虚引用在对象被回收前执行内存释放操作。
Younggc 为什么比 fullgc 快很多
在 JVM 中,Young GC(Minor GC)比 Full GC 快很多,主要是因为两者在内存区域、回收对象的数量、算法复杂度等方面存在本质上的区别。
- 内存区域的区别
- Young GC(Minor GC):只发生在新生代(Young Generation)。新生代分为三个区域:Eden 区和两个Survivor 区(S0 和 S1)。当 Eden 区填满时,JVM 会触发 Young GC,回收新生代的短命对象(大多数对象在创建后很快就会被回收)。新生代的区域较小,通常只包含一些存活时间较短的对象,所以回收的时间较短。
- Full GC:涉及整个堆内存,包括新生代、老年代(Old Generation)以及永久代(Metaspace)。Full GC 会回收整个堆中的所有对象,包括长寿命的对象,这些对象通常分布在老年代。老年代区域较大,回收时需要扫描和处理的对象更多,涉及到的区域更广。
- 回收对象的数量和对象生命周期
- Young GC:新生代主要存储短生命周期的对象,大多数对象在进入 Eden 区后很快就会变成垃圾。由于新生代的大部分对象都可以很快被回收,存活对象较少,因此 Young GC 回收速度较快。
- Full GC:在 Full GC 中,除了新生代的对象外,老年代中的长生命周期对象也需要被回收。由于老年代中存放了很多长期存活的对象(甚至包括存活了多个 GC 周期的对象),需要花费更多时间去检查这些对象是否可以被回收。老年代的对象比较多、比较稳定,垃圾回收的复杂度也更高。
- 垃圾回收算法的复杂度
- Young GC:新生代通常采用复制算法(Copying Algorithm),即将存活的对象从 Eden 区和一个 Survivor 区复制到另一个 Survivor 区。复制算法的特点是简单、高效,只需要扫描存活的对象,未存活的对象直接被清除,因此回收速度很快。
- Full GC:老年代通常采用的是标记-清除算法(Mark-Sweep)或标记-整理算法(Mark-Compact)。这些算法首先需要标记出所有的存活对象,然后再执行清除或整理。相比复制算法,标记-清除和标记-整理算法的执行过程复杂得多,尤其是标记和整理阶段会导致 Full GC 变慢。
- GC 频率和触发条件
- Young GC:新生代空间较小,Eden 区填满时频繁触发 Young GC,但因为新生代回收的是短命对象,并且区域小,所以尽管频繁发生,单次执行的时间较短。
- Full GC:Full GC 触发的条件更为复杂,通常是在老年代空间不足时触发。Full GC 的开销大,JVM 会尽量避免频繁进行 Full GC。
- GC 停顿时间
- Young GC:停顿时间较短,因为回收的新生代区域较小,存活的对象少,复制算法效率高。
- Full GC:停顿时间较长,回收整个堆内存,尤其是涉及到标记和整理阶段,老年代中对象的数量和生命周期都较长,导致停顿时间长。
- 内存整理(Compaction)
- Young GC:因为采用的是复制算法,在 Young GC 中不存在内存碎片的问题。新生代中没有使用的内存会被连续的清理和整理。
- Full GC:老年代在标记-清除算法后可能会产生内存碎片。如果老年代存在内存碎片,则需要进行内存整理(Compaction),这会导致回收耗时增加。内存碎片会影响大对象的分配,因为即使有足够的总内存,但由于碎片化,可能没有足够连续的空间来存储大对象。
Minor GC(Young GC)的触发条件
- Eden 区满:当新生代中的 Eden 区被填满时,JVM 会触发 Minor GC。这是最常见的触发条件。JVM 会检查新生代中的对象,回收那些不再使用的短生命周期对象。
- 手动调用:虽然不推荐,但可以通过 System.gc() 手动请求垃圾回收,可能会导致 Minor GC 的发生。
Full GC(Major GC)的触发条件
- 老年代满:当老年代的空间不足以容纳新分配的对象时,JVM 会触发 Full GC。这是 Full GC 最常见的触发条件。
- 永久代(Metaspace)满:在 Java 8 之前,Java 使用永久代来存放类的元数据。如果永久代满了,会触发 Full GC。在 Java 8 之后,永久代被 Metaspace 替代,Metaspace 的满也是触发 Full GC 的条件之一。
- Minor GC 后老年代未能释放足够内存:当进行 Minor GC 后,如果老年代没有足够的空间来容纳新对象,JVM 会触发 Full GC。
- 调用 System.gc():通过调用 System.gc(),JVM 会建议执行 Full GC,尽管并不保证会执行。
- JVM 参数设置:一些 JVM 参数设置可能会影响 Full GC 的触发,如 -XX:+UseG1GC 或其他垃圾收集器的特定配置。
在生产环境中,JVM 调优是确保 Java 应用程序性能和稳定性的重要步骤。调优的目标通常是减少垃圾回收的时间、降低内存使用和提高应用程序的吞吐量。以下是一些常见的 JVM 调优策略和方法:
- 选择合适的垃圾收集器,具体命令如上
- 调整堆内存大小,通过调整堆内存的大小,可以控制应用程序的性能。
- 设置初始堆大小:-Xms512m
- 设置最大堆大小:-Xmx2048m
- 设置年轻代大小:-Xmn256m
- 一般推荐将初始堆和最大堆的比值设置为 1:2 或 1:3。
- 调整垃圾收集参数
- 设置新生代和老年代的比例:-XX:NewRatio=3 # 新生代与老年代的比例
- 设置 Survivor 区的大小:-XX:SurvivorRatio=8 # Eden 区与 Survivor 区的比例
- 设置最大 GC 停顿时间(对于 G1 GC):-XX:MaxGCPauseMillis=200
- 定期监控和分析 JVM 的运行状态,使用各种工具来观察性能和内存使用情况。
- JVisualVM:Java 自带的可视化监控工具,可以用来查看内存、线程、CPU 使用情况。
- JConsole:用于监控 Java 应用的图形界面工具。
- GC 日志:启用 GC 日志以分析垃圾收集的性能,-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log。
- 使用 Java Flight Recorder:这是一个强大的性能监控工具,可以提供深入的性能分析。
- 有时,代码的优化可以显著减少内存使用和垃圾回收的压力。
- 减少对象创建:尽量复用对象,避免频繁创建短命对象。
- 使用合适的数据结构:选择合适的集合类,例如 ArrayList vs LinkedList,并根据需求选择合适的实现。
- 避免内存泄漏:定期检查代码中是否存在内存泄漏,例如未清理的缓存、静态集合中的对象引用等。
- 设置线程数,在多线程应用中,合理配置线程数可以提高性能。
- 设置最大线程数(取决于应用和服务器的具体情况)。-XX:ParallelGCThreads=4 # 设置并行 GC 线程数`
- 使用 JDK 8 及之后的版本的特性
- Metaspace:Java 8 之后,类元数据存储在本地内存中,避免了旧版本中永久代的限制。可以通过设置 Metaspace 大小来优化性能。-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m
- 测试与迭代:调优是一个迭代过程。在生产环境中,测试是非常重要的步骤:
- 负载测试:在类似生产环境中进行负载测试,以观察系统在高负载情况下的表现。
- 逐步调整:每次只调整一个参数,观察性能变化,再进行下一步调整。
Full GC 排查
在生产环境中,排查 Java 应用的 Full GC 问题是确保系统稳定性和性能的关键步骤。以下是一些有效的排查方法和工具:
- 启用 GC 日志:启用 GC 日志可以帮助你分析 Full GC 的发生频率、持续时间和触发原因。
-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
- 如果是 Java 9 及以上版本,可以使用以下参数:
-Xlog:gc*:file=gc.log:time
- 分析 GC 日志:使用工具或脚本分析生成的 GC 日志,查找 Full GC 的详细信息。
- GCViewer:一个可视化工具,可以帮助分析 GC 日志。
- GCEasy:一个在线工具,可以上传 GC 日志进行分析。
- 通过这些工具,你可以查看 Full GC 的时间、频率、回收的内存量以及各个阶段的耗时。
- 监控应用性能:使用监控工具观察应用的性能指标,找出与 Full GC 相关的趋势。
- JVisualVM:Java 自带的可视化监控工具,可以查看内存使用情况、线程情况和 CPU 使用情况。
- JConsole:可监控 Java 应用的性能。
- Prometheus + Grafana:可以实时监控 JVM 的指标,包括 GC 相关的指标。
- 检查内存配置:确认 JVM 的内存配置是否合理,避免内存不足导致频繁的 Full GC。
- 分析应用的内存使用:使用内存分析工具。
- Eclipse Memory Analyzer (MAT):可以帮助分析堆转储文件,找出内存泄漏和长生命周期对象。
- 生成堆转储:jmap -dump:live,format=b,file=heapdump.hprof
- 然后使用 MAT 等工具进行分析。
- 检查对象生命周期:通过分析代码,检查是否存在内存泄漏的情况,可能导致 Full GC 频繁发生。
- 静态集合:检查是否有静态集合中引用的对象未被清理。
- 长生命周期对象:分析老年代中存活的对象,找出那些不再使用的对象。
- 应用程序代码优化
- 减少对象创建:避免频繁创建短命对象。
- 使用合适的数据结构:根据需要选择合适的集合类。
- 测试与调整
- 负载测试:在生产环境中进行负载测试,观察 Full GC 的发生情况。
- 逐步调整参数:调整 JVM 参数后,观察效果,逐步进行调整。
- 查看 JVM 版本和参数:确保使用的是最新的稳定版本,并查看 JVM 的启动参数,某些参数可能会影响 GC 行为。
- 根据 GC 日志中的信息,识别 Full GC 的原因,如:
- 老年代不足。
- PermGen/Metaspace 区域不足。
- 对象的存活时间过长。
- 系统内存压力。
JVM GC 日志 是帮助开发人员分析和调优 Java 应用内存管理的重要工具。通过解析 GC 日志,可以了解 JVM 垃圾收集的行为,包括垃圾回收频率、持续时间、回收的内存大小、各代(新生代、老年代、元空间等)的变化情况等。通过以下 JVM 参数可以启用并配置 GC 日志:
-XX:+PrintGCDetails # 打印详细的 GC 日志
-XX:+PrintGCDateStamps # 打印 GC 发生的时间戳
-XX:+PrintGCTimeStamps # 打印 GC 发生的相对时间
-XX:+PrintHeapAtGC # 打印 GC 前后的堆状态
-Xloggc:<file_path> # 将 GC 日志输出到指定文件
java -Xms512m -Xmx1024m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:gc.log MyApplication
Minor GC (新生代 GC) 日志示例
2024-10-11T15:30:24.123+0000: 0.197: [GC (Allocation Failure) [PSYoungGen: 15360K->1984K(19456K)] 15360K->2000K(62976K), 0.0043510 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
- 时间戳:2024-10-11T15:30:24.123+0000 表示 GC 发生的实际时间。
- 相对时间:0.197 表示从 JVM 启动开始经过的时间(秒)。
- GC 类型:GC (Allocation Failure) 表示 GC 触发的原因是内存分配失败。
- Young Generation:PSYoungGen: 15360K->1984K(19456K) 表示 GC 发生时,新生代(Young Generation)的内存使用情况:
- GC 前:新生代占用了 15360K。
- GC 后:新生代占用了 1984K。
- 总空间:新生代的容量是 19456K。
- Heap Usage:15360K->2000K(62976K) 表示整个堆的使用情况:
- GC 前:堆总使用 15360K。
- GC 后:堆总使用 2000K。
- 总容量:堆的总容量是 62976K。
- GC 耗时:0.0043510 secs 表示此次 GC 持续了 4.351 毫秒。
- CPU 时间:user=0.01 sys=0.00, real=0.00 secs 表示:
- 用户态时间(user):0.01 秒。
- 内核态时间(sys):0.00 秒。
- 实际时间(real):0.00 秒。
Full GC 日志示例
2024-10-11T15:31:15.789+0000: 10.456: [Full GC (Allocation Failure) [PSYoungGen: 1024K->0K(19456K)] [ParOldGen: 20480K->18400K(20480K)] 21504K->18400K(39936K), [Metaspace: 3072K->3072K(1056768K)], 0.1234560 secs] [Times: user=0.10 sys=0.02, real=0.12 secs]
- GC 类型:Full GC (Allocation Failure) 表示发生了 Full GC,原因是内存分配失败。
- Young Generation:PSYoungGen: 1024K->0K(19456K) 表示:
- GC 前:新生代占用了 1024K。
- GC 后:新生代占用了 0K。
- 总空间:新生代的容量是 19456K。
- Old Generation:ParOldGen: 20480K->18400K(20480K) 表示:
- GC 前:老年代占用了 20480K。
- GC 后:老年代占用了 18400K。
- 总空间:老年代的容量是 20480K。
- Heap Usage:21504K->18400K(39936K) 表示:
- GC 前:堆使用了 21504K。
- GC 后:堆使用了 18400K。
- 总容量:堆的总容量是 39936K。
- Metaspace:Metaspace: 3072K->3072K(1056768K) 表示 Metaspace 空间的使用量没有变化,依然是 3072K。
- GC 耗时:0.1234560 secs 表示 Full GC 持续了 123.456 毫秒。
- CPU 时间:user=0.10 sys=0.02, real=0.12 secs。
G1 GC 日志示例
2024-10-11T15:32:45.234+0000: 45.678: [GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0211234 secs][Parallel Time: 18.9 ms, GC Workers: 8][Other: 2.2 ms][Eden: 8192.0K(8192.0K)->0.0B(7168.0K) Survivors: 1024.0K->2048.0K Heap: 18.0M(28.0M)->12.0M(28.0M)]
- GC 类型:GC pause (G1 Evacuation Pause) 表示 G1 GC 的 Evacuation Pause(即新生代 GC)。
- GC 耗时:0.0211234 secs 表示 GC 持续了 21.123 毫秒。
- 并行时间:Parallel Time: 18.9 ms, GC Workers: 8 表示 8 个 GC 工作线程花费了 18.9 毫秒。
- Eden 区:Eden: 8192.0K(8192.0K)->0.0B(7168.0K) 表示:
- GC 前:Eden 区使用了 8192K。
- GC 后:Eden 区的内存被清空。
- 总容量:Eden 区从 8192K 调整为 7168K。
- 堆内存:Heap: 18.0M(28.0M)->12.0M(28.0M) 表示:
- GC 前堆使用了 18M。
- GC 后堆使用了 12M。
- 总容量保持不变,28M。
常见的 GC 日志分析
- GC 频率过高:
- 如果 Minor GC 频率过高,可能表明 Eden 区容量不足,可以通过增大新生代空间或调优对象分配策略来优化。
- Full GC 频率过高:
- 如果 Full GC 频繁发生,可能是老年代空间不足或碎片化严重。可以通过增大老年代空间或优化对象的生命周期来减少 Full GC 发生。
- GC 时间过长:
- 如果 GC 持续时间较长,可能影响应用的响应时间。可以通过增加并行 GC 线程数(如 -XX:ParallelGCThreads)或调整垃圾收集器类型(如使用 G1 或 ZGC)来优化。
栈日志
在 JVM 中,栈日志(Stack Trace Logs)是用于记录线程执行过程中的方法调用栈信息的日志文件。栈日志通常在出现异常或错误时生成,并为开发人员和运维人员提供调试和问题诊断的依据。通过分析栈日志,可以了解线程的执行路径、方法调用的顺序、异常发生的位置等信息,从而定位性能瓶颈、线程问题或代码缺陷。栈日志的主要作用是帮助开发者和运维人员分析和解决以下问题:
- 异常定位:当 JVM 抛出异常(如 NullPointerException、ArrayIndexOutOfBoundsException 等)时,会生成栈日志,描述从异常发生点到当前方法调用栈的完整路径。这可以帮助开发者快速定位问题的根源。
Exception in thread "main" java.lang.NullPointerExceptionat com.example.MyClass.myMethod(MyClass.java:10)at com.example.MyClass.main(MyClass.java:5)
通过栈日志可以看到 NullPointerException 是在 MyClass 的 myMethod 方法的第 10 行发生的,而 myMethod 是从 main 方法调用的。
- 线程状态分析:栈日志可以显示每个线程当前的状态(如运行、等待、阻塞等),帮助分析线程问题。例如,通过线程栈信息,可以发现死锁、线程饥饿、线程阻塞等问题。
"Thread-1" prio=5 tid=0x00007f8c28010000 nid=0x2f03 waiting on condition [0x00007f8c9b500000]java.lang.Thread.State: BLOCKED (on object monitor)at com.example.MyClass.synchronizedMethod(MyClass.java:20)- waiting to lock <0x000000076b2e46d0> (a java.lang.Object)- locked <0x000000076b2e4700> (a java.lang.Object)
这里可以看出线程 “Thread-1” 处于 阻塞状态,并且等待获取对象锁。
- 性能分析:栈日志可以记录方法调用的深度和频率。当性能问题(如 CPU 使用率过高或方法调用栈过深)出现时,通过分析栈日志,可以发现哪个方法或代码段占用了过多的资源。
- 内存泄漏定位:栈日志在处理内存泄漏问题时也很有帮助。通过 OutOfMemoryError 相关的栈日志,可以发现哪些对象没有被正确释放,或在哪些地方频繁分配内存。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.example.MyClass.allocateMemory(MyClass.java:25)at com.example.MyClass.main(MyClass.java:10)
这里显示 OutOfMemoryError 是在 allocateMemory 方法中发生的,可以用作内存分析的起点。
栈日志通常在以下情况下生成:
- 异常抛出时:当 Java 程序抛出未捕获的异常时,JVM 会自动生成栈日志,显示异常的原因和发生位置。
- 死锁检测时:如果 JVM 检测到死锁,栈日志会显示参与死锁的线程信息。
- 显式打印栈信息:可以通过程序显式地调用 Thread.dumpStack() 方法打印当前线程的栈日志。
- 性能监控:一些监控工具(如 JVisualVM、JProfiler)可以生成线程的栈信息,用于性能调优和分析。
栈日志的结构通常包括以下几个部分:
- 线程名称:显示线程的名称和优先级。
- 线程状态:显示线程的当前状态(如 RUNNABLE、BLOCKED、WAITING 等)。
- 方法调用栈:记录每个方法的调用顺序,包括类名、方法名和行号。
- 锁信息:如果线程被阻塞或等待锁,栈日志中会包含锁相关的信息。
"main" prio=5 tid=0x00007f8c20002800 nid=0x2c03 runnable [0x00007f8c9b507000]java.lang.Thread.State: RUNNABLEat java.io.FileInputStream.readBytes(Native Method)at java.io.FileInputStream.read(FileInputStream.java:233)at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)at java.io.BufferedInputStream.read(BufferedInputStream.java:345)at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)at java.io.InputStreamReader.read(InputStreamReader.java:184)at java.io.BufferedReader.fill(BufferedReader.java:161)at java.io.BufferedReader.readLine(BufferedReader.java:324)at java.io.BufferedReader.readLine(BufferedReader.java:389)at com.example.MyClass.readFile(MyClass.java:35)at com.example.MyClass.main(MyClass.java:20)
线程的状态可以帮助确定线程当前的执行情况。栈日志中的线程状态通常包括:
- RUNNABLE:线程正在运行或等待 CPU 时间片。
- BLOCKED:线程被阻塞,等待获取某个锁。
- WAITING:线程在等待其他线程的通知(例如通过 Object.wait() 或 Thread.join())。
- TIMED_WAITING:线程在等待一定时间(例如 Thread.sleep() 或带超时的 wait()、join())。
- TERMINATED:线程已经结束。
如何分析线程状态:
- 如果大多数线程处于 RUNNABLE 状态,而 CPU 使用率较高,可能是系统出现了 高负载 或 CPU 密集型操作。
- 如果大量线程处于 BLOCKED 状态,可能是 锁争用 问题,某些线程长时间持有锁,导致其他线程无法继续执行。
- 如果线程处于 WAITING 或 TIMED_WAITING 状态,通常表示线程在等待外部事件(如 I/O 操作或其他线程的通知),可以检查等待时间是否过长。
死锁通常表现为多个线程互相等待彼此持有的锁,导致线程无法继续执行。栈日志中的死锁信息通常表现为:
- 两个或多个线程的状态为 BLOCKED。
- 线程显示 “waiting to lock” 和 “locked” 同时存在,形成循环。
示例死锁日志:
"Thread-1" prio=5 tid=0x00007f8c28010000 nid=0x2f03 waiting for monitor entry [0x00007f8c9b500000]java.lang.Thread.State: BLOCKED (on object monitor)at com.example.MyClass.method1(MyClass.java:20)- waiting to lock <0x000000076b2e46d0> (a java.lang.Object)- locked <0x000000076b2e4700> (a java.lang.Object)"Thread-2" prio=5 tid=0x00007f8c28020000 nid=0x2f04 waiting for monitor entry [0x00007f8c9b507000]java.lang.Thread.State: BLOCKED (on object monitor)at com.example.MyClass.method2(MyClass.java:30)- waiting to lock <0x000000076b2e4700> (a java.lang.Object)- locked <0x000000076b2e46d0> (a java.lang.Object)
锁争用是性能问题的常见来源,当多个线程争夺同一个锁时,可能会导致线程阻塞,降低系统的并发性能。栈日志中的锁争用信息通常显示为:
- 线程处于 BLOCKED 状态。
- 日志中显示 “waiting to lock” 和 “locked” 关键字。
示例锁争用日志:
"Thread-3" prio=5 tid=0x00007f8c28010000 nid=0x2f05 waiting for monitor entry [0x00007f8c9b500000]java.lang.Thread.State: BLOCKED (on object monitor)at com.example.MyClass.synchronizedMethod(MyClass.java:40)- waiting to lock <0x000000076b2e46d0> (a java.lang.Object)"Thread-4" prio=5 tid=0x00007f8c28020000 nid=0x2f06 runnable [0x00007f8c9b507000]at com.example.MyClass.synchronizedMethod(MyClass.java:40)- locked <0x000000076b2e46d0> (a java.lang.Object)
在这个例子中,Thread-3 被阻塞,等待 Thread-4 释放锁。在实际生产环境中,如果发现多个线程被长时间阻塞,可以通过栈日志找到持有锁的线程,从而优化锁的使用,减少争用。
如果栈日志显示了大量线程,尤其是大量线程处于相同状态(如 WAITING 或 TIMED_WAITING),这可能表明系统存在 线程过多 或 线程泄漏 问题。通常的表现是:
- 有成百上千的线程在栈日志中,可能是由于线程池配置不当或没有及时销毁线程。
- 线程的栈信息重复,表明某些线程在进行重复的任务。
这种情况下,可以考虑减少线程池的最大线程数,或者更高效地使用异步任务。
当 JVM 中的 CPU 使用率高时,栈日志可以帮助确认哪些线程消耗了大量的 CPU。通常表现为:
- 多个线程处于 RUNNABLE 状态。
- 线程栈深度较大,且在执行 CPU 密集型任务,如复杂的计算或循环操作。
示例:
"Thread-5" prio=5 tid=0x00007f8c28010000 nid=0x2f07 runnable [0x00007f8c9b500000]java.lang.Thread.State: RUNNABLEat com.example.MyClass.compute(MyClass.java:50)at com.example.MyClass.main(MyClass.java:10)
如何有效地分析 JVM 栈日志
- 关注线程状态:查看是否有大量线程处于 BLOCKED、WAITING 或 TIMED_WAITING 状态。线程的状态信息是栈日志分析的关键。
- 检查锁争用情况:如果线程被阻塞,检查是否有锁争用问题,锁的使用是否合理,锁等待时间是否过长。
- 定位异常:如果程序抛出异常(如 NullPointerException、OutOfMemoryError 等),通过栈日志定位异常发生的位置,确定异常的原因。
- 检查线程数量:观察是否有过多的线程创建,线程数是否超过合理的范围。
- 结合上下文分析:栈日志分析不应孤立进行,结合应用的运行环境(如 CPU、内存使用情况、I/O 状况等)和日志中的异常或性能信息,才能得到更加准确的分析结果。
如何生成 JVM 栈日志
- 使用 jstack 工具:jstack 是一个常用的命令行工具,用于打印 JVM 中所有线程的栈信息。可以通过以下命令生成栈日志:
jstack <pid> > thread_dump.txt
其中 是目标 JVM 进程的 ID。
- 通过 Java 代码生成:可以使用 Thread.getAllStackTraces() 或 Thread.dumpStack() 生成当前线程的栈信息并输出到日志中:
public class StackTraceExample {public static void main(String[] args) {Thread.dumpStack(); // 打印当前线程的栈日志}
}
- 在异常捕获时打印:可以在 catch 块中通过 Exception.printStackTrace() 方法打印异常的栈信息。
try {// 可能抛出异常的代码
} catch (Exception e) {e.printStackTrace(); // 打印栈日志
}
相关文章:

JVM 面试题
Java 虚拟机(JVM)是运行 Java 程序的引擎,它是 Java 语言 “一次编译,处处运行” 的核心技术。JVM 的主要任务是将 Java 字节码(Bytecode)解释成机器码并执行,负责内存管理、线程管理、垃圾回收…...

C语言蓝桥杯2023年省赛真题
文章目录 持续更新中...第一题题目描述输入格式输出格式样例输出提示 2 第二题题目描述 第三题题目描述输入格式输出格式样例输入样例输出 第四题题目描述输入格式输出格式样例输入样例输出提示 第四题题目描述输入格式输出格式样例输入样例输出提示 第五题题目描述输入格式输出…...

Flume基础概念
目录 作用组件构成ClientFlowAgentSourceSinkEvent 和Log4j的区别与定位事务传出流程输入到sourcesource端输入Channel 接收输入到SinkSink输出 作用 Flume可以从各种来源(如日志文件、消息队列、网络数据、文件系统、数据库等)收集数据,并将…...

哈希处理海量数据
接下来我们将以问题的形式来介绍如何用hash处理海量数据。 1.问题1 (位图) 给定100亿个整数,设计算法找到只出现一次的。 1.1问题分析 100亿个整数,一个整数占用4byte,那么就需要约40G左右的空间来存储。显然常见的…...

Go语言基础教程1
Go语言基础教程 目录 变量声明与使用基本数据类型常量切片操作字符串处理指针格式化输出参数 一、变量声明 1.1 基本变量声明 // 标准声明 var variableName variableType// 示例 var age int var name string1.2 变量声明与初始化 // 显式类型声明 var age int 30// 类…...

【每日一道面试题】for与foreach的区别(2024/12/6)
目录 foreach的特点遍历时删除时 foreach 和 for循环遍历数组的差别关于 foreach 和 for 循环的效率问题 首先我们要对foreach有个基本的了解,才能对它们进行区别 foreach的特点 遍历时 用foreach循环去遍历一个数组, 用foreach循环去遍历一个集合&…...

解密时序数据库的未来:TDengine Open Day技术沙龙精彩回顾
在数字化时代,开源已成为推动技术创新和知识共享的核心力量,尤其在数据领域,开源技术的涌现不仅促进了行业的快速发展,也让更多的开发者和技术爱好者得以参与其中。随着物联网、工业互联网等技术的广泛应用,时序数据库…...

React第十一节 组件之间通讯之发布订阅模式(自定义发布订阅器)
组件之间通讯常用方案 1、通过props 2、通过context 3、通过发布订阅模式 4、通过Redux 后面会有专栏介绍 什么情况下使用发布订阅模式 a、当我们想要兄弟组件之间通讯,而共同的父组件中又用不到这些数据时候; b、当多个毫无相关的组件之间想要进行数据…...

Vue 2与Vue 3项目中的屏幕缩放适配:使用vue2-scale-box和vue3-scale-box
🌟 前言 欢迎来到我的技术小宇宙!🌌 这里不仅是我记录技术点滴的后花园,也是我分享学习心得和项目经验的乐园。📚 无论你是技术小白还是资深大牛,这里总有一些内容能触动你的好奇心。🔍 &#x…...

Brain.js(九):LSTMTimeStep 实战教程 - 未来短期内的股市指数预测 - 实操要谨慎
系列的前一文RNNTimeStep 实战教程 - 股票价格预测 讲述了如何使用RNN时间序列预测实时的股价, 在这一节中,我们将深入学习如何利用 JavaScript 在浏览器环境下使用 LSTMTimeStep 进行股市指数的短期预测。通过本次实战教程,你将了解到如何用…...

云计算考试题
1、与SaaS不同的,这种“云”计算形式把开发环境或者运行平台也作为一种服务给用户提供。(B) A、软件即服务 B、基于平台服务 C、基于WEB服务 D、基于管理服务 2、云计算是对(D)技术的发展与运用 A、并行计算 B、网格计算 C、分布式计算 D、三个选项都是 3、Amazon.com公司…...

【设计模式】装饰器模式 在java中的应用
文章目录 1. 引言装饰器模式的定义与设计目的装饰器模式与其他设计模式的比较 2. 装饰器模式的结构组件接口(Component)具体组件(ConcreteComponent)装饰角色(Decorator)具体装饰类(ConcreteDec…...

【kafka】生产者的同步发送和异步发送
Kafka 的生产者端提供了同步发送和异步发送两种方式,适合不同的使用场景和性能需求。 以下是两种发送模式的详细讲解: 同步发送 概念 同步发送是指生产者在发送一条消息后,会阻塞当前线程,等待 Kafka 返回发送结果(…...

8. Debian系统中显示屏免密码自动登录
本文介绍如何在Debian系统上,启动后,自动免密登录,不卡在登录界面。 1. 修改lightDM配置文件 嵌入式Debian系统采用lightDM显示管理器,所以,一般需要修改它的配置文件/etc/lightdm/lightdm.conf,找到[Seat…...

SpringBoot 开源停车场管理收费系统
一、下载项目文件 下载源码项目文件口令: 【前端小程序地址】(3.0):伏脂火器白泽知洞座/~6f8d356LNL~:/【后台管理地址】(3.0):伏脂火器仇恨篆洞座/~0f4a356Ks2~:/【岗亭端地址】(3.0):动作火器智汇堂多好/~dd69356K6r~:/复制口令…...

QT的ui界面显示不全问题(适应高分辨率屏幕)
//自动适应高分辨率 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);一、问题 电脑分辨率高,默认情况下,打开QT的ui界面,显示不全按钮内容 二、解决方案 如果自己的电脑分辨率较高,可以尝试以下方案:自…...

双向链表的模拟实现 —— LinkedList
MyLinkedList类 public class MyLinkedList {// 定义节点类static class Node {int val;Node prev;Node next;public Node() {}public Node(int val) {this.val val;}}// 定义头节点private Node head;// 定义尾结点private Node tail;// 头插public void headInsert(int val…...

速盾:高防cdn预热指定url就只刷新这个吗?
高防CDN预热是指在网站上线或更新之前,将网站内容缓存到CDN节点服务器上,以提高用户访问网站的速度和稳定性。通常,预热可以通过指定URL来进行,而不是刷新整个网站。 预热指定URL的好处是可以选择性地进行缓存刷新,而…...

JDK21新特性
目录 虚拟线程(JEP 444): 顺序集合(JEP 431): 字符串模板(JEP 430): 模式匹配的增强(JEP 440、441以及443): 结构化并发和作用域值…...

json学习
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。它通常用于在服务器和客户端之间交换数据,特别是在 Web 应用中。 JSON 格式基于 JavaScript 对象表示法&#…...

005-mysql常用的名称
语言分类 DDL :数据定义语言 1、线上DDL语句在对表操作,是要锁元数据表的,此时所有的修改类的命令无法正常运行。 2、对大表在高峰期进行DDL操作,可以使用工具:pt-online-schema-change gh-ost 工具(8.0以…...

PostgreSQL和MySQL区别
PostgreSQL 和 MySQL 有以下一些主要区别: 一、功能特性 1. 数据类型支持 - PostgreSQL:支持丰富的数据类型,包括数组、JSON、JSONB、hstore(键值对存储)、范围类型等。例如,可以直接在数据库中存储和查…...

Android笔记(三十四):onCreate执行Handler.post在onResume后才能执行?
背景 偶然发现一个点,就是在onCreate执行Handler.post在onResume后才执行,以下是测试代码 多次运行的结果一致,为什么execute runnable不是在onCreate和onResume之间执行的呢,带着疑问撸了一遍Activity启动流程 关键源码分析 …...

动手学深度学习d2l包M4芯片 gpu加速
conda创建环境 CONDA_SUBDIRosx-arm64 conda create -n ml python3.9 -c conda-forge conda env config vars set CONDA_SUBDIRosx-arm64 conda activate mlpip安装包 pip install --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/n…...

游戏引擎学习第35天
开场介绍 今天的任务是继续改进一个虚拟的瓦片地图系统,使其适合处理更大的世界。我们希望这个系统能管理大范围的游戏世界,其中包含按需存储的小区域。昨天,我们介绍了“内存区域”的概念,用于管理持久性存储。我们计划今天继续…...

Python 3 和 MongoDB 的集成使用
Python 3 和 MongoDB 的集成使用 MongoDB 是一个流行的 NoSQL 数据库,以其灵活的数据模型和强大的查询功能而闻名。Python 3 作为一种广泛使用的编程语言,与 MongoDB 的集成变得日益重要。本文将介绍如何在 Python 3 环境中集成和使用 MongoDBÿ…...

MperReduce学习笔记下
自定义InputFormat合并小文件 案例需求 无论hdfs还是mapreduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案。 案例分析 小文件的优化无非以下几种方式: …...

react + antd desgin 使用form功能时upload,radio,checkbox不能回显的问题
最近使用react开发 遇到form回显的问题 ,处理upload回显的问题,提示 react-refresh:160 Warning: [antd: Upload] value is not a valid prop, do you mean fileList? 查看文档后,在form.item 组件下有一个特殊属性 valuePropName 子节点的值…...

【NLP修炼系列之Bert】Bert多分类多标签文本分类实战(附源码下载)
引言 今天我们就要用Bert做项目实战,实现文本多分类任务和我在实际公司业务中的多标签文本分类任务。通过本篇文章,可以让想实际入手Bert的NLP学习者迅速上手Bert实战项目。 1 项目介绍 本文是Bert文本多分类和多标签文本分类实战,其中多分…...

OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)
前言 1、前面写过一篇 阿里云免费ssl证书申请与部署,大家可以去看下 2、建议大家看完本篇博客,可以再去了解 openssel 命令 openssl系列,写的很详细 一、openssl 安装说明 1、这部分就不再说了,我使用centos7.9,是自…...

YOLOv11改进,YOLOv11添加U-Netv2分割网络中SDI信息融合模块,助力小目标检测
摘要 理论介绍 SDI模块的架构: 平滑卷积(SmoothConv):用于平滑特征图,帮助减少噪声并使得特征更加稳定。Hadamard积:用于在特征图中进行逐元素相乘(点乘),以加强语义信息和细节信息的融合。通道注意力(ChannelAttention):利用通道注意力机制来自动关注重要的特征通…...

flex布局 flex-end为什么overflow无法滚动及解决方法
flex-end为什么overflow无法滚动及解决方法 在使用Flexbox布局时,我们经常使用justify-content和align-items属性来定位子元素。其中,align-items属性用于控制子元素在交叉轴上的位置,例如顶部对齐、底部对齐或居中对齐等。当我们将align-it…...

从ground_truth mask中获取图像的轮廓图
引言 在图像取证领域,主要分为检测和定位两个方面。检测就是判断一张图片是否为伪造图,定位与传统意义上的语义分割任务相近,就是定位伪造像素的区域。如果单纯使用语义分割网络训练,只能获得次优解,而像多任务学习那样…...

Java项目实战II基于微信小程序的旅游社交平台(开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着移动互联网的迅猛发展,旅游已经成为人…...

开源即时通讯与闭源即时通讯该怎么选择,其优势是什么?
在选择即时通讯软件时,应根据企业的经营领域来选择适合自身需求的开源或闭源方案。不同领域对开源和闭源即时通讯的理念存在差异,因此总结两个点简要分析这两种选择,有助于做出更明智的决策。 一、开源与闭源的根本区别在于软件的源代码是否…...

【计算机网络】实验15:VLAN间通信的实现方法“单臂路由”
实验15 VLAN间通信的实现方法“单臂路由” 一、实验目的 加深对VLAN间通信的实现方法“单臂路由”的理解。 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑,并配置好主机的IP地址、子网掩码、默认网关,如图1,2所…...

数据库学习记录04
DDL【数据定义语言】 MySQL命名规则 数据库名不得超过30个字符,变量名限制为29个必须只能包含A-Z,a-z,0-9,_共63个字符不能在对象名的字符间留空格必须不能和用户定义的其他对象重名必须保证你的字段没有和保留字、数据库系统或常用方法冲突保持字段名和类型的一致…...

PDF文件打开之后不能打印,怎么解决?
正常的PDF文件是可以打印的,如果PDF文件打开之后发现文件不能打印,我们需要先查看一下自己的打印机是否能够正常运行,如果打印机是正常的,我们再查看一下,文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…...

A* 算法 是什么?
A*(A-star)算法是一种启发式搜索算法,用于在图或网格中找到从起点到目标的最短路径。它被广泛用于路径规划问题,例如导航、游戏开发中的角色移动,以及机器人路径规划。 1. A 算法的基本概念* A* 算法结合了两种经典搜…...

ORM框架详解:为什么不直接写SQL?
想象一下,你正在开发一个小型的在线书店应用。你需要存储书籍信息、用户数据和订单记录。作为一个初学者,你可能会想:“我已经学会了SQL,为什么还要使用ORM框架呢?直接写SQL语句不是更简单、更直接吗?” 如…...

厘米级高精度RTK手持终端北斗卫星定位手持pda
RTK是一种测量技术叫“载波相位差分技术”,是实时处理两个测量站载波相位观测量的差分方法,将基准站采集的载波相位发给用户接收机,进行求差解算坐标,以此得到高精度坐标。随着技术的不断革新,GPS接收机也由原来只能用…...

Kafka-Connect源码分析
一、上下文 《Kafka-Connect自带示例》中我们尝试了零配置启动producer和consumer去生产和消费数据,那么它内部是如何实现的呢?下面我们从源码来揭开它神秘的面纱。 二、入口类有哪些? 从启动脚本(connect-standalone.sh&#…...

【STM32 Modbus编程】-作为主设备读取保持/输入寄存器
作为主设备读取保持/输入寄存器 文章目录 作为主设备读取保持/输入寄存器1、硬件准备与连接1.1 RS485模块介绍1.2 硬件配置与接线1.3 软件准备2、读保持寄存器2.1 主设备发送请求2.2 从设备响应请求2.3 主机接收数据3、读输入寄存器4、结果4.1 保持寄存器4.2 输入寄存器在前面的…...

Kubesphere上搭建redis集群
Kubesphere上搭建redis集群 版本:redis:6.2.3 1)挂载配置 redis.conf: cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-require-full-coverage no cluster-migration-barrier 1 appendonly yes …...

learn-(Uni-app)跨平台应用的框架
使用 Vue.js 开发所有前端应用的框架,开发者编写一份代码,可发布到iOS、Android、Web(包括微信小程序、百度小程序、支付宝小程序、字节跳动小程序、H5、App等)等多个平台。 跨平台:Uni-app 支持编译到iOS、Android、W…...

target_compile_definitions
这个接口给目标定义的宏,不能像C中定义的宏一样,尝试利用宏进行替换: cmake_minimum_required(VERSION 3.8) project(compile_definitions_pro)add_executable(main_exec src/main.cpp)set(SYSTEM_NAME "") if(CMAKE_SYSTEM_NAME S…...

浏览器同源策略、跨域、跨域请求,服务器处理没、跨域解决方案
目录 什么是同源策略什么是跨域发生跨域时,服务器有没有接到请求并处理响应:(两种情况) 如何解决跨域 什么是同源策略 概念: 同源策略是浏览器的一种安全机制,用于防止恶意网站对用户的敏感数据进行未经授…...

深入理解网络安全等级保护:保障信息安全的关键策略与实践
随着信息技术的飞速发展,网络安全问题日益凸显。为了应对这一挑战,网络安全等级保护制度应运而生,旨在确保不同等级的信息和信息系统的安全。本文将探讨网络安全等级保护的基本概念、重要性及其实践方法。 一、信息安全等级保护的基本概念 1…...

MySQL
InnoDB 引擎底层存储和缓存原理 到目前为止,MySQL 对于我们来说还是一个黑盒,我们只负责使用客户端发 送请求并等待服务器返回结果,表中的数据到底存到了哪里?以什么格式存放的? MySQL 是以什么方式来访问的这些数据&…...

新书速览|循序渐进Node.js企业级开发实践
《循序渐进Node.js企业级开发实践》 1 本书内容 《循序渐进Node.js企业级开发实践》结合作者多年一线开发实践,系统地介绍了Node.js技术栈及其在企业级开发中的应用。全书共分5部分,第1部分基础知识(第1~3章)…...

遗漏的罪行? 腾讯医疗原总经理张猛案引争议
自2023年3月被深圳警方刑拘之后,现年51岁的ZHANGMENG(加拿大籍,中文名:张猛)一直身陷囹圄之中。这位腾讯医疗资讯与服务部(下称:腾讯医疗)原总经理,在2023年11月被深圳检方起诉,被控犯非国家工作人员受贿、职务侵占两宗罪;一年多后,即2024年12月,检方又认为在审理…...

市场监管总局出台“首违不罚”“轻微免罚”清单
新华社北京2月7日电(记者赵文君)为解决社会关注的“小案重罚”和“类案不同罚”问题,市场监管总局7日对外发布《市场监管行政违法行为首违不罚清单(一)》及《市场监管轻微行政违法行为不予处罚清单(一)》。据介绍,在充分考虑违法行…...

人境网新春座谈会在汉举行
大年初七,人境网新春座谈会在汉口卓尔书店开普号茶空间举行,著名作家和学者刘继明、老田出席。座谈会上,刘继明、老田就近期备受关注的DeepSeek谈了自己的看法。刘继明认为,人工智能的突飞猛进将给社会带来深刻影响:一、加速生产力向更高层次发展,财富和各种资源进一步向…...

朝鲜:军事力量强化“不设限”
据央视新闻消息,朝鲜外务省裁军与和平研究所2月2日发表公报。公报称,不久前美国以保证本土和同盟国安全免受所谓敌国变本加厉的“战略威胁”为借口,发表了进一步扩大导弹防卫体系的构想。美国新的导弹防卫体系计划提出了侵略性目标,并在与盟国的共谋勾结…...

肖志夫:特朗普为何钦点马斯克执掌“政府效率部”
2025年1月20日,美国白宫网站发布第47任美国总统唐纳德·特朗普签署的《建立和实施总统的“政府效率部”美国总统之行政命令》指出,设立“政府效率部”,目的是“通过现代化的联邦技术和软件来实现政府效率和生产力的…...

山西省原省长于幼军痛斥官场:有的人连良知都没有!
这是山西省原省长于幼军到中山大学任教后,举办的一次讲座上的内容。讲座中,于幼军谈农村状况时,联系到自己2005年到山西工作时的这样一段往事:我到山西工作,到了大同矿工棚户区。我当时去了几次,有些地方就老不带我去看。我问工人住在哪里啊,他们就不带我去。后来我看到…...