Java大厂面试题 -- JVM 优化进阶之路:从原理到实战的深度剖析(2)
最近佳作推荐:
Java大厂面试题 – 深度揭秘 JVM 优化:六道面试题与行业巨头实战解析(1)(New)
开源架构与人工智能的融合:开启技术新纪元(New)
开源架构的自动化测试策略优化版(New)
开源架构的容器化部署优化版(New)
开源架构的微服务架构实践优化版(New)
开源架构中的数据库选择优化版(New)
开源架构学习指南:文档与资源的智慧锦囊(New)
我管理的社区推荐:【青云交技术福利商务圈】和【架构师社区】
2025 CSDN 博客之星 创作交流营(New):点击快速加入
推荐技术圈福利社群:点击快速加入
个人信息:
微信公众号:开源架构师
微信号:OSArch
Java大厂面试题 -- JVM 优化进阶之路:从原理到实战的深度剖析(2)
- 引言
- 正文
- 一、JVM 内存管理原理深入解读
- 1.1 堆内存的结构与工作机制
- 1.2 栈内存与本地方法栈的作用及区别
- 1.3 方法区的功能与常量池的奥秘
- 二、垃圾回收算法原理与实践
- 2.1 标记 - 清除算法详解
- 2.2 复制算法的优势与应用场景
- 2.3 标记 - 整理算法的特点与适用范围
- 三、实战案例:优化一个高并发电商系统的 JVM 性能
- 3.1 系统性能问题分析
- 3.2 JVM 优化策略实施
- 调整堆内存大小和比例
- 选择合适的垃圾回收器
- 优化代码中对象的创建和使用方式
- 排查和修复内存泄漏问题
- 3.3 优化效果评估
- 四、新兴垃圾回收器展望
- 4.1 ZGC(Z Garbage Collector)
- 4.2 Shenandoah 垃圾回收器
- Shenandoah 工作流程
- 4.3 新兴垃圾回收器的对比与选择
- 结束语
- 🎯欢迎您投票
引言
亲爱的开源构架技术伙伴们!大家好!在当今软件开发领域的激烈竞争中,Java 虚拟机(JVM)性能优化已成为决定系统成败的关键因素。一个精心优化的 JVM 能够显著提升应用程序的响应速度和吞吐量,降低资源消耗,为用户带来极致流畅的体验,同时为企业节省大量成本。对于开发者而言,深入理解 JVM 优化的底层原理,并能在实际项目中灵活运用这些知识,不仅是提升个人技术能力的重要途径,更是在职场竞争中脱颖而出的必备技能。接下来,我们将一同深入揭开 JVM 优化的神秘面纱,从内存管理、垃圾回收算法等底层原理入手,结合实际案例,详细阐述 JVM 优化的全流程。
正文
一、JVM 内存管理原理深入解读
1.1 堆内存的结构与工作机制
堆内存是 JVM 中存储对象实例的核心区域,其精妙的结构对于理解对象的生命周期和内存分配策略起着决定性作用。在 Java 的世界里,堆内存主要分为新生代、老年代以及在 Java 8 之前存在的永久代(Java 8 及之后被元空间取代)。
新生代宛如一个充满活力的新生命诞生地,进一步细分为 Eden 区和两个 Survivor 区(通常称为 Survivor0 和 Survivor1)。大多数新创建的对象首先在 Eden 区开启它们的旅程。当 Eden 区的空间被占满时,就会触发一次 Minor GC(新生代垃圾回收),这如同一场 “大扫除”。在这次 “大扫除” 中,Eden 区里存活下来的对象会被 “搬迁” 到其中一个 Survivor 区(假设是 Survivor0),同时,这些对象的 “年龄”(对象经历垃圾回收的次数)会增加 1 岁。倘若 Survivor0 区也满了,再次进行 Minor GC 时,Eden 区和 Survivor0 区中存活的对象会被转移到 Survivor1 区,它们的 “年龄” 也会随之增长。当对象的 “年龄” 达到一定门槛(默认是 15 岁)时,就会 “晋升” 到老年代,开启另一段生命周期。
老年代则像是一个经验丰富的 “长者” 聚集地,主要存储那些生命周期较长的对象。这些对象历经新生代多次 “大扫除” 的考验,最终来到老年代。由于老年代中的对象存活率较高,所以这里的垃圾回收频率相对较低。然而,当老年代的内存空间不足时,就会触发 Full GC(全量垃圾回收),这是一场涉及新生代、老年代和方法区的大规模 “清理行动”,过程相对耗时,可能会导致应用程序短暂 “停顿”,就像一辆高速行驶的汽车突然急刹车。
在 Java 8 之前,永久代承担着存储类的元数据、常量、静态变量等重要信息的重任。但它存在一些 “小毛病”,比如容易出现内存溢出错误,就像一个容量有限的容器,东西装得太满就会溢出来。从 Java 8 开始,元空间闪亮登场,它巧妙地使用本地内存(Native Memory)来存储类的元数据,这一改变使得元空间的大小不再受限于 JVM 堆内存,大大降低了内存溢出的风险,为系统的稳定运行提供了更坚实的保障。
为了更直观地感受堆内存的工作机制,我们来看一段示例代码:
public class HeapMemoryExample {public static void main(String[] args) {// 创建一个10MB的大对象,模拟大对象在堆内存的分配byte[] largeObject = new byte[1024 * 1024 * 10]; // 模拟多个小对象的创建,观察它们在Eden区和Survivor区的流转for (int i = 0; i < 10; i++) {byte[] smallObject = new byte[1024 * 1024];}// 当Eden区满时,会触发Minor GC,部分对象可能会被转移到Survivor区或晋升到老年代}
}
通过这段代码,我们能更清晰地看到不同大小的对象在堆内存中如何 “安家落户”,以及它们在新生代和老年代之间可能的 “迁移轨迹”。为了让大家更清晰地理解堆内存结构,请看下面的图表:
该图表直观展示了堆内存的结构划分,有助于理解对象在不同区域间的流转。
1.2 栈内存与本地方法栈的作用及区别
栈内存就像是一个有条不紊的 “方法调用记录簿”,主要用于存储方法调用相关的信息,包括局部变量、操作数栈、方法出口等。每一个线程在执行方法时,都会在栈内存中创建一个栈帧(Stack Frame),它就像是一个独特的 “工作间”,包含了方法执行所需的各种要素,如局部变量表、操作数栈、动态链接和方法返回地址等。当一个方法被调用时,就像有新的任务到来,会在栈顶新建一个栈帧;而当方法执行完毕返回时,这个栈帧就完成了使命,会从栈顶移除。
下面这段代码生动地展示了栈内存中栈帧的创建和销毁过程:
public class StackMemoryExample {public static void main(String[] args) {method1();}public static void method1() {int a = 10;int b = 20;// 调用method2方法,在栈顶创建method2的栈帧int result = method2(a, b); System.out.println("Result: " + result);}public static int method2(int x, int y) {// 计算结果return x + y; }
}
在这个例子中,当main
方法启动时,在栈内存中创建了一个main
方法的栈帧。接着,main
方法调用method1
,在栈顶又新建了一个method1
方法的栈帧。method1
方法中再调用method2
,又会在栈顶增加一个method2
方法的栈帧。当method2
方法执行完毕返回时,method2
的栈帧从栈顶离开;接着method1
方法执行完毕返回,method1
的栈帧也随之移除;最后main
方法执行完毕,main
方法的栈帧也被移除,整个过程井然有序。
本地方法栈与栈内存类似,但它有自己独特的使命,主要用于执行本地(Native)方法。本地方法是使用其他编程语言(如 C、C++)编写的,通过 JNI(Java Native Interface)与 Java 代码进行交互。当 Java 程序调用一个本地方法时,JVM 就会在本地方法栈中创建一个栈帧来执行该本地方法,就像为外来的 “特殊任务” 专门开辟一个工作区域。本地方法栈与栈内存相互协作,使得 Java 程序能够无缝调用本地代码,拓展了 Java 的应用边界。
例如,假设我们有一个用 C 语言编写的本地方法库MyNativeLibrary
,用于实现一些复杂的计算功能,下面是一个简单的 Java 代码示例来调用这个本地方法:
public class NativeMethodExample {// 声明一个本地方法,用于执行复杂计算public native int nativeAdd(int a, int b); static {// 加载本地方法库System.loadLibrary("MyNativeLibrary"); }public static void main(String[] args) {NativeMethodExample example = new NativeMethodExample();// 调用本地方法int result = example.nativeAdd(10, 20); System.out.println("Native Method Result: " + result);}
}
在这个例子中,nativeAdd
方法是一个本地方法,通过System.loadLibrary
方法加载本地方法库MyNativeLibrary
。当nativeAdd
方法被调用时,JVM 会在本地方法栈中创建一个栈帧来执行这个 “特殊任务”,然后将执行结果返回给 Java 代码。为了更清晰地对比栈内存和本地方法栈,我们用以下表格说明:
内存区域 | 用途 | 与 Java 方法关系 | 与本地方法关系 | 栈帧特点 |
---|---|---|---|---|
栈内存 | 存储 Java 方法调用信息,包括局部变量、操作数栈、方法出口等 | 每个 Java 方法执行时创建栈帧 | 无 | 包含局部变量表、操作数栈等,随方法调用创建和销毁 |
本地方法栈 | 执行本地方法,为本地方法提供运行时内存支持 | 无 | 每个本地方法调用时创建栈帧 | 与 Java 栈内存协同工作,执行外来的 “特殊任务” |
1.3 方法区的功能与常量池的奥秘
方法区是 JVM 中一个至关重要的 “知识宝库”,用于存储类的元数据、常量、静态变量等信息,并且是所有线程共享的区域。类的元数据就像类的 “身份证”,包含了类的结构信息、字段信息、方法信息、访问权限等重要内容。当一个类被加载到 JVM 中时,其相关的元数据就会被妥善存储在方法区,并且在类的整个生命周期内都存在,直到类被卸载,就像一个人的身份信息在其一生中都存在一样。
常量池是方法区的一颗璀璨 “明珠”,又分为字符串常量池和运行时常量池。字符串常量池是一个专门存储字符串常量的 “仓库”。在 Java 中,字符串常量是一种特殊的对象,为了节省宝贵的内存空间,JVM 巧妙地使用字符串常量池来缓存已经创建的字符串对象。当程序中创建一个字符串常量时,JVM 会先到这个 “仓库” 里看看是否已经有相同内容的字符串对象,如果有,就直接返回该对象的引用,就像从仓库中取出已有的物品;如果没有,才会在字符串常量池中创建一个新的字符串对象,并返回其引用。
例如,下面这段代码清晰地展示了字符串常量池的工作原理:
public class StringPoolExample {public static void main(String[] args) {// 创建字符串常量str1String str1 = "Hello"; // 创建字符串常量str2,由于内容相同,会引用字符串常量池中的同一个对象String str2 = "Hello"; // 通过new关键字创建新的字符串对象str3,存储在堆内存中String str3 = new String("Hello"); // 调用intern方法,返回字符串常量池中的对象引用String str4 = str3.intern(); // 输出true,因为str1和str2引用的是字符串常量池中的同一个对象System.out.println(str1 == str2); // 输出false,因为str3是在堆内存中创建的新对象System.out.println(str1 == str3); // 输出true,因为str4通过intern方法返回了字符串常量池中的对象引用System.out.println(str1 == str4); }
}
运行时常量池则是在类加载过程中,将编译期生成的各种字面量和符号引用收集起来存储到方法区中的常量池。它不仅包含字符串常量,还涵盖其他基本数据类型的常量、类和接口的全限定名、字段和方法的名称及描述符等丰富信息。运行时常量池在运行时还能动态地解析和创建新的常量,比如在使用反射机制时,就可能在运行时常量池中创建新的常量,就像一个智能仓库能够根据需求随时添加新的物品。
通过深入理解方法区和常量池的工作机制,我们就能更好地优化程序中的常量使用,减少内存开销,让程序运行得更加高效,就像合理管理仓库能提高工作效率一样。为了直观呈现方法区与常量池的关系,如下图表所示:
该图表清晰展示了方法区中各部分的包含关系,有助于理解常量池在方法区中的位置和作用。
二、垃圾回收算法原理与实践
2.1 标记 - 清除算法详解
标记 - 清除算法是垃圾回收领域的一位 “老将”,其工作流程分为两个关键阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器就像一个细心的 “检查员”,从根对象(如栈中的局部变量、静态变量等)开始,沿着对象之间的引用关系进行遍历,标记出所有存活的对象,就像给存活的对象贴上 “存活标签”。在清除阶段,垃圾回收器会再次遍历整个堆内存,回收所有未被标记的对象,也就是那些没有 “存活标签” 的垃圾对象。
该算法实现起来相对简单,不需要额外的内存空间来进行复杂的对象复制等操作。然而,它也存在一些明显的缺点。一是容易产生内存碎片,就像打扫房间时,把不要的东西直接清理掉,导致房间里留下一些零散的空间,难以再利用。在堆内存中,被回收的对象所占用的内存空间被直接释放,会导致出现不连续的空闲内存块。当后续需要分配较大对象时,可能因为找不到连续的足够大的内存空间而不得不提前触发垃圾回收,影响系统性能。二是标记和清除过程效率较低,需要遍历两次堆内存,随着堆内存中对象数量的增加,垃圾回收的时间开销也会显著增加,就像在一个很大的仓库里反复查找和清理,会花费大量时间。
为了更直观地理解标记 - 清除算法的工作过程,我们通过一个简化的代码示例来模拟:
class MarkAndSweep {// 用于标记对象是否存活,true表示存活boolean[] marked; // 模拟堆内存中的对象数组Object[] objects; public MarkAndSweep(int size) {marked = new boolean[size];objects = new Object[size];// 初始化一些对象,这里简单用整数表示对象for (int i = 0; i < size; i++) {objects[i] = i;}}// 标记对象为存活public void mark(int index) {marked[index] = true;}// 清除未被标记的对象public void sweep() {int j = 0;for (int i = 0; i < objects.length; i++) {if (marked[i]) {// 将存活对象移动到数组前面objects[j++] = objects[i]; }}// 释放未被标记的对象所占用的内存for (int i = j; i < objects.length; i++) {objects[i] = null; }}
}
在这个示例中,MarkAndSweep
类模拟了标记 - 清除算法的执行过程。通过mark
方法给存活对象贴上 “存活标签”,通过sweep
方法清理掉没有标签的对象,从而实现垃圾回收。以下用图表展示标记 - 清除算法流程:
该图表清晰呈现了标记 - 清除算法从开始到结束的完整流程,便于理解。
2.2 复制算法的优势与应用场景
复制算法采用了一种独特的内存管理策略,将堆内存划分为两块大小相等的区域,通常称为 From 空间和 To 空间。在垃圾回收时,只使用其中一块空间(假设为 From 空间)来分配对象,就像在一个房间里只使用一半空间来放置物品。当 From 空间满了,触发垃圾回收。此时,垃圾回收器会将 From 空间中存活的对象复制到 To 空间,就像把有用的物品搬到另一个房间,然后清空 From 空间。接着,From 空间和 To 空间的角色互换,原来的 To 空间变为新的 From 空间,用于下一轮对象分配,而原来的 From 空间变为 To 空间,等待下一次垃圾回收时接收存活对象。
这种算法具有显著的优势。首先,它能避免内存碎片,因为在垃圾回收时,存活对象被复制到一块连续的内存空间中,就像把物品整齐地搬到另一个房间,不会产生零散的空间,使得堆内存的空间利用率更高,后续对象分配更加高效。其次,回收效率高,复制算法在垃圾回收过程中只需要复制存活对象,并且复制过程相对简单,不需要像标记 - 清除算法那样遍历整个堆内存,因此回收效率较高。尤其在新生代中,大多数对象的生命周期较短,存活对象较少,复制算法能够充分发挥其优势。
复制算法主要应用于新生代的垃圾回收。因为新生代中对象的存活率较低,使用复制算法可以快速地回收垃圾对象,同时保持堆内存的整齐有序。例如,在 HotSpot 虚拟机中,新生代的 Eden 区和两个 Survivor 区就采用了复制算法进行垃圾回收。
以下是一个简单模拟复制算法在新生代应用的代码示例:
class CopyingGC {// Eden区,用于存储新创建的对象Object[] eden; // Survivor1区,用于存放从Eden区转移过来的存活对象Object[] survivor1; // Survivor2区,与Survivor1区交替使用Object[] survivor2; // Eden区对象索引,标记当前可存放对象的位置int edenIndex = 0; // Survivor1区对象索引int survivor1Index = 0; // Survivor2区对象索引int survivor2Index = 0; public CopyingGC(int edenSize, int survivorSize) {eden = new Object[edenSize];survivor1 = new Object[survivorSize];survivor2 = new Object[survivorSize];}// 分配对象到Eden区public void allocate(Object object) {if (edenIndex < eden.length) {// 将对象放入Eden区eden[edenIndex++] = object; } else {// Eden区满,触发垃圾回收gc(); // 回收后将对象放入Eden区eden[0] = object; // 重置Eden区索引edenIndex = 1; }}// 执行垃圾回收public void gc() {int targetIndex = 0;// 将Eden区存活对象复制到Survivor1区for (int i = 0; i < edenIndex; i++) {if (eden[i] != null) {survivor1[targetIndex++] = eden[i];}}// 清空Eden区edenIndex = 0; // 交换survivor1和survivor2Object[] temp = survivor1;survivor1 = survivor2;survivor2 = temp;survivor1Index = targetIndex;survivor2Index = 0;}
}
在这个示例中,CopyingGC
类模拟了新生代中基于复制算法的垃圾回收过程。通过allocate
方法将对象分配到 Eden 区,当 Eden 区满时,通过gc
方法执行垃圾回收,将存活对象复制到 Survivor 区,并交换 Survivor 区的角色。以下用图表展示复制算法流程:
该图表清晰呈现了复制算法的循环过程,有助于理解其工作机制。
2.3 标记 - 整理算法的特点与适用范围
标记 - 整理算法巧妙地结合了标记 - 清除算法和复制算法的优点。它的工作流程是,首先如同标记 - 清除算法一样,从根对象开始进行遍历,将所有存活的对象标记出来,就像在一堆物品中找出有用的东西并做上标记。接着,它会把这些存活的对象往内存的一端移动,让存活对象所占用的内存空间变得连续起来,这就好比把有用的物品整齐地排列在仓库的一侧。最后,直接清理掉边界以外的内存空间,也就是那些没有存活对象的区域。
这种算法有两个显著的特点。其一,它有效地解决了内存碎片问题。由于标记 - 整理算法会将存活对象移动到连续的内存空间,避免了像标记 - 清除算法那样产生大量不连续的空闲内存块,使得堆内存能够得到更高效的利用,就像把仓库里的物品重新整理后,能腾出更多连续的空间来存放新物品。其二,与复制算法相比,它减少了对象复制的开销。复制算法需要将所有存活对象复制到另一块内存空间,而标记 - 整理算法只需要移动存活对象,尤其是在对象存活率较高的情况下,这种移动操作带来的开销相对较小。
标记 - 整理算法主要适用于老年代的垃圾回收。因为老年代中的对象生命周期通常较长,存活率较高,如果使用复制算法,会因为需要复制大量存活对象而导致效率显著降低,而标记 - 整理算法则能在保证内存空间连续性的同时,减少垃圾回收的时间开销,提高系统的整体性能。
下面是一个简化的标记 - 整理算法模拟代码示例:
class MarkAndCompact {// 模拟堆内存中的对象数组Object[] objects; // 用于标记对象是否存活boolean[] marked; public MarkAndCompact(int size) {objects = new Object[size];marked = new boolean[size];// 初始化一些对象,这里简单用整数表示对象for (int i = 0; i < size; i++) {objects[i] = i;}}// 标记对象为存活public void mark(int index) {marked[index] = true;}// 执行标记 - 整理操作public void compact() {int j = 0;for (int i = 0; i < objects.length; i++) {if (marked[i]) {if (i != j) {// 将存活对象移动到数组前面objects[j] = objects[i]; // 原位置置为nullobjects[i] = null; }j++;}}// 释放超出存活对象范围的内存空间for (int i = j; i < objects.length; i++) {objects[i] = null; }}
}
在上述代码中,MarkAndCompact
类模拟了标记 - 整理算法的执行过程。mark
方法用于标记存活对象,而compact
方法则负责将存活对象移动到内存的起始位置,使得内存空间连续,并释放边界以外的内存。以下图表展示标记 - 整理算法流程:
该图表清晰呈现了标记 - 整理算法的步骤,有助于理解其工作过程。
通过对这三个垃圾回收算法的详细介绍,我们对 JVM 如何管理和回收内存有了更深入的理解。不同的算法适用于不同的场景,开发者需要根据应用程序的特点和性能需求,选择合适的垃圾回收算法,以实现最优的 JVM 性能。
三、实战案例:优化一个高并发电商系统的 JVM 性能
3.1 系统性能问题分析
某头部电商平台,在业务快速扩张的浪潮中,其高并发电商系统频繁遭遇性能瓶颈。该系统肩负着海量的商品展示、订单处理、支付交易等核心业务,就像一个繁忙的超级市场,每天要接待大量的顾客。在促销活动期间,如 “双 11”“618” 等,系统流量呈现出爆发式增长,高峰时段每秒请求数(TPS)可达数万次,这就好比超级市场在节假日迎来了人潮汹涌的购物高峰。
在这种高并发的情况下,系统暴露出了一系列问题。响应时间大幅增加,用户在进行商品查询、下单等操作时,需要等待很长时间才能得到反馈,就像顾客在超市结账时排着长长的队伍。吞吐量严重不足,系统每秒能够处理的请求数有限,无法满足大量用户的同时访问需求,导致很多用户的请求被阻塞,就像超市的收银通道太少,无法快速处理大量顾客的结账需求。内存使用率居高不下,接近物理内存上限,系统频繁触发 Full GC,每次 Full GC 都会导致应用程序短暂停顿,影响用户体验,就像超市的仓库已经堆满了货物,不得不经常进行大规模的清理,而清理期间超市需要暂停营业。
为了更精准地定位问题,我们使用了专业的性能监控工具(如 VisualVM、JConsole 等)对系统进行了全面监测。通过分析监测数据,我们发现以下具体问题:
- 对象创建频繁:在订单处理和商品展示模块,大量的临时对象被频繁创建,导致堆内存占用快速上升,频繁触发 Minor GC。
- 内存泄漏隐患:缓存模块中的部分对象在使用完毕后没有及时释放,随着时间的推移,这些对象逐渐积累,导致内存泄漏,进一步加剧了内存压力。
- 垃圾回收策略不合理:当前使用的垃圾回收器在高并发场景下,无法有效控制垃圾回收的暂停时间,导致系统响应时间不稳定。
3.2 JVM 优化策略实施
针对上述性能问题,我们制定并实施了以下一系列精准的 JVM 优化策略:
调整堆内存大小和比例
我们依据系统在不同业务时段的内存使用情况以及硬件资源配置,对堆内存的大小和新生代与老年代的比例进行了精细优化。通过多轮压力测试和性能评估,我们将堆内存的初始大小(-Xms
)和最大大小(-Xmx
)均设置为 8GB。同时,将新生代与老年代的比例(-XX:NewRatio
)调整为 1:3,即新生代占用 2GB,老年代占用 6GB。
这样的设置充分考虑了系统中对象的生命周期特性。由于电商系统中大部分对象是短期存活的,如用户的临时查询请求、临时订单信息等,这些对象通常在新生代就会被回收。将新生代设置为合适的大小,可以有效降低新生代和老年代之间的对象晋升频率,进而减少 Full GC 的触发次数。在调整后的首次促销活动中,Full GC 次数显著降低至每小时 5 - 8 次,效果十分显著。以下是调整堆内存参数的 JVM 启动配置示例:
java -Xms8g -Xmx8g -XX:NewRatio=3 YourMainClass
选择合适的垃圾回收器
鉴于系统的高并发特性,我们选用了 G1(Garbage - First)垃圾回收器。G1 垃圾回收器具有独特的设计理念,它将堆内存划分为多个大小相等的 Region,就像把一个大仓库划分为多个小隔间。G1 能够根据每个 Region 中垃圾对象的数量,优先回收垃圾最多的 Region,实现高效的垃圾回收,就像先清理垃圾最多的小隔间。
同时,通过设置参数-XX:MaxGCPauseMillis
来精准控制垃圾回收的暂停时间,满足系统对响应时间的严苛要求。在本案例中,我们将-XX:MaxGCPauseMillis
设置为 150,即尽量将每次垃圾回收的暂停时间控制在 150 毫秒以内。优化后,系统的平均响应时间从原来的 300 - 500 毫秒大幅缩短至 100 - 150 毫秒,用户操作更加流畅。以下是使用 G1 垃圾回收器的 JVM 启动配置示例:
java -XX:+UseG1GC -XX:MaxGCPauseMillis=150 YourMainClass
优化代码中对象的创建和使用方式
我们对系统代码进行了全面细致的梳理和优化,大力减少不必要的对象创建。在订单处理模块,引入对象池技术,复用已创建的对象。例如,预先创建 1000 个订单对象放入对象池,当有订单处理请求时,从对象池中获取可用订单对象,处理完成后再放回对象池。通过这种方式,订单处理过程中对象创建次数减少了 80% 以上。以下是一个简单的对象池实现示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class Order {// 订单IDprivate String orderId; // 商品列表private String[] products; // 订单状态private String status; public Order() {// 初始化订单对象}// Getter和Setter方法public String getOrderId() {return orderId;}public void setOrderId(String orderId) {this.orderId = orderId;}public String[] getProducts() {return products;}public void setProducts(String[] products) {this.products = products;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}
}class OrderObjectPool {private static final int POOL_SIZE = 1000;private final BlockingQueue<Order> pool;public OrderObjectPool() {pool = new LinkedBlockingQueue<>(POOL_SIZE);for (int i = 0; i < POOL_SIZE; i++) {pool.add(new Order());}}// 从对象池获取订单对象public Order getOrder() throws InterruptedException {return pool.take();}// 将订单对象放回对象池public void returnOrder(Order order) {// 重置订单对象状态order.setOrderId(null);order.setProducts(null);order.setStatus(null);pool.add(order);}
}
排查和修复内存泄漏问题
我们对系统中可能存在内存泄漏的模块进行了深入细致的排查和修复。以缓存模块为例,重新设计缓存对象的生命周期管理机制。设置合理的缓存过期时间,根据商品的更新频率和热度,将热门商品缓存时间设置为 1 - 2 小时,普通商品缓存时间设置为 6 - 12 小时。
同时,引入弱引用(WeakReference
)来管理缓存对象,当系统内存紧张时,弱引用指向的缓存对象会被垃圾回收器优先回收,避免了内存泄漏。以下是一个使用弱引用管理缓存的示例代码:
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;class Cache {private final Map<String, WeakReference<Object>> cacheMap = new HashMap<>();// 向缓存中放入对象public void put(String key, Object value) {cacheMap.put(key, new WeakReference<>(value));}// 从缓存中获取对象public Object get(String key) {WeakReference<Object> ref = cacheMap.get(key);return ref == null? null : ref.get();}// 清理过期缓存public void cleanExpiredCache() {cacheMap.entrySet().removeIf(entry -> entry.getValue().get() == null);}
}
经过修复,缓存模块的内存占用降低了 50% 以上,系统稳定性大幅提升。
3.3 优化效果评估
通过实施上述 JVM 优化策略,我们使用专业性能监控工具(如 VisualVM、JConsole 等)对系统的性能进行了全方位、细致的评估,并对优化前后的性能数据进行了详细对比分析:
性能指标 | 优化前 | 优化后 |
---|---|---|
响应时间 | 平均 300 - 500 毫秒,高并发时段超 1 秒 | 平均 80 - 120 毫秒,99% 请求 200 毫秒内响应 |
吞吐量 | 每秒处理请求数(TPS)3000 - 5000 | TPS 稳定在 8000 - 10000,高峰时段达 12000 |
内存使用率 | 长期维持在 90% 以上,接近物理内存上限 | 稳定在 60% - 70% 之间 |
Full GC 次数 | 每小时 20 - 30 次,每次暂停时间超 300 毫秒 | 每小时 3 - 5 次,每次暂停时间控制在 150 毫秒以内 |
用户满意度 | 约 60% | 提升至 90% 以上 |
系统可用性 | 约 99% | 提升至 99.9% 以上 |
从以上对比数据可以清晰地看出,通过对 JVM 的深度优化,该高并发电商系统的性能得到了质的飞跃。响应时间大幅缩短,用户体验得到极大提升,在最近一次促销活动中,用户满意度从 60% 提升至 90% 以上,投诉率显著下降。吞吐量显著提高,充分满足了高并发业务的需求,为业务增长提供了有力支撑。内存使用率降低,系统稳定性明显增强,内存溢出错误(OOM)不再出现。Full GC 次数大幅减少,且每次 Full GC 的暂停时间得到有效控制,极大地降低了对应用程序的影响。
四、新兴垃圾回收器展望
除了前面提到的常用垃圾回收器,JVM 领域还涌现出了一些新兴的垃圾回收器,它们在性能和功能上有了进一步的提升。
4.1 ZGC(Z Garbage Collector)
ZGC 是一款可伸缩的低延迟垃圾回收器,旨在处理 TB 级别的堆内存,同时将停顿时间控制在毫秒级别。它采用了染色指针(Colored Pointers)和读屏障(Load Barriers)等先进技术,实现了并发的标记、转移和重定位操作。
染色指针技术允许在指针中存储额外的信息,使得 ZGC 可以在不扫描整个堆的情况下,快速定位和处理对象。读屏障则在对象访问时进行检查,确保在并发回收过程中对象的一致性。
例如,在一个处理海量数据的大数据应用中,使用 ZGC 可以显著减少垃圾回收的停顿时间,提高系统的响应性能。以下是使用 ZGC 的 JVM 启动配置示例:
java -XX:+UseZGC YourMainClass
4.2 Shenandoah 垃圾回收器
Shenandoah 垃圾回收器同样致力于实现极低的停顿时间,它通过与应用程序并发执行垃圾回收操作,减少了垃圾回收对应用程序的影响。Shenandoah 采用了 Brooks Pointers 技术,在对象头中添加一个额外的指针,用于实现对象的并发转移。
在一些对响应时间要求极高的实时系统中,如金融交易系统,Shenandoah 可以提供更好的性能表现。以下是使用 Shenandoah 垃圾回收器的 JVM 启动配置示例:
java -XX:+UseShenandoahGC YourMainClass
Shenandoah 工作流程
Shenandoah 的工作流程主要分为以下几个阶段,每个阶段都有其独特的作用和特点:
- 初始标记(Initial Mark):该阶段会暂停应用程序线程,标记出所有根对象直接引用的对象。这个阶段的停顿时间通常非常短,因为只需要标记根对象的直接引用,就像在一片森林中先标记出与入口直接相连的树木。
- 并发标记(Concurrent Mark):此阶段与应用程序线程并发执行,从初始标记阶段标记的对象开始,递归地标记所有可达对象。在这个过程中,应用程序可以正常运行,就像在森林中一边有人继续探索新的树木,一边有人可以正常进行其他活动。
- 最终标记(Final Mark):再次暂停应用程序线程,处理并发标记阶段产生的引用变化,确保所有存活对象都被正确标记。这一步就像是对之前的探索结果进行一次检查和修正。
- 并发清理(Concurrent Cleanup):与应用程序并发执行,清理那些没有被标记的对象,释放它们占用的内存空间。就像在森林中清理掉那些已经被判定为无用的树木。
- 并发转移(Concurrent Evacuation):这是 Shenandoah 的核心阶段之一,它会将存活对象从旧的内存区域转移到新的内存区域,同时更新引用。这个过程也是与应用程序并发执行的,使用 Brooks Pointers 技术确保在转移过程中对象引用的正确性。
- 初始转移(Initial Evacuation):暂停应用程序线程,为并发转移阶段做准备,例如初始化转移所需的数据结构。
- 最终转移(Final Evacuation):再次暂停应用程序线程,完成并发转移阶段未完成的工作,确保所有存活对象都被正确转移。
为了更直观地展示 Shenandoah 的工作流程,以下是对应的图表:
4.3 新兴垃圾回收器的对比与选择
ZGC 和 Shenandoah 都是为了实现低延迟垃圾回收而设计的,但它们在实现细节和适用场景上有所不同。
对比项 | ZGC | Shenandoah |
---|---|---|
停顿时间 | 理论上停顿时间可控制在毫秒级别,处理 TB 级堆内存也能保持低延迟 | 停顿时间同样极低,通过并发操作减少对应用的影响 |
内存使用 | 染色指针技术需要额外的内存开销,但能提高回收效率 | Brooks Pointers 技术会在对象头增加指针,有一定内存开销 |
适用场景 | 适合处理大规模数据、对响应时间有较高要求的场景,如大数据分析、云计算平台等 | 适用于对响应时间敏感的实时系统,如金融交易、游戏服务器等 |
在选择垃圾回收器时,开发者需要根据应用程序的特点、硬件资源以及性能需求来综合考虑。如果应用程序需要处理大规模的堆内存,并且对响应时间有较高要求,ZGC 可能是一个更好的选择;如果应用程序对响应时间极其敏感,且堆内存规模不是特别巨大,Shenandoah 可能更适合。
结束语
亲爱的开源构架技术伙伴们!在本文中,我们深入探究了 JVM 内存管理的原理,包括堆内存、栈内存、本地方法栈以及方法区和常量池的运行机制。同时,详细阐述了常见的垃圾回收算法,如标记 - 清除算法、复制算法和标记 - 整理算法,并深入分析了它们的优缺点和适用场景。通过一个高并发电商系统的实战案例,全面展示了如何从性能问题诊断入手,制定并实施有效的 JVM 优化策略,最终实现系统性能的大幅提升。
亲爱的开源构架技术伙伴们!展望未来,随着 Java 技术的持续创新,JVM 也在不断演进。除了已广泛应用的 G1 垃圾回收器,像 ZGC 和 Shenandoah 垃圾回收器等新兴技术,正致力于实现更低的停顿时间和更高的吞吐量,为 JVM 性能优化开辟新的方向。例如,ZGC 号称能够在处理 TB 级别的堆内存时,停顿时间控制在毫秒级别,这将为超大规模的 Java 应用带来质的飞跃。
同时,随着硬件技术的飞速发展,如多核 CPU、大容量内存的普及,JVM 也需不断优化以充分利用这些硬件资源。例如,更好地支持多核并行处理,提高内存访问效率等。作为开发者,我们应时刻关注 JVM 技术的前沿动态,持续学习和掌握新的优化技巧,以应对日益复杂的业务需求和严苛的性能挑战。
亲爱的开源构架技术伙伴们!在尝试应用本文的 JVM 优化策略时,你在哪个环节遇到的困难最大?欢迎在评论区或架构师交流讨论区分享您的宝贵经验和见解,让我们一起共同探索这个充满无限可能的技术领域!
亲爱的开源构架技术伙伴们!最后到了投票环节:你认为在 JVM 优化中,哪个方面最具挑战性?投票直达。
- 深度揭秘 JVM 优化:六道面试题与行业巨头实战解析(New)
- 开源架构与人工智能的融合:开启技术新纪元(New)
- 开源架构的自动化测试策略优化版(New)
- 开源架构的容器化部署优化版(New)
- 开源架构的微服务架构实践优化版(New)
- 开源架构中的数据库选择优化版(New)
- 开源架构的未来趋势优化版(New)
- 开源架构学习指南:文档与资源的智慧锦囊(New)
- 开源架构的社区贡献模式:铸就辉煌的创新之路(New)
- 开源架构与云计算的传奇融合(New)
- 开源架构:企业级应用的璀璨之星(New)
- 开源架构的性能优化:极致突破,引领卓越(New)
- 开源架构安全深度解析:挑战、措施与未来(New)
- 如何选择适合的开源架构框架(New)
- 开源架构与闭源架构:精彩对决与明智之选(New)
- 开源架构的优势(New)
- 常见的开源架构框架介绍(New)
- 开源架构的历史与发展(New)
- 开源架构入门指南(New)
- 开源架构师的非凡之旅:探索开源世界的魅力与无限可能(New)
🎯欢迎您投票
相关文章:
Java大厂面试题 -- JVM 优化进阶之路:从原理到实战的深度剖析(2)
最近佳作推荐: Java大厂面试题 – 深度揭秘 JVM 优化:六道面试题与行业巨头实战解析(1)(New) 开源架构与人工智能的融合:开启技术新纪元(New) 开源架构的自动化测试策略优…...
C++ 标准库参考手册深度解析
C 标准库参考手册是每个 C 开发者的必备工具。本文将系统性解析其架构设计、核心功能及实战应用技巧,帮助开发者构建高效的知识检索与代码开发工作流,涵盖从语法查询到编译器适配的全流程技术细节。 一、网站架构与技术细节 1. 信息组织体系 1.1 层级化…...
单片机学习笔记8.定时器
IAP15W4K58S4定时/计数器结构工作原理 定时器工作方式控制寄存器TMOD 不能进行位寻址,只能对整个寄存器进行赋值 寄存器地址D7D6D5D4D3D2D1D0复位值TMOD89HGATEC/(T低电平有效)M1M0GATEC/(T低电平有效)M1M000000000B D0-D3为T0控制,D4-D7为T1控制 GAT…...
神经网络入门:生动解读机器学习的“神经元”
神经网络作为机器学习中的核心算法之一,其灵感来源于生物神经系统。在本文中,我们将带领大家手把手学习神经网络的基本原理、结构和训练过程,并通过详细的 Python 代码实例让理论与实践紧密结合。无论你是编程新手还是机器学习爱好者…...
2025-04-05 吴恩达机器学习5——逻辑回归(2):过拟合与正则化
文章目录 1 过拟合1.1 过拟合问题1.2 解决过拟合 2 正则化2.1 正则化代价函数2.2 线性回归的正则化2.3 逻辑回归的正则化 1 过拟合 1.1 过拟合问题 欠拟合(Underfitting) 模型过于简单,无法捕捉数据中的模式,导致训练误差和测试误…...
美团滑块 分析
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向过程 距离识别不准简单学习一下&…...
C++ atomic 原子操作
一、原子操作简介 在多线程编程中,通常我们需要多个线程共享数据。但如果不加以控制,多个线程同时访问同一数据,可能会导致数据不一致或竞争条件(race conditions)。为了解决这个问题,我们引入了 原子操作…...
Leetcode - 双周赛153
目录 一、3498. 字符串的反转度二、3499. 操作后最大活跃区段数 I三、3500. 将数组分割为子数组的最小代价四、3501. 操作后最大活跃区段数 II 一、3498. 字符串的反转度 题目连接 本题直接模拟,代码如下: class Solution {public int reverseDegree(…...
25.4 GLM-4+RAG智能标注实战:标注成本暴降60%,检索准确率飙升40%!
使用 GLM-4 优化 RAG 程序:智能标注提升检索质量 关键词:GLM-4 数据标注, RAG 优化, 检索增强生成, 向量数据库, 语义关联度分析 1. RAG 数据标注的核心挑战 在检索增强生成(Retrieval-Augmented Generation)系统中,检索数据的标注质量直接影响最终生成效果。传统标注方…...
基于sklearn实现文本摘要思考
和各位小伙伴分享一下使用sklearn进行文本摘要的思考。 第一版本 原理 提取式文本摘要的基本原理是: 将文本分割成句子 计算每个句子的重要性(权重) 选择权重最高的几个句子组成摘要 常用的句子权重计算方法: TF-IDF:基于词频-逆文档频…...
Cortex-M3 NVIC可以控制异常向量表的哪些部分
Cortex-M3 的 NVIC(嵌套向量中断控制器)不直接控制整个异常向量表,但可以管理向量表中与中断相关的部分行为。以下是 NVIC 对异常向量表的具体控制范围和相关机制: 1. NVIC 直接控制的部分 NVIC 主要管理 外部中断(IRQ) 和部分 系统异常 的行为,但对向量表本身的存储位…...
AI朝代应避免AI幻觉:分析与应对策略
随着人工智能(AI)技术的不断发展,AI的应用领域已经涵盖了文本生成、图像与视频创作以及程序编写等多个方面。然而,AI的生成能力并非没有缺陷,其中最为显著的之一就是所谓的“AI幻觉”(AI hallucination&…...
网络传输H.264方法对比
一、引言 网络传输H.264有多种方法,包括但不限于:1.通过RTP直接传输H.264;2.通过UDP传输TS流;3.通过RTP传输TS流;4.通过RTP传输PS流。本文对这些方法进行对比。 二、通过RTP直接传输H.264 这种方案就是RTP包…...
第九章:可靠通信_《凤凰架构:构建可靠的大型分布式系统》
第九章 可靠通信 一、零信任网络模型 核心难点:理解安全模型从传统边界防护到动态信任验证的转变 零信任的核心原则 不再区分"内部可信网络"与"外部不可信网络"(传统防火墙模型失效)每次请求都需要进行身份验证和授权…...
HeidiSQL:多数据库管理工具
HeidiSQL 是一款广受欢迎的免费开源数据库管理工具,专为数据库管理员及开发者设计。无论您是刚接触数据库领域的新手,还是需要同时处理多种数据库系统的专业开发者,该工具都能凭借其直观的界面和强大的功能,助您轻松完成数据管理任…...
文件系统-inode/软硬件连接(未完结)
首先我们了解一下文件简洁理解来说就是:文件内容文件属性 ---> 磁盘上存储的文件存文件的内容存文件的属性;而文件的内容----存放在每个数据块 ;文件属性存放在iNode里面。 要明白:linux系统中的文件是存储在磁盘中的…...
数据库并发控制问题
并发控制问题是数据库管理系统(DBMS)中处理多个事务并发执行时,确保数据一致性、可靠性和完整性的一系列技术和挑战。并发控制问题通常与事务的隔离性和事务之间的相互影响有关。以下是并发控制中主要的几种问题及其解决方式的详细解释&#…...
(五)智能体与工具协同——打造智能对话的超级助手
上一篇:(四)数据检索与增强生成——让对话系统更智能、更高效 在前四个阶段,我们已经搭建了一个功能强大的智能对话,并深入探讨了输入输出处理、链式工作流构建和数据检索与增强生成的细节。现在,我们将进…...
第十三届蓝桥杯 2022 省 B 刷题统计
题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a a 道题目,周六和周日每天做 b b b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n n n 题? 输入格式 输入一行包含三个整数 a , b a, b a,b 和 n n n. 输出格…...
NLP/大模型八股专栏结构解析
1.transformer 结构相关 (1)transformer的基本结构有哪些,分别的作用是什么,代码实现。 NLP高频面试题(一)——Transformer的基本结构、作用和代码实现 (2)LSTM、GRU和Transformer结…...
不在 qtdesigner中提升,进行主题程序设计
以下是无需在Qt Designer中提升控件的完整主题化方案,保持现有代码结构的同时实现动态阴影效果管理: 一、增强主题管理器(支持动态控件发现) // thememanager.h #pragma once #include <QObject> #include <QSet> #…...
TDengine 3.3.6.0 版本中非常实用的 Cols 函数
简介 在刚刚发布的 TDengine 3.3.6.0 版本 中,新增了一个非常实用的 函数COLS ,此函数用于获取选择函数所在行列信息,主要应用在生成报表数据,每行需要出现多个选择函数结果,如统计每天最大及最小电压,并报…...
MessageQueue --- RabbitMQ WorkQueue and Prefetch
MessageQueue --- RabbitMQ WorkQueue and Prefetch 什么是WorkQueue分发机制 --- RoundRobin分发机制 --- PrefetchSpring example use prefetch --- Fair Dispatch 什么是WorkQueue Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同…...
第四章:透明多级分流系统_《凤凰架构:构建可靠的大型分布式系统》
第四章:透明多级分流系统 一、客户端缓存 核心目标:减少重复请求,降低服务端压力。 1. 强制缓存 定义:客户端直接根据缓存规则决定是否使用本地缓存,无需与服务端交互。关键HTTP头: Cache-Control&#…...
题解:AT_abc241_f [ABC241F] Skate
一道经典的 bfs 题。 提醒:本题解是为小白专做的,不想看的大佬请离开。 这道题首先一看就知道是 bfs,但是数据点不让我们过: 1 ≤ H , W ≤ 1 0 9 1\le H,W\le10^9 1≤H,W≤109。 那么我们就需要优化了,从哪儿下手…...
热题100-字母异位词分组
方法用Arrays.sort对每个String 进行排序,毕竟排序以后都一样了,然后放入Map中的key跟value,可以一对多,然后返回的时候只要返回Map中所有的values就可以了 class Solution {public List<List<String>> groupAnagram…...
WiFi加密协议
目录 1. 认证(Authentication) 1.1 开放系统认证(Open System Authentication) 1.2 共享密钥认证(Shared Key Authentication) 1.3 802.1X/EAP认证(企业级认证) 2. 关联(Association) 3. 加密协议(Security Handshake) 整体流程总结…...
【AI提示词】书籍推荐专家
提示说明 帮助您找到适合您的好书。 提示词 ## Role: 书籍推荐专家## Profile: - author: xxx - version: 1.0.0 - language: 中文 - description: 我是一位书籍推荐专家,我可以帮助您找到适合您的好书。## Goals: - 吸引读者的注意力,引导他们阅读更…...
Linux进程间通信——有名管道
一.概念 函数形式:int mkfifo(const char \*filename,mode_t mode); 功能:创建管道文件 参数:管道文件文件名\路径,权限,创建的文件权限仍然和umask有关系。 返回值:创建成功返回0,创建失败返回…...
JavaScript基础--01-JS简介
字面量:数字、字符串、布尔值 前言JavaScript背景Web前端有三层:发展历史JavaScript的发展:蒸蒸日上 JavaScript介绍JavaScript入门易学性JavaScript是脚本语言JavaScript的组成 JavaScript 的特点特点1:解释型语言特点2ÿ…...
2025年 能够有效提升AI的生成质量和逻辑严谨性 的通用型系统提示
以下是三个经过精心设计的通用型系统提示(System Prompt),能够有效提升AI的生成质量和逻辑严谨性,适用于各类对话、分析和创作场景: Prompt 1 - 专家级分步验证模式 你是一个具备跨领域知识整合能力的超级AIÿ…...
xss攻击
XSS 攻击,即跨站脚本攻击(Cross - Site Scripting),是一种常见的 Web 应用程序安全漏洞。以下是关于它的详细介绍: 原理 输入输出控制不严:程序对用户输入和输出处理不当。用户输入的数据没有经过充分的过…...
Node.js核心模块及Api详解
以下是 Node.js 最常用的核心模块及 API 详解,按使用频率和重要性分类整理: 一、高频核心模块 1. fs 文件系统 const fs require(fs); const fsPromises require(fs).promises; // Promise 版本// 异步读取文件(推荐) fs.read…...
解析keras.layers.Layer中的权重参数
文章目录 概要__init__()build()add_weight() 概要 keras.layers.Layers是所有层对象的父类,在keras.layers下所有实现类都是其子类,自定义层时需要继承该类。 init() Layer的构造函数,需要注意两个参数trainable和name trainable:指定该层…...
原型设计工具即时设计的简单使用攻略
即时设计是一款国产在线协同设计工具,支持从原型设计到开发交付的全流程,尤其擅长Web/App界面原型制作。其核心优势体现在: • 零门槛入门:浏览器直接访问无需安装,中文界面友好 • 资源生态完善:内置3000原…...
深入解析C++智能指针:从内存管理到现代编程实践
一、智能指针核心概念 1.1 智能指针的本质 智能指针是基于**RAII(资源获取即初始化)**的封装类,通过对象生命周期自动管理动态内存。与传统指针相比: 特性原始指针智能指针内存管理手动自动空指针检查需显式判断支持空状态检测…...
在 Langflow 中构建灵活的自定义组件:从基础到高级实践
本文深入探讨了如何在 Langflow 平台中创建功能丰富的自定义组件。通过详细的目录结构解析、分步实现指南和多个实战案例,帮助开发者掌握利用 Python 生态扩展低代码平台的方法,打造高效的数据处理流程。 理解组件架构设计 自定义组件是在 Langflow 中创…...
【数学建模】(时间序列模型)ARIMA时间序列模型
ARIMA时间序列模型详解及常见时间序列模型概览 文章目录 ARIMA时间序列模型详解及常见时间序列模型概览1 引言2 ARIMA模型的基本概念3 ARIMA模型的组成部分详解3.1 AR模型 (自回归模型)3.2 MA模型 (移动平均模型)3 I (差分) 4 ARIMA模型的建模步骤5 Python实现ARIMA模型6 常见时…...
模版的特性及其编译分离
1.模版的分类 模版参数分为 类型形参 和 非类型形参 类型形参:出现在模版参数列表中,跟在class和typename之后的参数类型名称 非类型形参:就是用一个常量作为类(函数)模版的一个参数,在类(函…...
8电池_多绕组反激式变压器均衡_4模式
(1)8节串联锂离子电池组 (2)多绕组双向反激式变压器,1个变压器解决多电池均衡 (3)亮点:支持1建切换4种均衡算法–>全网唯一 (4)多绕组变压器均衡也能设计多种均衡算法–>全网唯一 锂离子电池均衡,均衡拓扑,均衡算法...
6.1 python加载win32或者C#的dll的方法
python很方便的可以加载win32的方法以及C#编写的dll中的方法或者变量,大致过程如下。 一.python加载win32的方法,使用win32api 1.安装库win32api pip install win32api 2.加载所需的win32函数并且调用 import win32api win32api.MessageBox(0,"…...
STP学习
{所有内容均来自于西安欧鹏的陈俊老师} STP生成树 当二层交换机意外成环路的时候会发生: 1.广播风暴:当广播帧进入环路时,会被不断复制并传输,导致网络中的广播流量急剧增加,消耗大量的网络带宽,降低网络…...
特征值与特征向量:从理论到应用的全面解析
特征值与特征向量:从理论到应用的全面解析 一、特征值与特征向量核心概念 定义 对于方阵 ( A ),若存在标量 ( \lambda ) 和非零向量 ( v ),使得: [ A v \lambda v ] 则 ( \lambda ) 为特征值,( v ) 为对应的特征向…...
【Python】数组的条件逻辑统计运算元素排序
【Python】数组的条件逻辑&统计运算&元素排序: 一.条件逻辑二.统计运算三.数组元素排序检索数组元素是否满足条件查找数组的唯一元素判断元素是否在其他数组中 一.条件逻辑 import numpy as np arr_x np.array([1, 5, 7]) arr_y np.array([2, 6, 8]) arr_…...
数据流和重定向
1、数据流 不管正确或错误的数据都是默认输出到屏幕上,所以屏幕是混乱的。所以就需要用数据流重定向将这两 条数据分开。数据流重定向可以将标准输出和标准错误输出分别传送到其他的文件或设备去 标准输入(standard input,简称stdinÿ…...
Jetpack Compose 自定义组件完全指南
Jetpack Compose 自定义组件完全指南 Compose 的声明式 UI 范式为创建自定义组件提供了前所未有的灵活性。本指南将带你从基础到高级全面掌握 Compose 自定义组件的开发技巧。 一、自定义组件基础 1.1 基本结构 一个最简单的自定义组件: Composable fun Greeti…...
ETF 场内基金是什么?佣金最低又是多少呢?
嘿,朋友们,大家好啊,我是StockMasterX,今天咱们就坐下来慢慢聊聊这个话题,ETF 场内基金到底是个啥东西,它的佣金最低能到多少,真的是个值得深挖的问题。 说起ETF,我还记得刚入行那会…...
【C++篇】类与对象(中篇) 解密C++类的核心:六大默认成员函数详解与避坑指南
文章目录 前言一、类的六个默认成员函数二、构造函数1. 概念2. 特性(牢记) 三、析构函数1. 概念2. 特性(牢记) 四、拷贝构造函数1. 概念2. 特性(牢记) 五、赋值运算符重载1. 运算符重载2. 赋值运算符重载前…...
001 vue
https://cn.vuejs.org/ 文章目录 v-bindv-modelv-on修饰符条件渲染/控制:v-if v-show列表渲染 M:即Model,模型,包括数据和一些基本操作 V:即View,视图,页面渲染结果 VM:即View-Mode…...
web forms可视化开发显示的网页是用ExpressionWebEditorFrame控件,是IE内核还是简单的HTML解析?如何让他加载CSS和JS?
web forms可视化开发显示的网页是用ExpressionWebEditorFrame控件,是IE内核还是简单的HTML解析?如何让他加载CSS和JS? 1. ExpressionWebEditorFrame 控件的内核及解析机制 在 Visual Studio 中用于 Web Forms 可视化开发的 ExpressionWebEditorFrame 控件主要基于 Internet…...