Java虚拟机

持续更新中

模块序号目录链接
前言介绍1前言地址
2介绍地址
基础知识3计算机网络地址
4操作系统地址
5Java基础地址
6Java并发地址
7Java虚拟机地址
中间件8Mysql地址
9Redis地址
10Elasticsearch地址
11RabbitMQ地址
12RocketMQ地址
框架13分布式系统地址
14MyBatis地址
15Dubbo地址
16Spring地址
17Spring MVC地址
18Spring Boot地址
19Spring Cloud地址
20Spring Cloud Alibaba Nacos地址
21Spring Cloud Alibaba Sentinel地址
22Spring Cloud Alibaba Seata地址
23Tomcat地址
24Netty地址
容器25Docker地址
26Kubernetes地址
架构设计27场景架构设计地址
28领域驱动设计地址
29设计模式地址
数据结构与算法30数据结构与算法地址
31LeetCode题解地址

内存划分

说一下JVM的内存划分
:::tips
Java 虚拟机 (Java Virtual Machine, JVM) 是 Java 运行时环境的核心组件,它负责管理 Java 应用程序的内存、加载类、解释字节码、执行指令等。理解 JVM 的内存模型对于优化 Java 程序性能和调试内存问题非常重要。
JVM 中的内存主要分为以下几大区域,每个区域有着不同的作用和特性:

1. 方法区 (Method Area)

描述

  • 方法区是存储类信息、常量、静态变量、方法描述以及被 JIT 编译器编译后的代码等数据的区域。方法区是线程共享的,是 JVM 中的全局区。

特点

  • 方法区在 JVM 启动时创建,仅包含加载的类信息等元数据。
  • 运行时常量池 (Runtime Constant Pool) 是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,在类加载后存储到方法区的运行时常量池中。

影响

  • 内存不足可能导致 OutOfMemoryError 错误。

2. 堆 (Heap)

描述

  • 堆是 JVM 中最大的一块内存区域,被线程共享,用于存放所有对象实例和数组。

特点

  • 堆在 JVM 启动时创建,所有对象实例都在堆上分配。
  • 堆分成新生代(Young Generation)和老年代(Old Generation),Java 垃圾收集器 (Garbage Collector, GC) 主要针对堆来进行内存回收。

新生代 (Young Generation)

  • 包含 Eden 区和两个 Survivor 区 (S0, S1)。
  • 新生代用来存放新创建的对象,Eden 区存放新创建的对象,两个 Survivor 区用于对象的复制。
  • 显著的垃圾收集算法是 Minor GC。

老年代 (Old Generation)

  • 存放生命周期较长的对象,通常是从新生代复制过来的。
  • 主要的垃圾收集算法是 Major GC(也称为 Full GC)。
  • 当老年代内存不足时,可能导致 OutOfMemoryError 错误。

3. 虚拟机栈 (Java Virtual Machine Stacks)

描述

  • 每个线程创建一个虚拟机栈,虚拟机栈保存了局部变量表、操作数栈、动态链接、方法出口信息等数据。

特点

  • 与每个线程都有直接关联,方法在执行时会创建栈帧 (Stack Frame),栈帧保存方法的局部变量和部分结果。
  • 栈帧在方法调用时入栈,方法返回时出栈。
  • 局部变量表保存方法内的基本数据类型、对象引用和 returnAddress 类型(即字节码指令的地址)。

影响

  • 方法递归过深可能会导致 StackOverflowError 错误。
  • 线程请求的栈深度超出虚拟机栈允许的深度时会抛出StackOverflowError
  • 动态创建线程时分配栈内存不足会抛出 OutOfMemoryError

4. 本地方法栈 (Native Method Stack)

描述

  • 本地方法栈为 JVM 使用 Native 方法服务,与虚拟机栈类似,但虚拟机栈是为 Java 方法服务的,而本地方法栈是为 Native 方法服务。

特点

  • 每个线程都有一个本地方法栈。
  • 本地方法栈记录了每个 Java 线程运行本地方法的调用状态。

影响

  • 与虚拟机栈类似,可能会受到 StackOverflowErrorOutOfMemoryError 的影响。

5. 程序计数器 (Program Counter Register, PC Register)

描述

  • 每个线程有一个独立的程序计数器,存储即将执行的字节码指令地址。程序计数器是唯一一个不会发生 OutOfMemoryError 的内存区域。

特点

  • 字节码解释器工作时,通过改变程序计数器来选择下一条需要执行的字节码指令。
  • 对于处理器来说,它表示的是下一条指令的执行地址。
  • 如果是 Native 方法,则程序计数器为空。

6. 直接内存 (Direct Memory)

描述

  • 直接内存并不是 JVM 运行时数据区的一部分,但却是被频繁使用的内存区域。
  • 它直接从操作系统内存中分配,可以加快 I/O 操作的速度。

特点

  • 直接内存的大小不是由 JVM 本身控制的,但受限于操作系统本身的内存限制。
  • 比如 NIO (New Input/Output) 中的缓冲区依赖于直接内存。

影响

  • 如果直接内存分配过多,可能出现 OutOfMemoryError 即使堆内存并没有被耗尽。

JVM 内存结构的总结图示

image.png

结论

理解 JVM 的内存划分对于编写高性能的 Java 应用程序以及调试和优化内存问题至关重要。掌握不同内存区域的作用和特性,能够更好地分析和解决内存泄漏、内存溢出等问题。
希望这些解释能够帮助你理解 JVM 的内存划分。如果你对某个区域有更深入的兴趣或需要讨论更多细节,欢迎继续提问!
:::

垃圾回收算法

有哪些垃圾回收算法,各有什么优缺点
:::tips
Java 垃圾回收(Garbage Collection, GC)机制是 Java 运行时环境的重要组成部分,它负责自动回收不再使用的对象,释放内存空间。常见的垃圾回收算法主要包括以下几种,每种算法各有优缺点:

1. 标记-清除算法 (Mark-Sweep)

工作原理
标记-清除算法分为两个阶段:

  1. 标记阶段:从根节点开始,标记所有可达的对象。
  2. 清除阶段:遍历整个堆,回收所有未标记的对象。

优点

  • 简单:算法简单且相对容易实现。
  • 灵活:能够处理复杂的对象关系。

缺点

  • 碎片化:清除阶段会产生大量的内存碎片,导致后续内存分配效率低。
  • 性能波动:标记和清除阶段需要遍历所有对象,可能导致明显的暂停时间。

2. 复制算法 (Copying)

工作原理
复制算法将内存分为两个相同大小的半区,每次只使用其中一个半区。当半区内存用完时,GC 将可达的对象复制到另一个半区,剩下的半区则被清空。
优点

  • 避免碎片化:通过复制对象避免了内存碎片问题。
  • 分配效率高:仅涉及指针移动,不需要复杂的空闲列表管理。

缺点

  • 内存利用率低:需要两倍的内存空间。
  • 不适合老年代:对象存活时间长的情况下,复制大量对象的成本较高。

3. 标记-整理算法 (Mark-Compact)

工作原理
标记-整理算法也分为标记和整理两个阶段:

  1. 标记阶段:标记所有可达对象。
  2. 整理阶段:将所有存活对象移动到内存的一端,按照顺序排列,从而消除碎片。

优点

  • 减少碎片化:通过对象移动消除内存碎片。
  • 内存利用率高:不需要空闲列表管理,内存分配效率提高。

缺点

  • 对象移动成本:需要额外的计算和移动对象的成本,影响性能。

4. 分代收集算法 (Generational Collection)

工作原理
分代收集算法将堆划分为新生代和老年代。新生代存放生命周期短的对象,老年代存放生命周期长的对象。不同代使用不同的垃圾收集算法来回收内存。
新生代 (Young Generation)

  • Eden区:所有新创建的对象首先分配在Eden区。
  • Survivor S0/S1区:在Eden区存活下来的对象会被复制到Survivor S0区,下一轮GC时再复制到Survivor S1区,直到某个对象足够长时间存活而转移到老年代。

老年代 (Old Generation)

  • 存放由新生代晋升过来的对象或存活时间较长的对象。

优点

  • 性能优化:针对不同对象的生命周期优化垃圾回收策略,分别处理短期和长期对象。
  • 高效:在新生代使用效率高的复制算法,在老年代使用标记-整理算法,综合性能较好。

缺点

  • 复杂性:实现和调优相对复杂。
  • 暂停时间长:老年代GC(如Full GC)暂停时间较长,影响应用性能。

5. 引用计数算法 (Reference Counting)

工作原理
引用计数算法为每个对象维护一个引用计数器,记录有多少引用指向该对象。当一个引用被创建或销毁时,更新计数器。当计数器为0时,该对象即可以被回收。
优点

  • 及时回收:对象计数为0时,即时回收内存。
  • 低复杂度:实现简单,内存管理开销小。

缺点

  • 循环引用问题:无法处理循环引用的对象,导致内存泄漏。
  • 计数开销:频繁的引用计数更新可能造成性能负担。

6. 串行收集器 (Serial Collector)

工作原理
串行收集器是最简单的垃圾收集器,使用单线程进行垃圾收集。
优点

  • 简单:实现简单,适合单核或小型内存的应用。
  • 适合单线程环境:在单线程环境下性能较好。

缺点

  • 暂停时间长:由于只使用单线程进行垃圾收集,会导致较长的暂停时间。
  • 不适合高并发:在多线程环境下,性能较差。

7. 并行收集器 (Parallel Collector)

工作原理
并行收集器使用多线程并发执行垃圾收集任务,旨在缩短垃圾收集的暂停时间。
优点

  • 高效:充分利用多核 CPU 资源,提升垃圾收集效率。
  • 缩短暂停时间:减少垃圾收集对应用程序的影响。

缺点

  • 复杂性:实现和调优相对复杂。
  • 可能的开销:并行收集过程中可能引入额外的同步开销。

8. 并发标记-清除算法 (Concurrent Mark-Sweep, CMS)

工作原理
CMS 收集器旨在减少垃圾收集对应用程序的暂停时间,采用并发标记和清除。
优点

  • 低暂停时间:大部分工作与应用程序并发进行。
  • 高效:适合内存较大的老年代垃圾收集。

缺点

  • 碎片化:清除阶段可能产生内存碎片。
  • 标记和清除的开销:部分阶段仍需暂停应用程序。

9. 垃圾优先收集器 (G1 Garbage Collector)

工作原理
G1 收集器将堆分割成多个区域(Region),优先收集垃圾最多的区域,部分并行执行垃圾收集。
优点

  • 低暂停时间:控制和预测暂停时间。
  • 减少碎片:通过标记-整理减少内存碎片,支持大内存下工作。

缺点

  • 复杂性:实现和调优相对复杂。
  • 启动和配置:需要合适的配置来达到最佳性能。

总结

不同的垃圾回收算法各有优缺点,适用于不同场景。选择和调优垃圾收集器时,应根据具体应用的需求和环境,借助 JVM 提供的收集器参数和工具进行优化。在实践中,正确地配置和监控垃圾收集器,可以显著提高 Java 应用程序的性能和稳定性。
如果有更多具体问题或需要深入了解某个具体收集器算法,欢迎继续提问!
:::

垃圾收集器

说一下垃圾收集器,以及过程
:::tips
Java 的垃圾收集器(Garbage Collectors)是 JVM 内存管理的重要组成部分,通过自动回收不再使用的对象来释放内存。不同的垃圾收集器实现不同的垃圾回收算法,各有特点和适用场景。以下是一些常用垃圾收集器及其过程的详细描述:

1. 串行收集器 (Serial Collector)

特点

  • 使用单线程进行所有的垃圾收集工作。
  • 新生代使用复制算法,老年代使用标记-整理算法。
  • 适用于单核 CPU 或小规模的应用程序。

垃圾收集过程

  • Minor GC:在新生代空间不足时触发,单线程会暂停其他所有应用线程(Stop-The-World, STW),然后在新生代进行标记和复制,将存活的对象从 Eden 区和一个 Survivor 区复制到另一个 Survivor 区。
  • Major GC(Full GC):当老年代空间不足时,会触发标记-整理算法,将存活的对象压缩到一端,释放未使用的内存。

2. 并行收集器 (Parallel Collector)

特点

  • 使用多线程并发进行新生代和老年代的垃圾收集工作。
  • 新生代使用复制算法,老年代使用标记-整理算法。
  • 适用于多核 CPU 和需要高吞吐量的应用程序。

垃圾收集过程

  • Minor GC:类似于串行收集器,但使用多线程并发标记和复制存活的对象,减少了垃圾收集的暂停时间。
  • Major GC(Full GC):也使用多线程并行进行标记和整理,可以在老年代进行更高效的垃圾收集操作。

3. 并发标记-清除收集器 (Concurrent Mark-Sweep, CMS)

特点

  • 主要针对老年代,旨在减少垃圾收集的暂停时间。
  • 使用标记-清除算法,并且大部分阶段与应用线程并行执行。
  • 适用于需要低延迟的应用程序。

垃圾收集过程

  • 初始标记:STW,标记从根节点可达的对象。
  • 并发标记:与应用线程并发执行,继续标记所有可达的对象。
  • 重新标记:STW,处理在并发标记阶段应用程序仍发生变化的部分,确保标记的准确性。
  • 并发清除:与应用线程并行执行,清除未标记的对象,并将内存返回给堆。
  • 碎片整理:在正式触发 Full GC 时才会进行。

缺点

  • CPU 资源占用高。
  • 清除阶段残留的内存碎片可能需要额外的 Full GC 进行整理。

4. 垃圾优先收集器 (Garbage-First, G1)

特点

  • 分代收集器,用于取代 CMS 收集器。
  • 将整个堆分成多个相同大小的区域(Region),新生代和老年代不再是物理隔离的,而是逻辑上的集合。
  • 支持目标暂停时间和区域优先回收,适用于大堆内存和需要严格控制暂停时间的应用程序。

垃圾收集过程

  • 年轻代 GC (Young GC):类似于传统的新生代垃圾回收,使用复制算法回收新生代对象。
  • 混合 GC (Mixed GC):收集年轻代和一部分老年代区域,使用标记-整理算法减少老年代的内存碎片。
  • GC 初始标记:标记所有可达的对象,STW。
  • 并发标记:与应用线程并发执行,标记所有可达的对象。
  • 最终标记:STW,标记所有在并发标记阶段新创建或改变引用的对象。
  • 筛选回收:根据各区域的回收收益,选择若干回收性价比高的区域进行清理和整理,尽量减少内存碎片。

垃圾收集器的选择与调优

在选择和调优垃圾收集器时,需要根据具体应用的需求进行测试和决策。以下是一些调优建议:

  1. 串行收集器
    • 适用于单线程或小内存应用。
    • 可以通过 -XX:+UseSerialGC 启用。
    • 示例用法:
    • image.png
  2. 并行收集器
    • 适用于多线程、高吞吐量应用。
    • 可以通过 -XX:+UseParallelGC 启用。
    • 示例用法:
    • image.png
  3. 并发标记-清除收集器 (CMS)
    • 适用于低延迟应用。
    • 可以通过 -XX:+UseConcMarkSweepGC 启用。
    • 示例用法:
    • image.png
  4. 垃圾优先收集器 (G1)
    • 适用于大内存、需要严格控制暂停时间的应用。
    • 可以通过 -XX:+UseG1GC 启用。
    • 示例用法:
    • image.png

监控与调优的工具

  1. JVisualVM:可视化监控和分析工具,提供实时GC日志、内存快照和CPU分析。
  2. JConsole:Java 提供的标准监控工具,可以监控GC活动、内存使用、线程状态等。
  3. Java Mission Control (JMC):高级监控和分析工具,可以对运行中的Java应用进行深度分析。
  4. GC 日志:通过 JVM 启动参数开启 GC 日志,分析和调优 GC 行为。
  5. image.png

通过上述工具和参数,开发人员可以详细了解 GC 行为,找出性能瓶颈,进行针对性的调优,以满足应用的性能需求。
总结:

  • 不同的垃圾收集器和算法适用于不同的应用场景。
  • 有效监控和调优 GC 行为可以显著提高应用性能。
  • 选择合适的 GC 收集器前需要进行充分的测试和分析,确保最佳的性能与稳定性。

如果有更多具体问题或需要进一步讨论某个收集器,欢迎继续提问!
:::
可达性算法
:::tips
在 Java 虚拟机(JVM)中,判断一个对象是否可达(Reachable)是垃圾回收(Garbage Collection, GC)的重要步骤。GC 使用的可达性分析算法通常称为根搜索算法(Root Search Algorithm)或标记-清除算法的标记阶段。这个算法通过一组称为“GC Roots”的对象作为起点,沿着引用关系遍历整个对象图,从而判断哪些对象是可达的,哪些对象是不再使用的。
以下是关于如何判断一个对象是否可达的一些详细解释:

GC Roots 概念

GC Roots 是一组在 GC 开始时可达的“根”对象。这些根对象包括:

  • 栈上的变量:所有活动线程的栈帧中的局部变量和输入参数开头的对象。
  • 静态字段:所有加载的类的静态字段。
  • JNI 引用:Java 本地接口(JNI)创建的引用。
  • Java 虚拟机内部使用的类加载器

可达性分析算法(Mark-and-sweep)

可达性分析算法主要包括以下几个步骤:

  1. 标记阶段:从 GC Roots 出发,递归地标记所有从根可达的对象。
  2. 清除阶段:遍历整个堆内存,回收没有标记的对象。
标记阶段
  • 初始化:标记所有 GC Roots 为已访问。
  • 递归标记
    • 从 GC Roots 出发,遍历所有可到达的对象。
    • 如果一个对象 x 可达对象 y,则 y 也被标记为已访问。
    • 继续标记 x 和 y 所引用的其他对象,直到所有可达对象都被标记。

标记阶段采用深度优先搜索(DFS)或广度优先搜索(BFS)算法,通过堆栈或队列实现递归。

清除阶段
  • 遍历堆内存:所有未标记的对象被认为是不可达的对象,进行内存回收。

示例代码解析(伪代码)

以下是一个简单的伪代码示例,用于演示可达性分析算法的思路:
image.png

可达性分析中涉及的细节

  1. 处理循环引用
    • 可达性分析自然处理循环引用问题,只要对象被 GC Roots 引用,就会被认为是可达的。循环引用不会影响 GC 的正确性。
  2. 弱软引用
    • 一些引用类型(如软引用、弱引用、虚引用)对 GC 行为有不同影响。GC 可在不同阶段处理它们,通常这些引用类型不作为可达性分析的根节点。
  3. 并发GC
    • 垃圾收集器如 CMS 和 G1 垃圾收集器允许应用和 GC 并发执行。为了保证正确性,GC 采用了三色标记法(Tri-color Marking)及混合收集、重标记等机制。

实际应用和工具

监视和分析可达性

  1. JVisualVMJConsole:这些工具可以可视化 GC 的执行过程,帮助你了解内存使用情况和对象的可达性。
  2. MAT (Memory Analyzer Tool):用于深入分析堆转储,找出内存泄漏和无法到达的对象。

总结

  • 可达性分析通过从 GC Roots 出发标记所有可达的对象,判断对象是否在使用。
  • GC Roots 包括栈变量、静态字段、JNI 引用等。
  • 标记-清除算法 是判断可达性的一种经典算法,分为标记阶段和清除阶段。
  • 并发GC 和不同的引用类型对 GC 过程也有影响。

通过了解和掌握这些概念和工具,开发人员可以更加高效地调试和优化 Java 应用程序的内存使用。如果你还有更多问题或具体案例需要讨论,欢迎继续提问!
:::

JVM调优

你们项目用的什么垃圾收集器,有没有进行过相关调优
:::tips
在Java项目中,垃圾收集器(Garbage Collector, GC)是一个非常重要的组件,它负责管理应用程序的内存回收。不同的垃圾收集器有不同的特性和优缺点,选择合适的垃圾收集器以及进行相关的调优可以显著提升应用程序的性能和稳定性。

常见的垃圾收集器

以下是几种常见的垃圾收集器:

  1. Serial GC
    • 单线程的垃圾收集器,适用于单处理器机器和小型应用程序。
    • 参数: -XX:+UseSerialGC
  2. Parallel GC(也称为Throughput GC)
    • 使用多线程进行垃圾收集,适用于多处理器机器和高吞吐量的应用程序。
    • 参数: -XX:+UseParallelGC
  3. CMS (Concurrent Mark-Sweep) GC
    • 低延迟的垃圾收集器,适用于需要低暂停时间的应用程序。
    • 参数: -XX:+UseConcMarkSweepGC
    • 自JDK 9起被标记为废弃,并在JDK 14中被移除。
  4. G1 (Garbage-First) GC
    • 设计用于应用程序暂停时间可预测,适用于大多数应用程序。
    • 参数: -XX:+UseG1GC
  5. ZGC (Z Garbage Collector)
    • 专注于低延迟和大内存应用,能够处理TB级内存。
    • 参数: -XX:+UseZGC
    • 从Java 11实验性引入起,在Java 15中成为可用。
  6. Shenandoah GC
    • 另一个低延迟垃圾收集器,主要用于实时性要求较高的应用。
    • 参数: -XX:+UseShenandoahGC
    • 从Java 12实验性引入起,在Java 15中成为可用。

垃圾收集器的选择和调优

选择垃圾收集器和进行GC调优是一项复杂的任务,通常需要结合应用的具体运行环境和性能需求。下面是一般的步骤和思路:

  1. 选择合适的垃圾收集器
    • 低延迟:对于对暂停时间非常敏感的应用程序,如在线交易系统或实时数据处理系统,优先选择CMS、G1、ZGC或Shenandoah GC。
    • 高吞吐量:对于批处理或任务执行时间较长的应用程序,可以选择Parallel GC。
    • 简单场景:对于简单的应用程序或开发和测试环境,可以选择Serial GC。
  2. 初始参数设置
    • 设置初始堆大小和最大堆大小:例如 -Xms512m -Xmx2g
    • GC日志:启用GC日志以便分析,例如:-Xlog:gc*
  3. 性能监控
    • 使用工具例如Java VisualVM、JConsole、JMC(Java Mission Control)等实时监控GC行为。
    • 通过GC日志或Apm工具(如New Relic, Datadog)分析GC频率、暂停时间等性能指标。
  4. 调优GC参数
    • 了解GC日志中的信息,确定是否有Full GC过于频繁。
    • 通过调整堆内存大小、年轻代与老年代比例、线程数等参数优化GC性能。
    • 例如,G1 GC的一些常见参数:
      • -XX:MaxGCPauseMillis=200:设置最大暂停时间。
      • -XX:InitiatingHeapOccupancyPercent=45:设置触发混合GC的堆使用率百分比。

示例调优过程

假设你的应用使用G1 GC进行垃圾收集,并且你发现年轻代GC(Minor GC)过于频繁,Full GC暂停时间较长。你可以进行如下调优:

  1. 查看GC日志
    • 启用GC日志:-Xlog:gc*
    • 分析日志,确认GC频率和暂停时间。
  2. 增大年轻代内存
    • 增加年轻代内存以减少Minor GC频率:
      • -XX:NewRatio=2 或根据应用情况调整。
  3. 调整GC暂停时间目标
    • 设置可接受的最大GC暂停时间:
      • -XX:MaxGCPauseMillis=200
  4. 启用和调整G1 GC相关参数
    • 调整G1 GC特有参数:
      • -XX:InitiatingHeapOccupancyPercent=45:触发混合GC的堆使用率百分比。
  5. 运行和重新评估
    • 应用调优参数,运行应用,重新评估GC性能。
    • 重复上述步骤,直到性能满足需求。

结论

选择和调优垃圾收集器是一个迭代过程,需要结合应用实际分析和测试。通过合理的选择和调优,可以显著提升应用的性能和稳定性,减少GC引起的暂停时间,满足高性能和低延迟的要求。
具体到你的项目,实际选用的垃圾收集器和调优策略会根据应用的特性和需求不同而不同。建议使用工具和GC日志详细分析应用的GC行为,进行有针对性的调优。

你们项目如何进行JVM优化的
在Java项目中,JVM(Java Virtual Machine)优化是一个非常重要的环节。通过合理配置JVM参数,进行垃圾回收(GC)调优,以及内存、线程、性能监控等方面的优化,可以大幅度提高应用的性能和稳定性。以下是一个项目中进行JVM优化的常见步骤和策略:

JVM优化步骤

1. 性能分析和基线建立
  • 性能测试:使用性能测试工具(如JMeter、Gatling等)对应用进行基准测试,了解应用的基础性能。
  • 收集基线数据:记录关键指标如响应时间、吞吐量、CPU和内存使用率等。
2. 内存配置
  • 设置堆大小
    • 初始堆大小(-Xms)和最大堆大小(-Xmx):确保这两个值设置合理,通常建议设置为相同以避免JVM频繁调整堆大小。
    • 例如:-Xms4g -Xmx4g
  • 设置新生代大小
    • 新生代大小影响GC频率,可以根据应用情况调整。
    • 例如:-XX:NewRatio 用于设置新生代与老年代的比例。
3. 垃圾收集器配置
  • 选择合适的垃圾收集器
    • 根据应用特性选择适合的GC(如Serial GC、Parallel GC、CMS、G1 GC、ZGC等)。
    • 例如使用G1 GC:-XX:+UseG1GC
  • 调优GC参数
    • 设置GC相关参数,如暂停时间目标和触发条件。
    • 例如:-XX:MaxGCPauseMillis=200-XX:InitiatingHeapOccupancyPercent=45
4. 线程和并发调整
  • 调整线程数量
    • 调整线程池大小和并发数以匹配硬件资源。
    • 可以通过参数如 -XX:ParallelGCThreads-XX:ConcGCThreads 控制GC线程数。
    • 例如:-XX:ParallelGCThreads=4-XX:ConcGCThreads=2
5. JVM监控和日志
  • 启用GC日志
    • 启用GC日志可以帮助分析和调优GC行为。
    • 例如:-Xlog:gc*(JDK 9及以上)或 -XX:+PrintGCDetails -XX:+PrintGCDateStamps(JDK 8及以下)
  • 性能监控
    • 使用监控工具如Java VisualVM、JConsole、JMC(Java Mission Control)实时监控JVM状态。
    • 使用APM工具(如New Relic、Datadog、AppDynamics)监控应用。
6. 代码级优化
  • 分析热点代码
    • 使用分析工具(如VisualVM、YourKit、JProfiler)识别和优化热点代码。
  • 减少GC开销
    • 通过优化代码减少对象创建和销毁频率,减少GC负担。
    • 使用对象池、缓存等技术。

示例:G1 GC优化

假设应用选择使用G1 GC进行垃圾收集,以下是一个可能的优化配置示例:
image.png

步优化过程

  1. 初始配置和测试
    • 使用上述初始配置进行性能基准测试,收集和分析GC日志。
    • 观察GC频率、暂停时间以及内存使用模式。
  2. 调整GC参数
    • 根据日志分析结果,如GC频率过高或暂停时间过长,调整-XX:MaxGCPauseMillis-XX:InitiatingHeapOccupancyPercent等参数。
  3. 优化内存和线程配置
    • 调整堆大小、新生代比例、GC线程数等参数,以优化内存使用和并发性能。
    • 例如增大新生代内存以减少Minor GC频率:-XX:NewRatio=2
  4. 持续监控和调整
    • 在生产环境中持续监控JVM性能,通过监控工具实时分析内存和GC情况。
    • 根据监控数据和应用负载变化,进行必要的参数调整和优化。

工具和方法

  • 性能测试工具:JMeter、Gatling
  • 监控工具:Java VisualVM、JConsole、JMC、Prometheus + Grafana
  • APM工具:New Relic、Datadog、AppDynamics
  • 分析工具:YourKit、JProfiler

总结

JVM优化是一个复杂而连续的过程,需要结合应用的具体特点和运行环境进行合理的配置和调整。通过性能测试、合理配置JVM参数、监控GC行为和内存使用、优化代码热点等手段,可以显著提升Java应用的性能和稳定性。每个步骤都需要详细分析和测试,形成最适合当前应用的优化配置。

CMS优化过程,优化后性能从多少提高到多少
CMS(Concurrent Mark-Sweep)是Java中的一种低延迟垃圾收集器,专为减少垃圾收集时的暂停时间而设计。CMS采用并发的方式进行标记和清理操作,可以显著降低GC对应用程序的影响。以下是一个假设的CMS优化过程,以及优化前后性能提升的示例。

初始情况

假设你有一个Java应用,当前使用的是CMS垃圾收集器。应用在高负载场景下表现不佳,GC频繁导致长时间停顿,从而影响响应时间。优化前的性能基线如下:

  • 平均响应时间:300ms
  • 99%响应时间:800ms
  • 吞吐量:2000 requests/second
  • Full GC频率:每分钟2次
  • Full GC暂停时间:分别为1500ms左右

优化目标

  • 降低Full GC的频率
  • 减少Full GC的停顿时间
  • 提高应用的响应时间和吞吐量

优化过程

1. 收集初始GC日志

首先,启用GC日志以分析当前GC行为:
image.png
通过GC日志,分析以下信息:

  • 新生代GC(Minor GC)和老年代GC(Full GC)的频率和停顿时间
  • 各个GC阶段的耗时
  • 堆内存使用情况
2. 调整堆大小

确保堆大小配置合理,避免频繁的GC:
image.png
通过增大堆内存,从4GB增加到6GB,可以减缓Full GC的频率。

3. 调整新生代大小

增大新生代大小以减少Minor GC频率:
image.png
这意味着新生代和老年代的比例为1:2(即新生代占堆的1/3,老年代占2/3)。

4. 调整并发线程数

增加CMS垃圾收集器的并发标记和清理线程数:
image.png
确保GC线程数和CPU核心数匹配,充分利用多核CPU的性能。

5. 调整启动条件

调整CMS垃圾收集器的启动条件,提前触发CMS进行垃圾收集,避免老年代过满导致的Full GC:
image.png
这意味着当老年代使用到60%时启动CMS GC,而不是等待默认的68%。

6. 启用Class Unloading

通常在CMS中进行Full GC时会进行类卸载,通过启用类卸载可以减少内存使用:
image.png

7. 优化后重新测试和监控

应用以上参数调整后,重新启动应用并进行性能测试,收集新的性能数据和GC日志。

优化效果

假设你通过上述过程对应用进行了优化,以下是优化后的性能提升情况:

优化前:
  • 平均响应时间:300ms
  • 99%响应时间:800ms
  • 吞吐量:2000 requests/second
  • Full GC频率:每分钟2次
  • Full GC暂停时间:分别为1500ms左右
优化后:
  • 平均响应时间:150ms
  • 99%响应时间:400ms
  • 吞吐量:3000 requests/second
  • Full GC频率:每分钟0.5次
  • Full GC暂停时间:分别为700ms左右

具体优化后的配置示例

image.png

总结

通过调优CMS垃圾收集器的参数,可以显著提升应用的性能:

  1. 内存配置优化:增大堆内存和新生代内存,减少GC频率。
  2. 并发配置优化:增加并发GC线程数,提高GC并发处理能力。
  3. 启动参数调整:提前触发CMS GC,避免Full GC的发生。
  4. 类卸载:启用类卸载,减少内存使用。

经过这些优化措施,应用的响应时间降低了一半以上,吞吐量提高了50%,Full GC频率显著降低,GC导致的停顿时间减少了一半。优化后的性能提升是显著的,能够更好地满足高负载和低延迟的需求。
:::

OOM

线上OOM如何排查解决
:::tips
线上发生OOM(Out of Memory)异常是一个严重的问题,可能会导致应用程序崩溃和不可用。OOM异常发生时,通常是因为JVM的堆内存、非堆内存或本地方法栈等资源耗尽。排查并解决这一问题需要系统性的步骤和工具。以下是详细的排查和解决流程:

1. OOM异常的类型

首先,需要了解OOM异常的类型。常见的OOM异常包括:

  • java.lang.OutOfMemoryError: Java heap space
    • 表示JVM的堆内存耗尽。
  • java.lang.OutOfMemoryError: Metaspace
    • 表示Metaspace(JDK 8及以上版本替代PermGen)的内存耗尽。
  • java.lang.OutOfMemoryError: GC overhead limit exceeded
    • 表示在GC过程中耗费过多时间,通常是内存不足导致的高频GC。
  • java.lang.OutOfMemoryError: Direct buffer memory
    • 表示直接内存(通常由NIO使用)耗尽。
  • java.lang.OutOfMemoryError: unable to create new native thread
    • 表示无法创建新的本地线程,通常是系统资源或线程数达到限制。

2. 收集诊断信息

2.1 启用GC日志

启用GC日志以了解GC行为和内存使用情况:
image.png

2.2 生成Heap Dump

在发生OOM时生成Heap Dump文件,帮助分析内存的使用情况:
image.png
Heap Dump文件会记录JVM内存中的所有对象,供后续分析使用。

3. 分析诊断信息

3.1 GC日志分析

使用工具(如GCViewer、GCMV、JVisualVM)分析GC日志,检查以下信息:

  • GC频率和暂停时间
  • 堆内存的增长模式
  • 新生代和老年代的使用情况
3.2 Heap Dump分析

使用Heap Dump分析工具(如Eclipse MAT、JVisualVM、YourKit)分析Heap Dump文件:

  • 查找大对象:检查哪个对象占用了大量内存,是否是预期的。
  • 内存泄漏:查找长时间存活且不应该存活的对象,分析它们的引用链,找出根源。
  • 类加载问题:检查类元数据,如果Metaspace消耗过高,可能是类加载的问题。

4. 找出原因并制定解决方案

根据诊断信息,找出OOM的根本原因,可能包括:

  • 内存泄漏
    • 某些对象无法被GC回收,导致内存占用持续增加。
    • 解决方法:修复代码中的引用管理问题,确保不再需要的对象能及时被回收。
  • 内存设置不足
    • 应用程序所需内存超过了当前配置的JVM堆大小。
    • 解决方法:增加堆内存配置,例如:
    • image.png
  • 数据规模增长
    • 数据规模增大导致应用负载增加,比如缓存数据过多。
    • 解决方法:优化数据管理策略,如清理不必要的缓存、分页加载数据等。
  • 频繁Full GC
    • 老年代内存增长过快,频繁触发Full GC。
    • 解决方法:调优GC参数,考虑使用合适的GC算法(如G1、ZGC等)。

5. 监控和优化

5.1 持续监控
  • 工具:使用监控工具(如Prometheus + Grafana、New Relic、Datadog)持续监控JVM内存使用、GC行为和系统资源使用。
  • 指标:监控堆内存使用率、GC暂停时间、类加载数、线程数量等关键指标。
5.2 JVM参数优化
  • 根据监控数据和分析结果,持续调优JVM参数。例如:
    • 调整新生代与老年代的比例:
    • image.png
    • 调整Metaspace大小:
    • image.png
5.3 高效使用内存
  • 代码优化:优化代码中的内存使用,避免创建不必要的对象,及时释放资源。
  • 合理缓存:使用合理的缓存策略,避免缓存占用过多内存。
  • 数据分片:对于大数据集,考虑使用分布式处理,如分片加载、数据流处理等。

案例示例

假设一线上应用频繁发生java.lang.OutOfMemoryError: Java heap space错误,具体排查和解决过程如下:

  1. 收集GC日志和Heap Dump

image.png

  1. 分析GC日志和Heap Dump
  • 使用GCViewer分析GC日志,发现频繁Full GC,老年代内存持续增长。
  • 使用MAT分析Heap Dump,发现大量缓存对象未及时清理。
  1. 调整内存配置和优化代码
  • 增加堆内存配置:
  • image.png
  • 优化缓存策略,减少缓存对象的生命周期。
  1. 重新部署并监控
  • 重新部署应用,持续监控内存使用和GC行为。
  • 利用Prometheus + Grafana实时监控,确保内存使用情况稳定。

总结

线上发生OOM异常需要系统性排查和解决,通过启用GC日志、生成Heap Dump、使用分析工具等方法,找出OOM的根本原因。根据诊断信息,调整内存配置、优化代码和缓存策略,并持续监控应用性能。通过一系列优化措施,可以有效解决OOM问题,提升应用的稳定性

怎么定位哪些代码触发的OOM

Java中的OutOfMemoryError(OOM)通常是由于应用程序在尝试分配内存时失败,因为可用的内存已经耗尽。定位哪些代码触发了OOM异常,可以使用以下步骤和工具:

1. 开启Heap Dump

当JVM抛出OOM异常时,可以通过开启Heap Dump,生成堆转储文件(Heap Dump),它包含了JVM堆中所有对象的详细信息。
启动JVM时添加以下参数:
image.png
这样,当OOM发生时,JVM会自动生成堆转储文件到指定路径。

2. 分析Heap Dump文件

使用Heap分析工具(如Eclipse MAT、VisualVM)分析Heap Dump文件:

  • Eclipse MAT(Memory Analyzer Tool)
    • 下载并安装Eclipse MAT。
    • 打开Heap Dump文件(.hprof),使用MAT分析堆内存使用情况。
    • 通过“Dominator Tree”、“Histogram”视图找到占用大量内存的对象。
    • 使用“Path to GC Roots”定位对象的引用链,找到导致内存泄漏的代码。
  • VisualVM
    • 下载并安装VisualVM。
    • 打开Heap Dump文件并分析内存使用情况。
    • 通过“Classes”视图查找占用内存过多的类。
    • 通过“References”视图查找对象的引用。

3. 启用JVM参数

在问题排查过程中,可以启用一些JVM参数来帮助收集更多信息:

  • GC日志
    • 启用GC日志可以帮助了解内存使用情况和GC行为。
    • 示例配置:-Xlog:gc*(JDK 9及以上)或者 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log(JDK 8及以下)。
  • 其他诊断参数
    • -XX:+PrintClassHistogramBeforeFullGC-XX:+PrintClassHistogramAfterFullGC:打印Full GC前后的类直方图。
    • -XX:+PrintHeapAtGC:在GC发生时打印堆的详细信息。

4. 使用监控工具

使用APM(应用性能监控)工具和内存分析工具实时监控应用运行状态,定位问题代码。

  • APM工具
    • 工具如New Relic、Datadog、AppDynamics等可以实时监控内存使用情况,捕获OOM异常,并提供详细的堆栈信息。
  • Java内置工具
    • jmap:用于生成堆转储,分析内存使用情况。
    • jstack:用于生成线程转储,了解线程的运行状态和占用的堆栈。
    • jstat:用于监控JVM内存和GC情况。

5. 代码级别的诊断

结合收集到的信息,进行代码级别的诊断:

  • 排查大对象:分析代码中是否有大对象的频繁创建或保存。
  • 内存泄漏:查找是否有集合类如List, Map中存在未及时清理的元素,或单例模式导致的对象无法回收。
  • 性能热点:分析代码中的性能热点,查看哪些方法或类频繁分配内存。

示例操作过程

假设你已经启用了Heap Dump并捕获了一个OOM异常:

第一步:生成并收集Heap Dump

image.png

第二步:分析Heap Dump文件

使用Eclipse MAT分析Heap Dump文件:

  1. 打开Eclipse MAT,选择File -> Open Heap Dump,打开生成的Heap Dump文件(如.hprof)。
  2. 在Overview页面查看“Histogram”视图,查找占用内存最多的类。
  3. 在“Dominator Tree”视图中,查看占用内存最多的对象和其引用链。
  4. 使用“Path to GC Roots”功能,找到导致对象无法被回收的引用链,定位内存泄漏的代码位置。
第三步:分析GC日志

通过启用GC日志查看内存分配和GC信息:
image.png
查看生成的GC日志:
image.png

第四步:使用APM工具
  • 配置New Relic、Datadog、AppDynamics等APM工具,捕获应用内存使用情况和OOM异常。
  • 在APM工具的控制台查看内存使用曲线、GC活动、异常堆栈等信息。
第五步:调试和修复代码

根据分析结果,在代码中找到大量内存占用或内存泄漏点:
image.png
优化代码示例,避免未必要的大对象分配或及时清理集合:
image.png

总结

通过启用Heap Dump、分析GC日志和内存使用情况、使用监控和APM工具,可以有效地定位和诊断出引发OOM异常的代码部分。结合Heap分析工具和GC日志分析,可以快速找到问题的根源,并进行针对性的优化和修复。
:::

CPU&LOAD

CPU和LOAD分别是什么load就是负载吗,跟CPU啥关系,啥区别
:::tips
CPU和Load(负载)是衡量计算机系统性能的两个重要指标,但它们关注的侧重点和意义不同。理解这两个指标及它们之间的关系对于系统优化和性能监控非常重要。

CPU 使用率 (CPU Usage)

CPU 使用率是衡量处理器在特定时间段内执行指令的活动程度的指标。一般用百分比表示,分为用户态(user time)、系统态(system time)、等待I/O(iowait)等多个维度。

  • 用户时间(user time):CPU在用户空间(用户进程)执行的时间。
  • 系统时间(system time):CPU在内核空间(操作系统内核)执行的时间。
  • I/O等待时间(iowait):CPU等待I/O操作完成的时间。

例如:
image.png
输出:
image.png
这表示CPU的各个时间段的使用情况。

Load (系统负载)

**Load(系统负载)**是一个度量系统整体负载的指标,反映某个时间段内系统中处于可运行或不可中断状态的进程数量的平均值。Load 平均值通常是通过1分钟、5分钟和15分钟的时间窗口计算的。
Load 是由可运行态(running)和不可中断态(uninterruptible sleep, 通常是等待I/O)的进程组成的。如果Load 值高于系统可用的CPU核心数,表示系统可能存在性能瓶颈。
image.png
输出:
image.png
这表示过去1分钟、5分钟和15分钟的系统负载平均值。

Load 和 CPU 的关系

  • 直接关系:当系统负载增加时,通常意味着CPU使用率会增加,因为有更多的进程需要CPU时间片。反之,当系统负载很低时,CPU使用率也较低。
  • 复杂关系:Load 不仅仅由CPU占用的进程数量决定,还包括等待I/O的进程数量。因此,Load 会受I/O操作、内存压力等多种因素影响,不仅仅是CPU性能。

例如,在一个多核系统中:

  • Load和CPU都较低:系统处于空闲状态。
  • 高Load但低CPU使用率:可能有大量等待I/O的操作(如磁盘I/O),导致进程阻塞。
  • 高Load和高CPU使用率:系统处于高负载运行状态,可能需要优化或扩展资源。

实例分析

假设一个4核处理器的系统,以下是一些典型情况及解释:

CPU 和 Load 都低

image.png

  • 解释:系统大部分时间处于空闲状态,负荷很低。
高Load 和 高CPU 使用率

image.png

  • 解释:系统在高负载下工作,可能需要更多的CPU资源,或者有优化空间。
高Load 但低CPU 使用率

image.png

  • 解释:大量的进程在等待I/O(例如磁盘I/O),不是CPU瓶颈,而是I/O瓶颈。
正常Load 和 CPU 使用率

image.png

  • 解释:系统负载正常,CPU使用率也在合理范围内,系统运行健康。

总结

  1. CPU 使用率(CPU Usage) 表示CPU在处理指令时的繁忙程度,用百分比表示。
  2. 系统负载(Load) 是系统中处于可运行或不可中断状态的进程数量的平均值,反映系统负载。
  3. 关系:负载反映系统中等待CPU时间片或I/O的进程数量,因此间接影响,或是由CPU使用率反映的忙碌程度。
  4. 区别:CPU使用率直接反映CPU的繁忙程度,而系统负载反映系统整体的需求状况,包括但不限于CPU。

理解这两个指标及其关系,对于优化系统性能、提升用户体验至关重要。

4C8G,cpu load多少正常,多少不正常,临界点多少
在一个拥有4核CPU和8GB内存(4C8G)的系统中,“正常”与“异常”系统负载(load)的判断需要考虑具体工作负载类型、应用特点以及整体系统表现。以下是一些指南,可以帮助你确定什么样的加载情况是合理的以及需要关注的临界点。

理解Load Average

首先,务必理解Load Average的含义:

  • Load Average 是一段时间内平均处于可运行或不可中断状态(例如,等待I/O操作)的进程数目。
  • 它通常包含三个值,分别为过去1分钟、5分钟和15分钟的平均值。

判断标准

对于一个4核CPU的系统:

  • Load Average ≤ 4:通常表示所有CPU内核都有任务,但不会出现明显的等待情况。这是一个可以接受的负载情况。
  • Load Average = 4:表示所有内核都满负荷工作,但没有出现任务排队等待。这是系统的设计负载上限之一。
  • Load Average > 4:表示任务开始排队等待CPU时间片。系统可能开始变得繁忙,需要进一步关注和优化。
  • Load Average ≥ 8:系统负载非常高,CPU资源已严重不足,任务等待时间可能明显增加,影响系统响应能力。需要立即采取措施优化性能或增加资源。

具体示例

假设 uptime 命令输出如下:
image.png

各负载区间解释

Load Average <= 4
  • 示例输出
  • image.png
  • 解释:
    • Load average均低于4,表明系统的负载在4个CPU核心范围内,可以充分利用CPU资源,是很健康的负载状态。
Load Average = 4
  • 示例输出
  • image.png

解释:

  • Load average正好为4,表明系统的负载刚好达到n个CPU核心,每个核心都被充分利用但没有任务等待切片时间。
Load Average > 4
  • 示例输出
  • image.png
  • 解释:
    • Load average超过4,表明多个任务在等待CPU资源,系统变得繁忙,需要关注。通过监控进程和有可能的调整资源来解决依赖问题。
Load Average ≥ 8
  • 示例输出
  • image.png
  • 解释:
    • Load average达到或超过8,表示所有CPU核心都在满负荷工作和大量任务排队等待。需要立即采取行动进行优化性能或增加硬件资源。

临界点的选择

根据以上的解释,4核CPU系统设定临界点Load为4是一个常识性的选择,因为超出这点就意味着有进程在等待CPU时间片。过高的负载值(8或以上),可能导致用户体验变差的问题。

性能问题及解决方案

1. 性能监控
  • 使用工具如 top, htop, vmstat, iostat 等,监控系统的 CPU, 内存, I/O 等性能指标。
2. 优化应用
  • 分析进程和线程的行为,确定是否有性能瓶颈或内存泄漏。
  • 优化应用程序的性能,例如通过提高算法效率、使用缓存等。
3. 调整系统配置
  • 调整内核参数(如 sysctl 参数),优化 CPU 调度和内存管理。
  • 调整进程的优先级(nice 值)以控制其对系统资源的影响。
4. 增加资源
  • 横向扩展:增加服务器实例,例如在集群环境中添加更多节点。
  • 纵向扩展:增加物理资源,如增加更多的 CPU 核心或内存。

总结

在一个4核CPU (4C) 的系统中,Load Average 指数反映了系统的整体负载情况。以下是一般的指导方针:

  • Load ≤ 4: 系统运行正常。
  • Load = 4: 系统达到设计负载上限,但没有严重问题。
  • Load > 4: 系统繁忙,需要关注。
  • Load ≥ 8: 系统严重过载,需要立即优化或增加资源。

通过合理监控和分析系统负载,根据需要调整应用和系统配置,可以有效提升系统的稳定性和性能。根据实际情况采取优化措施,确保系统在合理的负载范围内运行。

CPU怎么看呢,CPU利用率多少正常多少异常
了解和监控CPU利用率对管理和优化系统性能非常重要。以下是关于如何查看和解释CPU利用率的一些指导,以及什么样的CPU利用率是正常的、什么是异常的。

查看CPU利用率

以下是几种常见的方法和工具,用于查看CPU利用率:

1. 使用 top 命令

top 命令是一个实时的任务管理器,可以显示当前的系统性能,包括CPU利用率。
image.png
输出示例:
image.png

CPU利用率多少算正常
确定一个系统的CPU利用率是否“正常”取决于多个因素,包括系统的用途、运行的工作负载、响应性要求以及是否有其他资源瓶颈。以下是一些通用准则来帮助你判断CPU利用率是否在合理范围内。

通用准则

1. 低负载系统
  • CPU利用率:0% - 20%
  • 解释:系统大部分时间处于空闲状态,少量工作负载。典型的低负载系统包括开发环境、测试环境、文件服务器等。这种情况下,CPU利用率低是正常的。
2. 中等负载系统
  • CPU利用率:20% - 70%
  • 解释:系统有适度的工作负载,常见于普通的业务应用,如中等流量的Web服务器、数据库服务器等。这种利用率适中,可以充分利用CPU资源,而不会出现瓶颈。
3. 高负载系统
  • CPU利用率:70% - 90%
  • 解释:系统负载较高,但仍在可接受范围内。常见于高流量的Web服务器、大型数据库、计算密集型任务等。这时需要注意监控,如果负载持续高,可能需要优化应用或增加资源。
4. 超高负载系统
  • CPU利用率:90% - 100%
  • 解释:系统几乎处于满载状态,可能有性能瓶颈,响应变慢。此时需要考虑优化应用、负载均衡或扩展系统资源。持续的超高负载通常是不正常的,需要采取措施。

具体情境考量

1. 实时系统
  • CPU利用率:通常低于50%
  • 解释:实时系统(如金融交易系统、监控系统)对响应时间要求高,需要留出余裕来处理突发负载
    :::

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值