余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌软引用和虚引用适用场景

发表于 2025-04-26 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌软引用和虚引用适用场景

题目详细答案

软引用

软引用主要用于实现内存敏感的缓存。

软引用可以用于缓存那些可以在内存不足时安全回收的对象。例如,图片缓存、数据缓存等场景。在内存充足时,缓存的对象不会被回收;当内存不足时,缓存的对象会被回收以释放内存。这种机制可以在不影响应用程序功能的前提下,最大限度地利用可用内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

public class SoftReferenceCache<K, V> {
private final Map<K, SoftReference<V>> cache = new HashMap<>();

public void put(K key, V value) {
cache.put(key, new SoftReference<>(value));
}

public V get(K key) {
SoftReference<V> ref = cache.get(key);
if (ref != null) {
return ref.get();
}
return null;
}
}

SoftReferenceCache使用软引用来缓存对象,当内存不足时,缓存的对象可能会被回收。

虚引用

虚引用主要用于跟踪对象被垃圾收集器回收的时间,通常用于实现特殊的清理机制。

虚引用可以用于管理那些需要在对象被回收后进行清理的资源,例如直接内存(Direct Memory)、文件句柄等。

当对象被垃圾收集器回收时,虚引用会被放入引用队列(ReferenceQueue),通过处理这个队列,可以执行必要的清理操作。

虚引用可以用于监控对象何时被回收,从而在对象回收时执行一些特定的操作,例如记录日志、更新状态等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceExample {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, refQueue);

obj = null; // 允许 obj 对象被垃圾收集器回收

// 强制垃圾收集
System.gc();

// 检查引用队列
if (refQueue.poll() != null) {
System.out.println("Object has been collected");
// 执行清理操作
}
}
}

当obj被垃圾收集器回收时,phantomRef会被放入refQueue中,可以通过检查refQueue来执行清理操作。

👌Gc root对象都包含哪些?

发表于 2025-04-25 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌Gc root对象都包含哪些?

题目详细答案

什么是GC Root

GC Root是垃圾回收器确定对象是否可达的起始点。在Java中,GC Root是一组特殊的对象,GC Root对象保证了这些对象及其引用链不会被垃圾回收器回收,因为它们是程序的起始点,其他对象通过它们间接可达,它确保了内存中的对象能够正确地被管理和清理,避免内存泄漏和无效引用的问题。

gcroot 对象

虚拟机栈(栈帧中的本地变量表)中的引用:

每个线程都有一个虚拟机栈,栈帧中的本地变量表(Local Variable Table)包含了方法执行过程中用到的所有局部变量。这些局部变量可能包含对对象的引用。

方法区中的类静态变量引用:

方法区中存储了类的元数据,包括类的静态变量。这些静态变量可能引用对象。

方法区中的常量引用:

方法区还包含运行时常量池(Runtime Constant Pool),其中可能有对对象的引用。

本地方法栈中的 JNI(Java Native Interface)引用:

本地方法栈(Native Method Stack)用于本地方法的调用。本地方法可以通过 JNI 引用 Java 对象,这些引用也是 GC Roots。

活动线程:

所有正在运行的线程本身也是 GC Roots。

类加载器:

类加载器本身也是 GC Roots,因为它们负责加载类,而类加载器的引用链可以追溯到所有被加载的类及其静态变量。

系统类:

一些系统级的类,比如java.lang.Thread,java.lang.System等,也被视为 GC Roots。

JNI 全局引用:

JNI 中的全局引用(Global References)也是 GC Roots。

JVM 内部的某些数据结构:

JVM 内部的一些数据结构(如 JIT 编译器生成的代码中的引用)也可能被视为 GC Roots。

👌一次完整的垃圾回收过程是什么样的?

发表于 2025-04-25 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌一次完整的垃圾回收过程是什么样的?

题目详细答案

Jvm 垃圾回收的基本过程可以分为以下三个步骤:

1719147802226-96af4d13-19d6-4cf6-bdc9-c3c028ccd29e.png

垃圾分类

首先我们的 jvm 在进行垃圾回收的过程,需要确定哪些对象是垃圾对象,哪些对象是存活对象。这个类似于我们在做一件事之前的规划。具体的分类方法一般情况下,垃圾回收器会从堆的根节点(如程序计数器、虚拟机栈、本地方法栈和方法区中的类静态属性等),也就是 gc root。开始遍历对象图,标记所有可以到达的对象为存活对象,未被标记的对象则被认为是垃圾对象。进过标记后,分类成功。

垃圾查找

分类后,已经知道了对象所处的一个状态,jvm 会根据分类后对象,先找出所有垃圾对象,以便进行清理。

不同的垃圾收集,其中的查找方式会产生相应的差异。随着现在 jdk 的 升级与发展,还会产生更加高效的算法,后面会有垃圾收集的算法详细介绍。

垃圾清理

标记完成后,进行最后的清理与删除。这里涉及不同的垃圾收集器,清理的方式也不同,常见的有

标记-清除算法,复制算法,标记-整理算法,分代算法。

需要注意的是,垃圾清理可能会引起应用程序的暂停,不同的垃圾回收器通过不同的方式来减少这种暂停时间,从而提高应用程序的性能和可靠性。

常见的垃圾收集器有

Serial GC

Parallel GC

CMS GC(Concurrent Mark Sweep)

G1 GC(Garbage First)

ZGC

👌如何优化减少 FULL GC?

发表于 2025-04-25 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌如何优化减少 FULL GC?

题目详细答案

调整堆内存大小

增加堆内存大小:适当增加堆内存大小,可以减少老年代空间不足的情况,从而减少 Full GC 的发生。可以通过-Xmx和-Xms参数调整最大和最小堆内存大小。

调整新生代大小:适当增加新生代(Young Generation)的大小,可以减少对象晋升到老年代的频率,从而减少老年代的压力。可以通过-XX:NewSize和-XX:MaxNewSize参数调整新生代大小。

调整垃圾收集器参数

根据应用程序的具体需求,调整垃圾收集器的参数,可以优化垃圾收集行为,比如

G1 GC 参数:

-XX:MaxGCPauseMillis=:设置目标最大 GC 暂停时间,G1 GC 会尝试在这个目标时间内完成 GC。

-XX:InitiatingHeapOccupancyPercent=:设置触发混合回收的老年代占用比例。

CMS 参数:

-XX:CMSInitiatingOccupancyFraction=:设置触发 CMS GC 的老年代占用比例。

-XX:+UseCMSInitiatingOccupancyOnly:仅在老年代占用达到设定比例时触发 CMS GC。

优化对象分配和生命周期

减少对象分配和优化对象生命周期,可以减轻垃圾收集器的负担,从而减少 Full GC 的发生:

减少短生命周期对象:尽量减少短生命周期对象的创建,或将其分配在栈上而不是堆上。

缓存和重用对象:使用对象池(Object Pool)缓存和重用对象,减少对象分配和垃圾回收的频率。

避免显式调用System.gc()

显式调用System.gc()会请求 JVM 进行 Full GC,尽量避免在代码中使用System.gc(),除非有充分的理由和必要性。

调整元空间(Metaspace)大小

适当增加元空间大小,可以减少因元空间不足而触发的 Full GC

-XX:MetaspaceSize=:设置初始元空间大小。

-XX:MaxMetaspaceSize=:设置最大元空间大小。

👌如何破坏双亲委派模型

发表于 2025-04-25 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌如何破坏双亲委派模型

题目详细答案

自定义类加载器

通过创建自定义类加载器并覆盖loadClass方法,可以实现不同于双亲委派机制的类加载策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CustomClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 如果是特定的类,不使用双亲委派机制
if (name.startsWith("com.example")) {
// 自定义加载逻辑
byte[] classData = getClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
}
}
// 否则,使用默认的双亲委派机制
return super.loadClass(name);
}

private byte[] getClassData(String className) {
// 实现类加载的逻辑,例如从文件系统或网络加载类字节码
return null;
}
}

通过反射机制

利用反射机制直接操作类加载器的父类加载器,绕过双亲委派机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.reflect.Field;

public class BreakParentDelegation {
public static void main(String[] args) throws Exception {
CustomClassLoader customClassLoader = new CustomClassLoader();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();

// 获取系统类加载器的父类加载器(扩展类加载器)
Field parentField = ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(appClassLoader, customClassLoader);

// 现在系统类加载器的父类加载器被替换为自定义类加载器
Class<?> clazz = Class.forName("com.example.MyClass", true, appClassLoader);
System.out.println(clazz.getClassLoader());
}
}

OSGi 框架

OSGi 框架提供了一种模块化的类加载机制,允许每个模块(Bundle)有自己的类加载器,从而可以打破双亲委派机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// OSGi 中的 BundleActivator 示例
public class MyBundleActivator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
// 在 OSGi 环境中,每个 Bundle 有自己的类加载器
ClassLoader bundleClassLoader = getClass().getClassLoader();
Class<?> clazz = bundleClassLoader.loadClass("com.example.MyClass");
System.out.println(clazz.getClassLoader());
}

@Override
public void stop(BundleContext context) throws Exception {
// 停止 Bundle 时的清理工作
}
}

使用 SPI(Service Provider Interface)

某些服务提供者接口的实现中,可能需要打破双亲委派机制来加载服务实现类。

1
2
3
4
5
6
7
8
9
10
import java.util.ServiceLoader;

public class SPIDemo {
public static void main(String[] args) {
ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service : loader) {
service.execute();
}
}
}

在META-INF/services目录下创建一个文件,文件名为接口的全限定名,文件内容为实现类的全限定名。通过这种方式,JVM 会使用Thread.contextClassLoader来加载服务实现类,从而可以打破双亲委派机制。

👌如何判断对象是否可以被回收?

发表于 2025-04-24 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌如何判断对象是否可以被回收?

口语化回答

引用计数法和可达性分析法是两种不同的内存管理和垃圾回收算法。引用计数法通过维护引用计数器来跟踪对象的引用数量,具有实时性好、简单高效等优点,但存在循环引用等问题;而可达性分析法则通过分析对象的引用关系来判断对象是否可达,从而决定对象是否可以被回收,具有准确性高、效率好等优点,是JVM中常用的垃圾回收算法之一。

题目详细答案

引用计数法

引用计数法(Reference Counting)是一种内存管理技术,用于跟踪对象的引用数量。每个对象都有一个引用计数器,记录着指向该对象的引用数量。

当一个对象被引用时,引用计数器加一;当一个引用被释放时,引用计数器减一。当引用计数器为零时,表示没有任何引用指向该对象,该对象可以被释放,回收其占用的内存。

优点:

实时性好:当没有引用指向一个对象时,该对象可以立即被回收,释放内存资源。

简单高效:引用计数法是一种相对简单的内存管理技术,实现起来较为高效。

无需沿指针查找:与GC标记-清除算法不同,引用计数法无需从根节点开始沿指针查找。

缺点

循环引用问题:当存在循环引用的情况下,对象之间的引用计数可能永远不会为零,导致内存泄漏的发生。

额外开销:每个对象都需要维护一个引用计数器,这会带来一定的额外开销。

不支持并发:在多线程环境下,引用计数法需要进行额外的同步操作,以确保引用计数的准确性,可能导致性能损失。

可达性分析法

可达性分析算法是JVM垃圾回收中的一种算法,它通过分析对象的引用关系,判断对象是否可达,从而决定对象是否可以被回收。

工作原理

  1. GC Roots:在Java中,GC Roots通常包括虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区(静态变量)中引用的对象、本地方法栈中JNI(Native方法)引用的对象等。
  2. 搜索过程:可达性分析算法从GC Roots开始,递归地访问所有可达的对象,并给它们打上标记。这个过程可以使用深度优先搜索(DFS)或广度优先搜索(BFS)等图遍历算法来实现。
  3. 回收判定:如果一个对象到GC Roots没有任何引用链相连(即该对象从GC Roots不可达),则证明该对象是不可用的,可以判定为可回收对象。

特点

准确性:通过从GC Roots开始搜索引用链,可以准确地判断哪些对象是可回收的。

效率:结合现代JVM的优化技术,如增量标记、并发标记等,可以提高可达性分析算法的效率。

灵活性:可达性分析算法可以与不同的垃圾回收策略(如标记-清除、标记-整理等)结合使用,以适应不同的应用场景和硬件环境。

👌方法区和永久代以及元空间有什么区别

发表于 2025-04-23 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌方法区和永久代以及元空间有什么区别

题目详细答案

方法区(Method Area)

方法区是 JVM 运行时数据区的一部分,用于存储类元数据、常量、静态变量、即时编译器编译后的代码等信息,是 JVM 规范的一部分,但规范并未规定其具体实现方式,是所有线程共享的内存区域。

永久代(Permanent Generation, PermGen)

永久代是 HotSpot JVM 在 Java 7 及之前版本中对方法区的一种具体实现。

永久代的内存空间是固定的,默认情况下不能动态扩展,容易导致内存溢出(OutOfMemoryError)。

主要存储类元数据、运行时常量池、静态变量、即时编译器编译后的代码等。

由于固定大小,容易出现内存不足的情况,尤其是在大量动态生成类或使用大量反射的应用中。

元空间(Metaspace)

元空间是 HotSpot JVM 在 Java 8 及之后版本中对方法区的一种新的实现方式,替代了永久代。

元空间使用本地内存(Native Memory)而不是 JVM 堆内存。默认情况下,元空间可以根据需要动态扩展,减少了内存溢出的风险。可以通过 JVM 参数(如-XX:MaxMetaspaceSize)来控制元空间的最大大小。

与永久代类似,元空间也存储类元数据、运行时常量池、静态变量、即时编译器编译后的代码等。由于使用本地内存并且可以动态扩展,元空间更灵活,减少了内存溢出的风险。

对比总结

特性 方法区 (Method Area) 永久代 (PermGen) 元空间 (Metaspace)
定义 JVM 规范的一部分 方法区的实现之一 方法区的实现之一
存储内容 类元数据、常量、静态变量、即时编译器编译后的代码 类元数据、常量、静态变量、即时编译器编译后的代码 类元数据、常量、静态变量、即时编译器编译后的代码
内存类型 JVM 内存的一部分 JVM 堆内存的一部分 本地内存
内存大小 规范未定义 固定大小 动态扩展
垃圾收集 规范未定义 有,但频率较低 有,但频率较低
适用 JVM 版本 所有版本 Java 7 及之前 Java 8 及之后
内存管理 规范未定义 固定大小,容易溢出 动态扩展,减少溢出风险

👌方法的形参在哪块区域?

发表于 2025-04-22 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌方法的形参在哪块区域?

题目详细答案

在Java虚拟机(JVM)中,方法的形参(即方法的参数)在方法调用时存储在栈帧(Stack Frame)中。栈帧是 JVM 栈(Java Stack)中的一个数据结构,每当一个方法被调用时,JVM 会为该方法创建一个新的栈帧。

一个栈帧主要包含以下几个部分:

  1. 局部变量表(Local Variable Array):用于存储方法的局部变量和形参。
  2. 操作数栈(Operand Stack):用于执行字节码指令时的操作数。
  3. 帧数据(Frame Data):包括方法的返回地址、动态链接、方法的调用者等。

方法的形参在方法调用时会被传递到局部变量表中。局部变量表是栈帧的一部分,用于存储方法的局部变量和形参。每个局部变量在局部变量表中的位置是通过索引来访问的,这些索引是从0开始的。

详细示例

假设有以下Java方法:

1
2
3
4
public int add(int a, int b) {
int sum = a + b;
return sum;
}

在调用add(5, 10)时,JVM 会执行以下步骤:

  1. 创建栈帧:为add方法创建一个新的栈帧。
  2. 初始化局部变量表:

a被存储在局部变量表的索引0处。

b被存储在局部变量表的索引1处。

  1. 执行方法体:

int sum = a + b;会在操作数栈上执行,然后将结果存储在局部变量表的索引2处。

  1. 返回结果:将sum的值从局部变量表中取出,并作为方法的返回值。

栈帧结构示意图

1
2
3
4
5
6
7
8
9
10
11
+--------------------+
| 局部变量表 |
|--------------------|
| 索引0: a = 5 |
| 索引1: b = 10 |
| 索引2: sum = 15 |
+--------------------+
| 操作数栈 |
+--------------------+
| 帧数据 |
+--------------------+

在这个过程中,a和b作为方法的形参被存储在局部变量表中。因此,方法的形参在JVM中存储于栈帧的局部变量表中。

👌触发FULL GC的几种情况?

发表于 2025-04-21 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌触发FULL GC的几种情况?

题目详细答案

老年代空间不足

当老年代的空间不足以容纳新的对象时,会触发 Full GC。具体情况包括:

对象晋升:在 Minor GC 过程中,如果 Survivor 区空间不足,存活对象会被晋升到老年代。如果老年代空间不足以容纳这些晋升的对象,就会触发 Full GC。

大对象分配:分配大对象(如大型数组或字符串)时,如果老年代空间不足以分配这些大对象,也会触发 Full GC。

永久代或元空间空间不足

在使用旧版 JVM(如 Java 7 及之前)时,永久代(Permanent Generation)用于存储类的元数据。如果永久代空间不足,会触发 Full GC。在 Java 8 及之后,永久代被元空间(Metaspace)取代,用于存储类的元数据。如果元空间不足,也会触发 Full GC。

调用System.gc()

调用System.gc()方法会显式请求 JVM 进行 Full GC。尽管 JVM 不保证一定会执行 Full GC,但通常情况下会触发一次 Full GC。

CMS垃圾收集器的失败

在使用 CMS 垃圾收集器时,如果并发收集过程中老年代空间不足,CMS 会触发一次 Full GC 作为后备措施。这种情况通常被称为 “promotion failure” 或 “concurrent mode failure”。

JNI 代码导致的内存不足

某些本地代码(JNI)可能会在堆内存中分配大量对象,导致堆内存不足,从而触发 Full GC。

内存碎片

如果堆内存中存在大量碎片,导致无法找到足够大的连续空间来分配新对象,也可能触发 Full GC 以尝试压缩内存和清理碎片。

👌为什么要分Eden和Survivor?

发表于 2025-04-20 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌为什么要分Eden和Survivor?

题目详细答案

在 JVM 中将新生代(Young Generation)分为 Eden 区和两个 Survivor 区(S0 和 S1)的主要原因是为了优化垃圾回收的效率和性能。这种分区策略基于对象的生命周期特点,利用复制算法来减少内存碎片和提高垃圾回收的效率。

优化垃圾回收效率

新生代的垃圾回收通常使用复制算法,这种算法的核心思想是将存活的对象从一个区域复制到另一个区域,而不是在原地进行标记和清除。复制算法的步骤如下:

对象分配:新创建的对象首先分配在 Eden 区。

Minor GC 触发:当 Eden 区填满时,会触发一次 Minor GC。

对象复制:在 Minor GC 过程中,存活的对象会从 Eden 区和当前使用的 Survivor 区(例如 S0)复制到另一个 Survivor 区(例如 S1)。复制完成后,Eden 区和当前使用的 Survivor 区将被清空。

区域交换:两个 Survivor 区在每次 GC 后交替使用。

假设有一个新生代大小为 1 GB,其中 Eden 区占 80%(800 MB),两个 Survivor 区各占 10%(100 MB)。对象首先分配在 Eden 区,当 Eden 区填满时,触发 Minor GC,将存活对象复制到一个 Survivor 区。下次 GC 时,再将存活对象从当前 Survivor 区复制到另一个 Survivor 区。

这种算法的优点是:

减少内存碎片:复制算法通过将存活对象紧密排列在一起,避免了内存碎片的问题。

提高回收速度:复制算法只需要遍历存活对象,而不需要遍历整个内存区域,这使得垃圾回收的速度更快。

优化内存分配

将新生代分为 Eden 区和两个 Survivor 区,能够更好地管理对象的生命周期:

大多数对象生命周期短:大多数新创建的对象很快就会变得不可达并被回收。Eden 区专门用于存储这些短生命周期对象,提高了内存分配和回收的效率。

幸存者对象管理:那些在一次或多次 Minor GC 后仍然存活的对象会被复制到 Survivor 区。通过在两个 Survivor 区之间复制和交换,可以有效管理这些对象的生命周期,直到它们被提升到老年代。

减少 GC 停顿时间

复制算法和分区策略有助于减少 GC 停顿时间(GC Pause Time),提高应用程序的响应速度:

Minor GC 更快速:由于新生代通常较小,并且复制算法只处理存活对象,Minor GC 的停顿时间通常较短。

老年代 GC 减少:通过有效管理新生代的对象,减少了老年代的对象数量和垃圾回收频率,从而减少了 Major GC 或 Full GC 的次数和停顿时间。

👌常见的jvm垃圾收集器有哪些?

发表于 2025-04-20 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌常见的jvm垃圾收集器有哪些?

题目详细答案

按照堆内存分代管理的思想,目前主要的垃圾收集器有:

1719153437245-6b20a86f-da22-4468-a839-248939d27401.png

Serial 和Serial Old收集器

Serial是jvm中最早一批的收集器之一,它是一款单线程收集器。在进行垃圾收集时,需要暂停所有的用户线程(Stop-The-World)

它的设计初衷是为了适应早期的硬件环境和应用场景。在那个时候,硬件配置相对较低,主要特点包括内存容量较小、CPU 单核、并发应用场景相对较少。

基于这些限制条件,Serial 系列的垃圾收集器采用了简单高效、资源消耗最少、单线程收集的设计思路

简单高效:由于硬件资源有限,垃圾回收器需要设计得简单高效,以减少系统资源的占用。Serial 系列的垃圾收集器实现简单,适用于小型应用或者简单的测试场景。

资源消耗最少:考虑到当时硬件资源有限,Serial 系列的垃圾收集器尽可能地减少了对系统资源的占用。通过使用单线程执行垃圾回收操作,避免了多线程切换的开销,从而最大程度地节约了系统资源。

单线程收集:由于早期的硬件环境和应用场景下,并发需求较低,采用单线程收集的设计方案足以满足当时的需求。单线程收集简化了垃圾回收器的实现,并降低了系统复杂性,使得垃圾回收过程更加可控和稳定。

Serial采用复制算法进行垃圾收集,Serial Old 收集器是 Serial 的老年代版本,同样是一个单线程收集器,采用标记整理法进行垃圾收集

Parallel Scavenge 和 Parallel Old收集器

与ParNew类似,也是一款用于新生代的多线程收集器。但Parallel Scavenge的目标是达到一个可控制的吞吐量,而ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间。

随着硬件资源的升级,包括内存空间的增大和 CPU 的多核化,传统的 Serial 垃圾收集器面临着性能瓶颈。由于它采用单线程执行垃圾回收操作,无法充分利用多核 CPU 的优势,导致在处理大内存空间时性能下降,垃圾回收时间变得更长。为了充分发挥多核 CPU 的优势,JVM 推出了 Parallel 收集器系列。Parallel 收集器的设计思想是利用多线程并行执行垃圾回收操作,以提高整个垃圾收集过程的并行度和性能。

Parallel 收集器的核心特点包括

多线程并行执行:Parallel 收集器利用了多核 CPU 的优势,通过多个线程同时执行垃圾回收操作,加快了垃圾收集的速度。

高吞吐量:由于并行执行垃圾收集操作,Parallel 收集器适用于吞吐量要求较高的应用场景。它能够在保证吞吐量的同时,尽可能地减少垃圾收集的停顿时间。

适用于大内存堆:随着内存空间的扩大,Parallel 收集器能够更好地应对大内存堆的情况,通过并行执行垃圾收集操作,提高了整个垃圾收集过程的效率。

相比于传统的 Serial 收集器,Parallel 收集器能更好地适应现代应用的需求,特别是大型内存堆和高吞吐量的场景。

Parallel Scavenge是一款采用复制算法进行垃圾收集的新生代收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,也是一款多线程的收集器,采用标记整理法进行垃圾收集

ParNew 收集器

ParNew 和 Parallel Scavenge 垃圾收集器都属于并行垃圾收集器。但ParNew的目标是尽可能缩短垃圾收集时用户线程的停顿时间。

ParNew 垃圾收集器之所以应用更加广泛,一个重要原因是它是唯一能与 CMS(Concurrent Mark-Sweep)收集器配合使用的新生代收集器,特别适用于那些对停顿时间要求较高的应用场景。

以下是 ParNew 垃圾收集器的一些特点和与 CMS 配合的优势

与 CMS 配合:ParNew 垃圾收集器能够与 CMS 垃圾收集器配合使用,用于处理老年代的垃圾回收。在这种组合中,ParNew 负责新生代的垃圾收集,而 CMS 负责老年代的并发垃圾收集。这种分工合作可以有效地减少应用程序的停顿时间,满足对低停顿时间的需求。

并行收集:ParNew 垃圾收集器采用多线程并行收集的方式,类似于 Parallel Scavenge 收集器。它能够充分利用多核 CPU 的优势,加快垃圾收集的速度,提高整个应用程序的性能。

应对停顿时间要求高的场景:由于 ParNew 与 CMS 配合使用,可以针对那些对停顿时间要求较高的应用场景。CMS 收集器通过并发执行垃圾回收操作,尽量减少停顿时间,而 ParNew 则能够在新生代中高效地执行垃圾回收操作,进一步降低停顿时间。

ParNew采用复制算法进行垃圾收集,是一款新生代的并行收集器

CMS收集器

CMS 垃圾收集器的设计初衷是允许垃圾收集器在进行垃圾回收的同时,与应用程序的线程并发执行,不需要长时间暂停应用程序线程。它的工作原理是通过并发标记和清除的方式,先标记所有的存活对象,然后清除未被标记的对象。允许在垃圾收集过程中与应用程序并发执行,从而降低了垃圾收集的停顿时间,提高了系统的响应性和用户体验。

随着硬件技术的发展,可用内存越来越大,这为应用程序提供了更多的内存空间,从而能够创建更多的对象,减少了垃圾收集的频率。然而,随着内存空间的增大,垃圾收集的时间也相应增加,可能导致长时间的停顿,影响用户体验。在这种情况下,传统的垃圾收集器需要暂停应用程序线程进行垃圾收集,这会导致用户在执行某些操作时出现延迟甚至停顿的情况,这是无法接受的。

CMS 垃圾收集器的优势

并发标记和清除:CMS 垃圾收集器采用了并发标记和清除的方式,允许在垃圾收集过程中与应用程序并发执行。这意味着垃圾收集过程中只有一小部分时间需要暂停应用程序线程。

低停顿时间:由于并发执行的特性,CMS 垃圾收集器能够在较短的时间内完成垃圾回收操作,从而减少了应用程序的停顿时间。通常情况下,CMS 垃圾收集器能够将停顿时间控制在几百毫秒甚至更低。

CMS 垃圾收集器主要针对老年代进行垃圾回收,对于新生代则通常使用 ParNew 收集器。这种分代收集的方式能够更好地适应不同内存区域的特点和垃圾回收需求。使用了标记清除法+标记整理法

G1收集器

CMS 垃圾收集器开创了垃圾收集器的一个新时代,实现了垃圾收集和用户线程同时执行,从而达到了垃圾收集的过程不停止用户线程的目标。这种并发垃圾收集的思路为后续垃圾收集器的发展提供了重要的参考。

随着硬件资源的不断升级,可用的内存资源越来越多,这对于垃圾收集器的发展提出了新的挑战。传统的垃圾收集器采用物理分区的方式将内存分为老年代、新生代、永久代或 MetaSpace,但随着可用内存的增加,某一分代区域的大小可能会达到几十上百 GB。在这种情况下,传统的物理分区收集方式会导致垃圾扫描和清理时间变得更长,性能下降。

G1 垃圾收集器摒弃了传统的物理分区方式

将整个内存分成若干个大小不同的 Region 区域。每个 Region 在逻辑上组合成各个分代,这样做的好处是可以以 Region 为单位进行更细粒度的垃圾回收。G1 垃圾收集器在进行垃圾回收时,可以针对单个或多个 Region 进行回收,从而提高了收集效率和性能。

G1 垃圾收集器吸取了 CMS 垃圾收集器的优良思路并通过摒弃物理分区、采用 Region 分区的方式,实现了更细粒度的垃圾回收,从而提高了整个系统的性能和可用性。 G1 垃圾收集器在大内存环境下的表现更加出色,成为了现代 Java 应用中的重要选择。

Region(局部收集)

G1 垃圾收集器的最核心分区基本单位是 Region。与传统的垃圾收集器不同,G1 不再将堆内存划分为固定连续的几块区域,而是完全舍弃了物理分区,而是将堆内存拆分成大小为 1MB 到 32MB 的 Region 块。然后,以 Region 为单位自由地组合成新生代、老年代、Eden 区、Survivor 区和大对象区(Humongous Region)等。随着垃圾回收和对象分配的进行,每个 Region 也不会一直固定属于某个分代,它们可以随时扮演任何一个分代区域的内存角色。

Collect Set(智能收集)

在G1里面会维护一个Collect Set集合。这个集合记录了待回收的 Region 块的信息,包括每个 Region 块可回收的大小空间。有了这个 CSet 信息,G1 在进行垃圾收集时可以根据用户设定的可接受停顿时间来进行分析,找出在设定的时间范围内收集哪些区域最划算,然后优先收集这些区域。这样做不仅可以优先收集垃圾最多的 Region,还可以根据用户的设定来计算收集哪些 Region 可以达到用户所期望的垃圾收集时间。

通过 CSet,G1 垃圾收集器的性能得到了极大的提升,并且能够实现可预测的停顿时间要求。这使得垃圾回收过程变得更加智能化,更加适应不同的应用场景和用户需求。需要注意的是,用户设定的时间应该合理,官方建议在 100ms 到 300ms 之间,以平衡垃圾收集的效率和停顿时间的需求。

G1采用标记复制法进行垃圾收集,是一款适用于整个堆内存的并行收集器

ZGC收集器

ZGC(Z Garbage Collector)是一种低延迟的垃圾回收器,是 JDK 11 引入的一项重要特性。ZGC 的出现为 Java 应用提供了一种更加高效、可预测的垃圾回收解决方案,与传统的垃圾回收器相比,ZGC 的主要目标是实现极低的垃圾回收停顿时间,使得 Java 应用能够以更可预测的方式运行,尤其在大内存堆上表现良好。

ZGC 的优势和特点包括:

低停顿时间:ZGC 致力于将垃圾回收的停顿时间降至最低。它通过并发标记、并发清理等技术,在整个垃圾回收过程中尽量减少对应用程序的影响,从而实现了极低的垃圾回收停顿时间。这使得 Java 应用能够更加平滑地运行,减少了因垃圾回收而导致的不可预测性和性能波动。

可预测性:ZGC 的设计注重可预测性,即使在大内存堆上,也能够提供稳定的性能和可预测的垃圾回收行为。这使得开发人员能够更加信任和依赖于 Java 应用在生产环境中的稳定性和可靠性。

适用于大内存堆:ZGC 的低停顿时间特性使其特别适用于大内存堆的场景。在这种场景下,传统的垃圾回收器可能会面临长时间的停顿,影响应用的响应性和用户体验,而 ZGC 能够有效地缓解这一问题,保持较低的停顿时间,从而确保应用的流畅运行。

ZGC采用并发标记法+并发清理进行垃圾收集,是一款适用整个堆内存并行收集器。

👌Linux中断机制是什么?

发表于 2025-04-17 | 更新于 2025-09-14 | 分类于 Linux
字数统计 | 阅读时长

👌Linux中断机制是什么?

Linux中断机制是操作系统处理硬件和软件事件的一种重要方式。中断可以使操作系统及时响应各种事件,如硬件设备的输入输出操作、定时器事件等。

中断的基本概念

1.中断类型

  • 硬件中断:由硬件设备(如键盘、网卡、硬盘等)发出的信号,引起CPU中断当前执行的程序,转而执行中断处理程序。
  • 软件中断:由软件指令(如系统调用、异常等)引发的中断。

2.中断向量表

中断向量表(Interrupt Vector Table, IVT)是一个包含中断服务程序地址的表。当中断发生时,CPU通过中断向量表找到相应的中断服务程序并执行。

中断处理流程

  1. 中断发生:硬件设备或软件事件发出中断信号。
  2. 中断响应:CPU停止当前执行的程序,保存当前的上下文(如程序计数器、寄存器等),并根据中断向量表找到对应的中断服务程序。
  3. 中断处理:执行中断服务程序,处理中断事件。
  4. 中断返回:中断处理完成后,恢复保存的上下文,继续执行被中断的程序。

中断处理的关键组件

1.中断控制器

中断控制器(如PIC、APIC)负责管理和分配中断请求。它可以屏蔽不需要的中断,并确定中断的优先级。

2.中断处理程序

中断处理程序(Interrupt Service Routine, ISR)是处理特定中断事件的函数。它通常分为顶半部(Top Half)和底半部(Bottom Half):

  • 顶半部:快速响应中断,执行紧急的处理任务。
  • 底半部:延迟执行较长时间的任务,以减少顶半部的执行时间,避免阻塞其他中断。

3.中断上下文

中断上下文是指中断发生时CPU的状态,包括程序计数器、寄存器等。保存和恢复中断上下文是中断处理的关键步骤。

中断处理的实现

1.注册中断处理程序

在Linux内核中,可以使用request_irq函数注册中断处理程序:

1
2
3
#include <linux/interrupt.h>

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

2.中断处理程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>

static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
printk(KERN_INFO "Interrupt occurred!\n");
// 中断处理逻辑
return IRQ_HANDLED;
}

static int __init my_module_init(void) {
int irq = 1; // 假设中断号为1
int result = request_irq(irq, my_interrupt_handler, IRQF_SHARED, "my_interrupt", NULL);
if (result) {
printk(KERN_ERR "Failed to request IRQ\n");
return result;
}
return 0;
}

static void __exit my_module_exit(void) {
int irq = 1; // 假设中断号为1
free_irq(irq, NULL);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple interrupt handler module");
MODULE_AUTHOR("Your Name");

3.顶半部和底半部

  • 顶半部:快速处理中断的关键部分。
  • 底半部:使用任务队列、工作队列或软中断机制延迟处理较长时间的任务。

中断的优先级和屏蔽

中断控制器可以设置中断的优先级和屏蔽某些中断,以确保重要的中断优先得到处理。APIC(Advanced Programmable Interrupt Controller)提供了更高级的中断管理功能,如多级中断优先级和分布式中断处理。

中断的嵌套和重入

中断处理程序应尽量短小精悍,以减少中断嵌套的层数。中断处理程序应避免使用可能导致阻塞的操作,如长时间的I/O操作和锁定操作。

👌常见的jvm垃圾收集器的工作流程?

发表于 2025-04-16 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

👌常见的jvm垃圾收集器的工作流程?

题目详细答案

Serial

Serial 垃圾收集过程的简单之处在于其采用了单线程执行的方式,以简化实现并减少资源占用。

  1. 暂停用户线程(Stop the World):

在开始垃圾收集过程之前,Serial 垃圾收集器会暂停(停止)所有的用户线程。这是为了确保在垃圾收集过程中对象的状态不会被修改,从而保证垃圾收集的准确性。

  1. 执行垃圾收集:

一旦用户线程暂停,Serial 垃圾收集器会开启一个单线程来执行垃圾回收操作。这个线程会遍历堆中的对象,标记并清理不再使用的对象,以释放内存空间。

  1. 等待垃圾收集完成:

在垃圾收集过程中,用户线程会被暂停,直到垃圾收集完毕。这意味着用户线程无法在垃圾收集期间执行任何操作。

当垃圾收集完成后,Serial 垃圾收集器会恢复用户线程的执行。此时,垃圾已被清理,堆内存中有更多的可用空间供应用程序使用。

Parallel Scavenge 和 Parallel Old

Parallel Scavenge 和 Parallel Old 是 Parallel 收集器系列的两个组成部分,它们的工作机制相似,都是利用多线程并行执行垃圾回收操作,以提高整个垃圾收集过程的效率和吞吐量。以 Parallel Scavenge 为例来说明其工作机制:

  1. 多线程并行执行:Parallel Scavenge 收集器利用了多个线程并行执行新生代的垃圾回收操作。这意味着在进行新生代垃圾回收时,多个线程同时工作,加快了垃圾收集的速度。
  2. 暂停用户线程:与 Serial 收集器类似,Parallel Scavenge 在进行垃圾收集时会暂停用户线程,以确保垃圾回收的准确性。这一阶段通常称为“Stop the World”。
  3. 多线程并发清理:Parallel Scavenge 收集器的特点之一是在新生代垃圾收集过程中采用并发清理的方式。这意味着在暂停用户线程期间,多个线程同时清理新生代中的垃圾对象,从而更快地完成垃圾收集过程。

通过利用多个线程并行执行垃圾收集操作,Parallel Scavenge 能够充分发挥多核 CPU 的优势,提高了垃圾收集的效率。相比于 Serial 收集器,它能更快地完成垃圾回收操作,从而减少了应用程序的停顿时间,从而提高了整个应用程序的性能。

ParNew 和 Parallel Scavenge

ParNew 收集器和 Parallel Scavenge 收集器在工作流程上确实非常相似,都是并行垃圾收集器。

1、 标记阶段

在垃圾收集开始时,ParNew 收集器会暂停所有应用线程(Stop-The-World),然后开始标记所有存活的对象。标记阶段的主要任务是识别哪些对象是存活的,并将它们标记出来。

2、 复制阶段

在标记阶段完成之后,ParNew 收集器会将所有存活的对象从 Eden 区和一个 Survivor 区(例如 S0)复制到另一个 Survivor 区(例如 S1)。如果目标 Survivor 区没有足够的空间容纳所有存活对象,存活对象将被移动到老年代。

3、 清理阶段

复制阶段完成后,Eden 区和之前的 Survivor 区(例如 S0)中的所有对象都被认为是垃圾,并且这些区域的内存可以被清理和重用。

4、 应用程序恢复

在清理阶段完成之后,应用程序线程将被重新启动,继续执行。

ParNew 收集器的工作流程与 Parallel Scavenge 收集器类似,都是通过停止应用程序线程,然后利用多线程并行执行垃圾回收操作,最后恢复用户线程的执行。这种并行执行的方式能够提高垃圾收集的效率,同时在暂停用户线程期间确保垃圾收集的准确性。

CMS

CMS垃圾收集器为了尽量减少用户线程的停顿时间,采用了一种创新的策略。这一策略使得在垃圾回收过程的某些阶段,用户线程和垃圾回收线程可以共同工作,从而避免了长时间的垃圾回收导致用户线程一直处于等待状态。

整个 CMS 垃圾收集过程被划分为四个阶段,它们分别是:

  1. 初始标记:在这个阶段,CMS 垃圾收集器会对根对象进行一次快速的标记,标记出所有与根对象直接关联的存活对象。这个阶段需要暂停用户线程,因为要确保标记的准确性。
  2. 并发标记:在这个阶段,CMS 垃圾收集器会与用户线程并发执行,对整个堆进行标记。垃圾回收线程会在后台标记所有存活对象,而用户线程可以继续执行,不受影响。
  3. 重新标记:在并发标记阶段结束后,CMS 垃圾收集器会进行一次重新标记,来处理在并发标记阶段发生变化的对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  4. 并发清理:在重新标记完成后,CMS 垃圾收集器会与用户线程并发执行,清理未标记的对象。垃圾回收线程会在后台清理不再使用的对象,而用户线程可以继续执行,不受影响。

通过将垃圾回收过程分为多个阶段,并在其中允许用户线程和垃圾回收线程并发执行,CMS 垃圾收集器成功地减少了用户线程的停顿时间。这种创新的并发垃圾收集策略提高了系统的响应性和用户体验,确保了应用程序的顺畅运行。

G1

G1 垃圾收集器的回收流程与 CMS 的逻辑大致相同,包括初始标记、并发标记、重新标记和筛选清除等阶段。但是,与 CMS 不同的是,G1 在最后一个阶段不会直接进行整体的清除。相反,它会根据用户设置的停顿时间进行智能的筛选和局部的回收。

  1. 初始标记:在初始标记阶段,G1 垃圾收集器会对根对象进行一次快速的标记,标记出所有与根对象直接关联的存活对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  2. 并发标记:在并发标记阶段,G1 垃圾收集器会与用户线程并发执行,对整个堆进行标记。垃圾回收线程会在后台标记所有存活对象,而用户线程可以继续执行,不受影响。
  3. 重新标记:在并发标记阶段结束后,G1 垃圾收集器会进行一次重新标记,来处理在并发标记阶段发生变化的对象。这个阶段需要暂停用户线程,以确保标记的准确性。
  4. 筛选清除:在重新标记完成后,G1 垃圾收集器不会立即进行整体的清除操作。相反,它会根据用户设置的停顿时间智能地筛选出需要回收的 Region,并执行局部的回收。这样可以在尽量满足停顿时间的情况下,最大限度地回收垃圾。

通过这种智能的筛选和局部回收方式,G1 垃圾收集器能够更好地平衡垃圾回收的效率和停顿时间,从而提高系统的响应性和用户体验。

ZGC

ZGC 的垃圾回收过程几乎全部都是并发执行的,即与应用程序线程同时进行。

  1. 初始标记(Initial Mark):在初始标记阶段,ZGC 会标记出根对象以及直接与根对象关联的存活对象。这个阶段需要短暂地暂停所有应用线程,以确保标记的准确性。
  2. 并发标记(Concurrent Mark):在并发标记阶段,ZGC 与应用程序线程并发执行,标记所有存活对象。这个阶段不会暂停应用程序线程,因此垃圾回收和应用程序可以并发执行。
  3. 最终标记(Final Mark):在并发标记阶段结束后,ZGC 需要再次短暂地暂停所有应用线程,完成最终的标记工作。这个阶段主要用于标记在并发标记阶段有可能发生变化的对象。
  4. 筛选(Concurrent Sweep):在最终标记完成后,ZGC 会进行一次筛选,确定哪些对象可以被回收。这个阶段会并发地进行,不会暂停应用程序线程。
  5. 并发清除(Concurrent Cleanup):在筛选阶段完成后,ZGC 会并发地清除未被标记的对象,释放它们所占用的内存。这个阶段也不会暂停应用程序线程。

👌Linux查看文件磁盘占用情况?

发表于 2025-04-16 | 更新于 2025-09-14 | 分类于 Linux
字数统计 | 阅读时长

👌Linux查看文件磁盘占用情况?

1.du命令

du(disk usage)命令用于估算文件和目录的磁盘使用情况。

基本用法

1
du [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -s:显示总计(不显示子目录的详细信息)。
  • -a:包括所有文件和目录。
  • -c:显示总计。
  • --max-depth=N:限制显示的目录层级深度。

示例

  • 查看当前目录下每个文件和子目录的磁盘使用情况:
1
du -h
  • 查看指定目录的总磁盘使用情况:
1
du -sh /path/to/directory
  • 查看指定目录及其子目录的磁盘使用情况,限制深度为1:
1
du -h --max-depth=1 /path/to/directory

2.df命令

df(disk free)命令用于查看文件系统的磁盘使用情况。

基本用法

1
df [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -T:显示文件系统类型。
  • -i:显示inode使用情况。

示例

  • 查看所有挂载的文件系统的磁盘使用情况:
1
df -h
  • 查看特定目录所在的文件系统的磁盘使用情况:
1
df -h /path/to/directory

3.ncdu工具

ncdu(NCurses Disk Usage)是一个基于ncurses的磁盘使用分析工具,提供了交互式界面。

安装

在Debian/Ubuntu系统上:

1
sudo apt-get install ncdu

在CentOS/RHEL系统上:

1
sudo yum install ncdu

使用

1
ncdu /path/to/directory

4.ls命令

ls命令也可以显示文件的大小,但不如du详细。

示例

  • 以人类可读的格式显示文件大小:
1
ls -lh
  • 显示目录的总大小:
1
ls -lhS

5.find命令结合du

可以使用find命令查找特定条件的文件,然后结合du命令查看它们的磁盘使用情况。

示例

  • 查找大于100MB的文件并显示其磁盘使用情况:
1
find /path/to/directory -type f -size +100M -execdu -h {} +

原文: https://www.yuque.com/jingdianjichi/xyxdsi/bnus9ng4m4oydmlu

👌Linux死锁是什么?

发表于 2025-04-16 | 更新于 2025-09-14 | 分类于 Linux
字数统计 | 阅读时长

👌Linux死锁是什么?

在Linux操作系统中,死锁(Deadlock)是指两个或多个进程在等待彼此释放资源,从而导致它们都无法继续执行的情况。这是一种常见的并发问题,尤其在多线程或多进程环境中。

死锁的四个必要条件

死锁的发生需要满足以下四个条件(也称为Coffman条件):

  1. 互斥条件(Mutual Exclusion):至少有一个资源是非共享的,即一次只能被一个进程使用。
  2. 占有并等待条件(Hold and Wait):一个进程已经持有了至少一个资源,并且正在等待获取其他被其他进程持有的资源。
  3. 不剥夺条件(No Preemption):资源不能被强制从进程中剥夺,只有持有资源的进程可以主动释放资源。
  4. 环路等待条件(Circular Wait):存在一个进程链,使得每个进程都在等待链中下一个进程所持有的资源。

死锁的检测和预防

为了处理死锁问题,可以采取以下几种策略:

1.预防死锁

预防死锁的方法包括破坏上述四个条件之一:

  • 破坏互斥条件:尽可能减少对非共享资源的使用。
  • 破坏占有并等待条件:在进程开始时一次性分配所有需要的资源,或者要求进程在请求资源前释放所有持有的资源。
  • 破坏不剥夺条件:允许操作系统强制剥夺某些资源。
  • 破坏环路等待条件:对资源进行排序,并要求进程按顺序请求资源。

2.避免死锁

避免死锁的方法主要是通过资源分配策略来确保系统永远不会进入死锁状态。常用的方法包括银行家算法(Banker’s Algorithm),它通过模拟资源分配情况来判断是否会导致死锁。

3.检测和恢复

如果无法预防或避免死锁,可以通过检测和恢复来处理死锁:

  • 死锁检测:定期检查系统中是否存在死锁。可以使用资源分配图(Resource Allocation Graph)来检测环路,从而识别死锁。
  • 死锁恢复:一旦检测到死锁,可以通过以下方式恢复:
    • 终止进程:强制终止一个或多个进程以打破死锁。
    • 资源剥夺:强制从某些进程中剥夺资源并重新分配。

👌Linux内存有问题怎么查看?

发表于 2025-04-16 | 更新于 2025-09-14 | 分类于 Linux
字数统计 | 阅读时长

👌linux 内存有问题怎么查看?

如果怀疑内存有问题,可以通过以下步骤进行检查和诊断:

1. 使用free命令查看内存使用情况

free命令可以显示系统的内存使用情况,包括总内存、已使用内存、空闲内存和缓存等信息。

1
free -h

输出示例:

1
2
3
total        used        free      shared  buff/cache   available
Mem: 7.8G 3.2G 1.5G 200M 3.1G 4.1G
Swap: 2.0G 0.0K 2.0G

2. 使用top或htop命令查看内存使用情况

top和htop命令可以实时显示系统的资源使用情况,包括内存使用情况。

  • top命令:
1
top
  • htop命令(需要安装):
1
2
sudo apt-get install htop
htop

3. 使用vmstat命令查看内存和系统性能

vmstat命令可以报告虚拟内存、进程、CPU 活动等信息。

1
vmstat 1 5

输出示例:

1
2
3
4
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1576828 72464 2192160 0 0 8 11 64 150 1 0 99 0 0
0 0 0 1576820 72464 2192160 0 0 0 0 59 144 0 0 100 0 0

4. 检查内存使用情况的日志

查看系统日志,如/var/log/syslog或/var/log/messages,看看是否有与内存相关的错误或警告。

1
2
sudo grep -i memory /var/log/syslog
sudo grep -i memory /var/log/messages

👌linux的中断机制用来干什么?

发表于 2025-04-16 | 更新于 2025-09-14 | 分类于 Linux
字数统计 | 阅读时长

👌linux的中断机制用来干什么?

Linux的中断机制用于处理各种硬件和软件事件,使操作系统能够及时响应并处理这些事件。中断机制在操作系统中起着至关重要的作用。

1.硬件设备的事件处理

硬件设备通过中断机制通知操作系统它们的状态变化或需要处理的事件。例如:

  • 键盘输入:当键盘按键被按下时,键盘控制器会发出中断信号,通知CPU读取按键值。
  • 网络数据接收:网卡接收到数据包时,会触发中断,通知操作系统处理接收到的数据。
  • 硬盘I/O完成:硬盘完成读写操作后,会发出中断信号,通知操作系统该操作已完成,可以继续处理数据。

2.定时器和时钟管理

操作系统使用定时器中断来管理系统时钟和时间片轮转。例如:

  • 系统时钟更新:定时器会定期触发中断,操作系统通过这些中断更新系统时钟。
  • 时间片轮转:在多任务操作系统中,定时器中断用于实现时间片轮转调度,确保各个进程能够公平地获得CPU时间。

3.系统调用和异常处理

软件中断(如系统调用和异常)允许用户空间程序与内核进行交互,并处理异常情况。例如:

  • 系统调用:用户程序通过触发软件中断进入内核态,执行系统调用以请求操作系统服务(如文件操作、进程管理等)。
  • 异常处理:当程序发生异常(如除零错误、非法内存访问等)时,CPU会触发异常中断,操作系统捕获并处理这些异常。

4.中断驱动的I/O操作

中断机制使得设备驱动程序可以实现高效的I/O操作。例如:

  • 非阻塞I/O:通过中断通知,设备驱动程序可以在设备准备好时处理I/O操作,而无需进程持续轮询设备状态,从而提高系统效率。

5.电源管理

中断机制在电源管理中也扮演重要角色。例如:

  • 电池状态变化:当电池电量低或充电完成时,电源管理芯片会触发中断,通知操作系统采取相应的措施(如节能模式或提示用户)。

6.多核处理器的中断分配

在多核处理器系统中,中断控制器(如APIC)可以将中断分配到不同的CPU核,以实现负载均衡和提高并行处理能力。

👌Mysql常用监控指标?

发表于 2025-04-11 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌Mysql常用监控指标?

1、QPS:数据库每秒处理的请求数量

1
show global status where variable_name in ('Queries', 'uptime');

QPS = (Queries2 -Queries1) / (uptime2 - uptime1)

2、TPS:数据库每秒处理的事务数量

1
show global status where variable_name in ('com_insert' , 'com_delete' , 'com_update', 'uptime');

事务数TC ≈’com_insert’ , ‘com_delete’ , ‘com_update’

TPS ≈ (TC2 -TC1) / (uptime2 - uptime1)

3、并发数:数据库实例当前并行处理的会话数量

1
show global status like 'Threads_running';

4、连接数:连接到数据库会话的数量

1
show global status like 'Threads_connected';

5、缓存命中率:查询命中缓存的比例

innodb缓冲池查询总数:

1
show global status like 'innodb_buffer_pool_read_requests';

innodb从磁盘查询数:

1
show global status like 'innodb_buffer_pool_reads';

生产中配置报警阈值:innodb_buffer_pool_read_requests /(innodb_buffer_pool_read_requests + innodb_buffer_pool_reads) > 0.95

6、可用性:数据库是否可以正常对外服务

周期性连接数据库并执行 select @@version;

7、阻塞:当前阻塞的会话数

1
2
3
4
5
6
7
8
select waiting_pid as '被阻塞线程',
waiting_query as '被阻塞SQL',
blocking_pid as '阻塞线程',
blocking_query as '阻塞SQL',
wait_age as '阻塞时间',
sql_kill_blocking_query as '建议操作'
from sys.innodb_lock_waits
where(unix_timestamp()-unix_timestamp(wait_started))>阻塞秒数

8、慢查询:慢查询情况

开启慢查询日志。my.inf

1
2
3
4
slow_query_log=on
slow_query_log_file=存放目录
long_query_time=0.1秒
log_queries_not_using_indexes=on

👌如何写好代码?

发表于 2025-04-11 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌如何写好代码?

前言

从代码的角度来说,好的代码可以提高系统的可维护性,扩展性。坏的代码会导致系统后期的维护和复杂性都变得非常高,非常混乱。全是坏代码的话,如果不重构就是,一坨屎山。如果重构的话,重构的人,就要💩上雕花。这是很难受的。下面是鸡哥觉得一个好代码要注意的点,大家理解吸收之后和面试官吹就好了。

代码整洁、可读

这个是必须的,代码工整,好理解,不要为了秀技,写一堆复杂的代码。而且代码之间的逻辑一定要清晰,不要大段大段的写,一屏幕一屏幕的写,没意义。需要划分模块,规定方法,单一职责,各司其职。

重复的代码会极大的增加后续的维护成本,也容易造成修改的遗漏。一定要对重复代码进行抽象和处理。

遵循规范

遵循项目风格的规范,不要搞特殊,每个项目历史背景不同,时间不同,风格都不一样,大家一定要注意保持一致,不要增加本来熟悉这个项目的人的理解成本。

还有就是团队内使用统一的代码格式,如缩进、空格和注释风格。

模块化设计

开闭原则和单一原则、模块化。要重点考虑。这样当一个需求发生的时候,其他人修改代码也是十分放心的。只需要关注自己修改的地方,而不需要考虑更多的逻辑。

模块间的依赖尽量少,各自搞各自的。不要强耦合。

单一职责就是一个方法,类最好只干一件事,这样的理解成本非常低。

注释

其实说过好多遍,一定要写,复杂的逻辑,注释更加易于理解。但是不要写成流水账,而是描述代码的意图。

单元测试

确保关键功能都有对应的单元测试,覆盖常见的和边界的用例,使用持续集成工具自动运行测试,确保每次代码变更都不会破坏已有功能。

持续重构

在开发过程中定期进行代码重构,改善代码结构和质量,每次重构的改动应尽量小且可控,确保系统的稳定性。

鲁棒性

鲁棒性表示系统可以在各种异常的情况下仍然可以稳定的工作,而不是出现不可预期的问题。好的代码在设计的时候就需要考虑各种异常和极端的场景应该如何处理,也就是常常说的面向失败和异常编程,多考虑失败场景如何处理。

学习和使用设计模式

学习常见的设计模式并在适当的场景下应用,可以提高代码的可扩展性和可维护性。同时不过度使用设计模式,保持设计的简单和直接。

代码评审

通过代码评审发现潜在问题,分享知识,提高团队整体的代码质量。积极接受同事的反馈,持续改进自己的编码习惯和技巧。一定要多吸取别人建议。

性能大关

在开发过程中关注代码的性能,避免不必要的性能瓶颈。使用性能分析工具检测和优化代码中的性能问题。

通过遵循这些建议,可以显著提高代码的质量,使其更易于维护、扩展和理解。

👌如何准备项目压测?

发表于 2025-04-11 | 更新于 2025-09-14 | 分类于 方法论
字数统计 | 阅读时长

👌如何准备项目压测?

一道开放题,年限高的会问到,初入茅庐的小伙伴会问。我不是开发吗。为什么问压测的问题!压测研发同样要配合很多东西。考察你的全局掌控能力。主要是要先梳理清楚几个方面。

数据库层面梳理

数据库层面我们重点关注的是高可用,慢sql,预警,连接池,读写分离。

比如库名,数据库从库是否在两个以上,并且从库不在同一个机房,是否订阅慢SQL,数据库线程池是否配置合理,是否有考虑库存容量及增长情况,数据库是否做了读写分离等等。

Redis层面

梳理集群,缓存主备是否双写,是否具备动态切换机制,是否考虑缓存容量及增长情况,是否配置缓存报警,是否有key未设置过期时间,是否存在热Key,是否存在大Key

应用层面

梳理是否为双机房部署,JVM配置标准化检查,内存占用率排查,是否设置磁盘预警规则

消息队列层面

梳理TOPIC,队列类型,是否配置消息积压报警,是否开启消息归档

外部依赖梳理

作为前台的业务系统,主要是依赖中台的外部接口来进行业务逻辑处理,那么一定要梳理外部依赖的接口,别名,外部接口负责人,是否要提升限流值,限流后触发的现象,以及外部不可用是否有降级措施等等。

梳理依赖的外部接口,当前qps,预估qps ,紧急联系人

资源梳理

容器资源作为我们的基石,我们有必要去把每个服务占用的资源情况梳理清楚,同时判断在618期间是否要进行扩容,来扛住更多的量,分析机房之间的机器数量是否均衡,应该保证1:1的情况。不要造成流量的倾斜。对于一些大的应用,比如超过500核,我们也要认真评估其是否有必要。

梳理,现有应用总容器数,促销活动总容器数,机房,分组,计划扩容数量等等

应用接口梳理

梳理功能模块,接口级别,场景描述,日常QPS(峰值),日常调用量/天,日常TP999,预估QPS,预估调用量,预估TP999,是否压测,负责人

配合测试确定范围

梳理清楚之后,就是和测试进行压测范围的圈定,确定接口和预计达到的巅峰。

压测观察

边压测的过程要边进行观察。如果有异常,或者恶劣影响,及时停止!

压测复盘

复盘压测时不通过的接口,和性能瓶颈,下来进行优化。

<i class="fa fa-angle-left"></i>1…456…12<i class="fa fa-angle-right"></i>

239 日志
22 分类
30 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini