Java 垃圾回收,看一遍就懂

了解 Java 垃圾收集的工作原理并优化应用程序中的内存使用情况。详细了解 Java 中内存管理的复杂性。

f4470e4fe2894c76a7059c7df6654381.png

垃圾收集是一个关键过程,可以帮助任何Java 开发公司。编程语言中的这一强大功能可以巧妙地管理内存分配和释放,防止内存泄漏并优化资源利用率。它就像一个坚定的看门人,勤奋地清理未使用的对象,使我们免于被不必要的数字杂乱所淹没。作为一家 Java 开发公司,我们在编码工作中经常遇到这种挑战,而垃圾收集为此提供了一个优雅的解决方案。让我们深入研究一下这个复杂的机制。

GC 是 Java 编程中的无名英雄,它不仅为我们清理垃圾,还使 Java 内存更加高效。它至关重要,因为作为程序员,我们需要通过释放资源(尤其是未引用的对象)来管理好内存,而我们在寻求新想法时经常会忘记它(有时是因为懒惰)。

Java 垃圾回收的工作原理

让我们来研究一下这个安静清洁器在 Java 中的具体功能。

Java 中的垃圾收集是一个通过回收不再使用的未使用对象来自动管理内存的过程。

内存结构

在Java中,内存分为栈内存、堆内存、元空间,我们来详细了解一下。

堆栈内存

堆栈内存是方法执行期间存储局部变量、对象引用、方法参数和其他方法特定数据的内存区域。堆栈内存的大小是固定的。

堆内存

堆内存是用于存储实际对象的内存区域。其大小是固定的,并且可以根据需要动态增大或缩小。

这是一个例子。

Integer num = new Integer(12);

这会在堆栈内存中创建一个“num”变量,并在堆内存中创建一个新的 Integer 对象。堆栈内存中的“num”变量存储对原始对象的引用。

4e2b9b5a261c41da985a8336666faf6e.png

元空间

元空间是 Java 虚拟机 (JVM) 使用的本机内存的一部分,用于存储有关类和静态方法的元数据。它取代了永久代堆内存,后者是堆内存的一部分。

在早期版本的 Java 中,PermGen 用于存储有关类和静态方法的元数据。但它有一些限制。其中之一是它们的大小是固定的。另一个问题是 PermGen 空间与堆的其余部分一起被垃圾收集,这导致了性能问题。

Metaspace 可动态调整大小,并单独进行垃圾回收。它允许在多个 JVM 实例之间共享类元数据,从而减少内存使用量并提高性能。

垃圾收集资格

活动可访问对象是被程序的某些部分引用的对象,而死亡或不可访问对象是未被程序的任何部分引用的对象。例如:

Integer num = new Integer(12);
num = null;

如上所述,第一行在堆内存中创建一个新的 Integer 对象,并在堆栈内存中创建一个变量,该变量存储对原始对象的引用。

然后,在下一行中,我们更改了“num”的引用,这意味着“num”不引用我们之前创建的 Integer 对象。事实上,该 Integer 对象未被我们程序的任何部分引用。因此,它是一个无法访问或死对象。死对象可以被垃圾回收。

在以下情况下对象将变得无法访问:

  1. 所有引用该对象的变量都不再引用它(它们要么设置为空,要么设置为不同的值)。
  2. 当该方法从堆栈内存中释放时,在该方法内部创建的对象将变得无法访问。

隔离岛

隔离岛是指一组对象相互引用,但不再被程序中的任何对象引用。在下面给出的示例中,“a”和“b”相互引用,但不再被任何其他对象引用。

class Node {Node next;Node prev;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.prev = a;

为了打破孤立岛,我们需要改变对象的引用。这里,只有改变“a”和“b”的引用(例如,将 a 和 b 设置为 null)后,它们才能被垃圾回收。

堆内存的各部分

前面提到过,堆内存是负责存储对象的内存部分,分为年轻代空间(Young Generation Space)和老生代空间(Old Generation Space)。

年轻一代

在 Java 中,年轻代堆内存是新对象被创建的地方。这部分内存又分为两个部分:Eden 空间和 Survivor 空间。

伊甸园空间

伊甸园空间是年轻代空间的一部分,新对象被分配于此。

cd044be05c8a40b7aa105ca8d9243796.png

幸存者空间

经过一轮垃圾收集后,Eden 空间中幸存下来的对象将被提升至幸存者空间。

Java 垃圾收集器中的 Survivor Spaces 数量取决于所使用的具体收集器。Survivor Spaces 数量取决于所使用的具体收集器。在并行收集器和 CMS 收集器中,有多个 Survivor Spaces。并行收集器将 Survivor Space 划分为多个区域,而 CMS 收集器使用多个 Survivor Spaces。我们将在下面仔细研究不同的 Java 垃圾收集器。

老一辈

经过一定次数的垃圾收集后仍存活的对象将被提升到老一代。老一代中的对象寿命更长。它们不适合进行次要 GC,只能在主要垃圾收集期间清除。

老生代又称为终身代。

垃圾收集涉及的步骤

Java 垃圾收集通过持续监视 Java 虚拟机的堆内存来识别不再使用的对象。

Java垃圾收集按以下步骤进行:

  • 标记: GC 首先识别堆中的所有活动对象并对其进行标记。
  • 清除:一旦识别并标记了所有活动对象,GC 就会清除堆并释放不再使用的内存。然后这些内存便可分配给新对象。
  • 压缩:在某些 Java 垃圾收集算法中,清除之后,剩余的对象会被压缩,这意味着它们会被移动到堆的一端,使得 JVM 更容易分配新对象。

小型垃圾收集器

次要垃圾收集是从年轻一代识别和收集垃圾的过程,保持其无垃圾并减少主要 Java 垃圾收集周期的频率。

小型 Java 垃圾收集在较小的堆大小上进行,因此比大型垃圾收集快得多。

次要垃圾收集的工作原理如下:

Eden 空间填充:随着新对象被分配到 Eden 空间,Eden 空间最终会被填满。当 Eden 空间已满时,垃圾收集器将开始一次次要 GC 循环。

初始标记:垃圾收集器通过执行初始标记阶段来开始次要垃圾收集周期。在此阶段,垃圾收集器会识别 Eden 空间和幸存者空间中的所有活动对象。垃圾收集器会标记这些活动对象以表明不应收集它们。

复制收集:初始标记阶段完成后,垃圾收集器将执行复制收集阶段。在此阶段,垃圾收集器将所有存活对象从 Eden 空间和其中一个幸存者空间复制到另一个幸存者空间。

清除未使用的空间:将存活对象复制到幸存者空间后,垃圾收集器将清除 Eden 空间和当前垃圾收集周期中未使用的幸存者空间。任何未复制到幸存者空间的对象都被视为垃圾,并由垃圾收集器回收。

对象的提升:经历了一定数量的垃圾收集周期而幸存下来的对象最终将被提升到老一代(也称为终身一代),在那里它们将由针对较长寿命的对象进行优化的不同垃圾收集算法进行管理。

多重循环:如果在当前 Java 垃圾收集周期中使用的幸存者空间已满,则收集器将执行额外的次要垃圾收集周期,直到足够多的对象被提升到老一代或幸存者空间再次变空。

bbb0637cad7a4882bb1ad9d943ed2bdb.png

大型垃圾收集器

当老年代空间被填满时,主垃圾收集器就会启动。之所以称为“主垃圾收集器”,是因为它作用于整个堆,并且调用频率低于次要垃圾收集器。它通常更耗时且占用资源更多。

主要垃圾收集所涉及的步骤与上面描述的非常相似。

Java 中的垃圾收集器类型

Java 提供了几种不同类型的垃圾收集器。以下是所有垃圾收集器的列表以及它们的工作原理和优点。

串行收集器或停止并复制

串行垃圾收集器是 Java 中的一种 GCr,它使用单个线程执行 Java 垃圾收集过程。它主要用于具有相对简单的内存使用模式的基本应用程序。

您可能已经猜到了,串行垃圾收集器按顺序工作,这意味着它在执行 Java 垃圾收集过程时会停止应用程序中的所有线程。应用程序执行中的这种暂停有时被称为“stop the world”事件。

要使用串行收集器,请传入 -XX:UseSerialCollector 作为参数。

例如,java -XX:UseSerialCollector YourProgram

bee6e2433f154093baa7a6ad93d981b0.png

串行收集器的优点:

  1. 简单:这是 Java 中最简单、最直接的垃圾收集器。它占用空间小,需要的调整最少。
  2. 可预测性:由于它使用单线程,因此其行为可预测且易于理解。这使其对于需要可预测内存使用模式的应用程序非常有用。
  3. 非常低的开销:这使其对于性能至关重要的小型应用程序非常有用。

串行收集器的缺点:

  1. 不设计为可扩展:它不是设计为随着堆的大小或系统上可用的处理器的数量而扩展。
  2. 内存使用率低:可用内存利用率低。
  3. 长暂停时间:由于其设计,长暂停时间被融入到流程中。

并行收集器

并行垃圾收集器是Java 中的默认垃圾收集器,它是一种利用多个线程来提高垃圾收集性能的方法。它对于具有复杂内存使用模式的大型应用程序特别有效。

通过将堆细分为更小的段,并行垃圾收集器利用多个线程同时执行垃圾收集过程。与串行收集器类似,并行收集器也会在垃圾收集期间导致应用程序执行暂时暂停。

要使用并行收集器,请传入 -XX:+UseParallelGC 作为参数。

598572d14703468380e6effbcb682211.png

并行收集器的优点:

  1. 更快:由于利用了多线程,与串行收集器相比,其性能更佳,可以实现更快的垃圾收集操作。
  2. 更好的可扩展性:设计用于通过堆的大小有效地扩展,使其适合具有更大内存需求的应用程序。

并行收集器的缺点:

  1. 更长的暂停时间:并行垃圾收集器在垃圾收集过程中停止应用程序,与其他垃圾收集器相比,这会导致更长的暂停时间。
  2. 更高的 CPU 开销:并行垃圾收集器使用多个线程,这会导致更高的开销和增加内存使用量。

并发标记清除收集器

CMS (并发标记和清除)收集器是另一种垃圾收集器。它与应用程序并发执行垃圾收集过程,或者换句话说,它使用多个垃圾收集器线程。它旨在最大限度地减少应用程序中的暂停时间并减少 Java 垃圾收集对性能的影响。

要使用 CMSCollector,请传入 -XX:+UseConcMarkSweepGC 作为参数。

56944805885c4cdcb7c8111d260482a1.png

CMS的优点:

  1. 低暂停时间: CMS 最大限度地减少垃圾收集期间的暂停时间,为延迟敏感的应用程序提供更流畅的体验。
  2. 可预测: CMS 提供更可预测的垃圾收集暂停,这对于实时系统或具有严格性能要求的应用程序至关重要。
  3. 适用于更大的堆:即使堆大小增加,CMS 仍能保持其性能,这使其成为具有大量内存需求的应用程序的可行选择。

CMS 的缺点:

  1. 更高的 CPU 开销: CMS 由于其并发特性而消耗更多的 CPU 资源,这可能会影响应用程序的整体性能。
  2. 碎片化风险: CMS 不是长期运行应用程序的理想选择,因为堆会随着时间的推移而变得碎片化。这种碎片化会导致内存使用量增加和性能下降。

G1 收集器

Garbage -First (G1) 收集器是 Java 7 中引入的一种垃圾收集算法,旨在解决传统垃圾收集器(例如并行收集器和 CMS 收集器)的局限性。G1 被设计为低暂停、以吞吐量为导向的收集器,可以处理非常大的堆。

要使用 G1 Collector,请传入以下参数:

-XX:+UseG1GC

G1收集器的优点:

  1. 低暂停时间: G1 的设计旨在最大限度地减少暂停时间,这使其适合实时应用。
  2. 可扩展性: G1 具有可扩展性,这使其适用于具有不同堆大小的大型应用程序。

G1收集器的缺点:

  1. 开销:与其他垃圾收集器相比,G1 消耗更多的 CPU 资源,导致 CPU 开销增加。
  2. 较长的初始标记时间: G1 中的初始标记阶段可能需要更长时间,特别是对于较大的堆大小,这可能会对应用程序性能产生负面影响。
  3. 不适合小堆:G1 收集器不适合堆大小较小的应用程序,因为它的优势在较大的内存环境中才能得到最好的体现。

ZGC

ZGC 垃圾收集器( ZGC) 是一款 Java 垃圾收集器,专门用于管理超大堆(最大 16TB),同时保持最短的暂停时间。其主要目标是最大限度地缩短垃圾收集过程的持续时间,从而最大限度地提高应用程序的吞吐量。

ZGC的优势:

  1. 低暂停时间:旨在最大限度地减少暂停时间,使其适用于实时或延迟敏感的应用程序。
  2. 可扩展性:它可以根据堆的大小和可用处理器的数量进行扩展。
  3. 高性能:针对高性能进行了优化,实现了可观的吞吐量,同时最大限度地减少了 Java 垃圾收集对应用程序性能的影响。

ZGC的缺点:

  1. 高内存开销: ZGC 需要大量内存才能有效运行。
  2. 兼容性有限: ZGC 仅在某些平台上可用,包括 Linux/x64,并且至少需要 JDK 11。
  3. 更高的 CPU 利用率:由于其先进的功能,ZGC 可能与其他垃圾收集器相比消耗更多的 CPU 资源,从而可能影响整体应用程序的性能。

雪兰多Shenandoah

Shenandoah是一款 Java 垃圾收集器,旨在实现超低暂停时间,同时保持高吞吐量。作为并发垃圾收集器,它与应用程序并行运行,非常适合对延迟敏感的应用程序。

要使用 Shenandoah Collector,请传入以下参数:

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

雪兰多的优势:

  1. 超低暂停时间:提供最短的暂停时间,使其成为实时或延迟敏感应用程序的理想选择。
  2. 低内存开销:有效管理内存,从而减少内存开销。
  3. 高吞吐量:尽管 Shenandoah 专注于降低暂停时间,但它仍然保持高吞吐量,确保最佳的应用程序性能。

Shenandoah 的缺点:

  1. 兼容性有限: Shenandoah 仅在某些平台上可用,包括 Linux/x64,并且至少需要 JDK 8u40。
  2. 增加 CPU 利用率:与其他垃圾收集器相比,这可能会消耗更多的 CPU 资源,从而可能影响整体应用程序的性能。

系统.gc()

Runtime.getRuntime().gc() 或 System.gc() 是要求 JVM 执行垃圾收集以释放一些内存的方法——强调“建议”这个词,因为这正是它所做的。

您无法强制进行垃圾收集。如果您收到“ java.lang.OutOfMemoryError ”,则调用 System,gc() 将无法解决问题,因为 JVM 通常会在抛出“ java.lang.OutOfMemoryError ”之前尝试运行垃圾收集器。可能的修复方法是使用不同的 GC 或增加堆大小。

一般来说,建议开发人员避免直接调用System.gc(),而是依赖JVM提供的自动垃圾收集功能。

垃圾收集的常见问题及其解决方法

以下是一些可能面临的问题:

内存不足错误:

当 JVM 内存不足时,就会发生此错误。要解决此问题,开发人员可以增加堆大小或优化应用程序以使用更少的内存。

  • -Xmx:为您的应用程序设置最大堆大小。
  • -Xms:为您的应用程序设置初始堆大小。

例如,要将最大堆大小设置为 2 GB,将初始堆大小设置为 512 MB,可以使用以下命令:

java -Xmx2g -Xms512m app.jar

长时间暂停:

垃圾收集期间的长时间暂停可能会导致应用程序无响应。要解决此问题,您可以选择专为低暂停时间设计的垃圾收集器或调整垃圾收集器参数。

内存泄漏:

内存泄漏是指对象未正确从内存中释放,导致内存使用量随时间增加。为了解决此问题,开发人员可以使用分析器等工具来识别内存泄漏并进行修复。

Java 内存管理的最佳实践

为了避免垃圾收集的常见问题并有效地管理内存,以下是一些最佳做法:

  1. 将引用设置为空:当不再需要某个对象时,始终将引用设置为空。
  2. 避免创建不必要的对象:创建不必要的对象会增加内存使用量并导致更频繁的垃圾回收。您应避免创建不必要的对象并尽可能重用现有对象。
  3. 使用匿名对象:这是当您不存储对对象的引用时。

例如,createUser(new User())。

  1. 不再需要时释放资源:使用外部资源(例如文件句柄或数据库连接)的对象应在不再需要时释放,以避免内存泄漏。

结论

无论您是内部开发还是决定外包 Java 开发,了解 Java 垃圾收集机制都是必须的,特别是如果您想提高 Java 应用程序的性能。我们详细研究了 Java 编程的这一重要部分,从垃圾收集工作原理的基础知识和不同类型的垃圾收集器到内存管理的细节。请记住,即使您雇用了 Java 开发人员,选择正确的 Java 垃圾收集类型并有效地管理内存也会对应用程序的速度产生很大的影响。继续探索,继续编码,并记住每一点效率都可以为更流畅、更快的应用程序做出贡献。祝您编码愉快!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/402119.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue2移动端(H5项目)项目基于vant实现select单选(支持搜索、回显、自定义下拉label展示功能)

一 最终效果 二、参数配置 1、代码示例&#xff1a; <t-selectv-model"formData.materialNo"valueKey"materialNo"showLabel"materialName"labelKey"label"label"判定品级"input-align"right"placeholder&qu…

Docker-命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Docker架构二、Docker进程相关命令&#xff08;一&#xff09;启动 docker 服务&#xff08;二&#xff09;重启 docker 服务&#xff08;三&#xff09;停…

尚硅谷MYSQL(12-13章)

第十二章数据类型 比如说tinyint&#xff08;4&#xff09;这个四表示的是那个取值范围的位数 他只是一个显示 比如说int后面应该是int&#xff08;11&#xff09;这是有符号的 无符号int是int&#xff08;10&#xff09; 如果写成int&#xff08;3&#xff09;但是存的数据是…

JS入门学习

JS引入方式 内部引入 内部脚本:将JS代码定义在HTML页面中 JavaScript代码必须位于<script></script>标签之间在HTML文档中&#xff0c;可以在任意地方&#xff0c;放置任意数量的<script>可以放在HTML文件的任意位置&#xff0c;一般会把脚本置于<body…

红酒与节日装饰:打造节日氛围的需备品

随着节日的脚步渐渐临近&#xff0c;节日的氛围也愈发浓厚。在这个特殊的时刻&#xff0c;红酒与节日装饰无疑成为了营造节日氛围的需备品。洒派红酒&#xff08;Bold & Generous&#xff09;作为定制红酒的品牌&#xff0c;其不同的韵味与节日装饰的精致整合&#xff0c;共…

通过域名和HTTPS上线MSF

使用受信任证书的Meterpreter载荷 现在大多数安全解决方案还会通过分析进出目标机器的流量来进行网络入侵检测。在这种情况下&#xff0c;很可能即使使用编码器绕过了防病毒软件&#xff0c;但有效载荷也会在尝试连接到我们的侦听器时被捕获和拦截。 # 准备工作 首先需要准备…

SpringBoot + Hadoop + HDFS + Vue 实现一个简单的文件管理系统

1. 安装前的准备工作 1.1 更新系统并安装必要的工具 在终端中运行以下命令&#xff1a; sudo apt-get update sudo apt-get install -y ssh rsync curl1.2 安装 Java 如果系统中没有安装 Java&#xff0c;可以通过以下命令安装 OpenJDK&#xff1a; sudo apt-get install …

基于ESP32的智能门锁系统测试

项目介绍 基于ESP32的智能门锁系统。可以运用在商务办公、家用住宅、酒店以及公租房短租公寓等领域。基于esp32的智能门锁系统是生物识别技术和嵌入式系统技术的完美结合&#xff0c;基于ESP32系统进行开发&#xff0c;同时在云端服务器搭建了MQTT服务器并连接开源的家庭自动化…

工商业和户用光伏区别及怎样运维

工商业光伏系统和户用光伏系统在设计、安装和运维方面存在一些显著的区别。首先&#xff0c;工商业光伏系统通常安装在工厂、办公楼、商场等大型建筑物的屋顶或空地上&#xff0c;而户用光伏系统则主要安装在居民住宅的屋顶上。工商业光伏系统的规模一般较大&#xff0c;发电量…

Unity | AmplifyShaderEditor插件基础(第二集:模版说明)

目录 一、前言 二、核心模版和URP模版 1.区别介绍 2.自己的模版 三、输出节点 1.界面 2.打开OutPut 3.ShderType 4.ShaderName 5.Shader大块内容 6.修改内容 四、预告 一、前言 内容全部基于以下链接基础以上讲的。 Unity | Shader基础知识&#xff08;什么是shader…

Android 实现动态换行显示的 TextView 列表

在开发 Android 应用程序时&#xff0c;我们经常需要在标题栏中显示多个 TextView&#xff0c;而这些 TextView 的内容长度可能不一致。如果一行内容过长&#xff0c;我们希望它们能自动换行&#xff1b;如果一行占不满屏幕宽度&#xff0c;则保持在一行内。本文将带我们一步步…

仅12%程序员担心被AI取代 62%开发者在使用AI工具

**根据Stack Overflow近日发布的2024年开发者调查报告&#xff0c;只有12%的开发者认为AI威胁到了他们当前的工作&#xff0c;而高达70%的受访者已经将AI工具整合到了自己的工作流程中。**该调查共有超过6.5万名开发者参与&#xff0c;结果显示&#xff0c;使用AI工具的开发者比…

Java知识点一——列表、表格与媒体元素

显示表格边框&#xff1a;<table border"1"></table> 因为初始的表格是没有边框的 collapse相邻的单元格共用同一条边框&#xff08;采用 collapsed-border 表格渲染模型&#xff09;。 separate默认值。每个单元格拥有独立的边框&#xff08;采用 sep…

什么是实时数据仓库? 优势与最佳实践

在当今数据驱动的世界中&#xff0c;许多企业使用实时数据仓库来满足其分析和商业智能 (BI) 需求。这使他们能够做出更好的决策、推动增长并为客户提供价值。 数据仓库是一种数据存储和管理系统&#xff0c;其设计目标只有一个&#xff1a;管理和分析数据&#xff0c;以实现商…

掌握Jenkins自动化部署:从代码提交到自动上线的全流程揭秘

Jenkins自动化部署是现代软件开发中不可或缺的一部分&#xff0c;它不仅简化了代码的发布过程&#xff0c;还为整个团队带来了无与伦比的效率和协作力。想象一下&#xff0c;开发者们可以专注于编写高质量的代码&#xff0c;而不是为繁琐的手动部署所烦恼&#xff1b;测试人员能…

Python进阶之3D图形

Python进阶之3D图形 在数据可视化中&#xff0c;2D图形通常可以满足大多数需求。然而&#xff0c;对于一些复杂的数据或分析&#xff0c;3D图形可以提供更多的视角和洞察。在Python中&#xff0c;使用 Matplotlib 和 Plotly 等库可以轻松创建各种3D图形。本文将介绍如何使用这…

C++_2_ inline内联函数 宏函数(2/3)

C推出了inline关键字&#xff0c;其目的是为了替代C语言中的宏函数。 我们先来回顾宏函数&#xff1a; 宏函数 现有个需求&#xff1a;要求你写一个Add(x,y)的宏函数。 正确的写法有一种&#xff0c;错误的写法倒是五花八门&#xff0c;我们先来“见不贤而自省也。” // …

SpringCloud的能源管理系统-能源管理平台源码

介绍 基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码 软件架构

提升体验:UI设计的可用性原则

在中国&#xff0c;每年都有数十万设计专业毕业生涌入市场&#xff0c;但只有少数能够进入顶尖企业。尽管如此&#xff0c;所有设计师都怀揣着成长和提升的愿望。在评价产品的用户体验时&#xff0c;我们可能会依赖直觉来决定设计方案&#xff0c;或者在寻找改善产品体验的切入…

【STM32F4】——DMA初始化结构体详解

一.DMA_InitTypeDef 初始化结构体 typedef struct {uint32_t DMA_Channel; //通道选择 uint32_t DMA_PeripheralBaseAddr;//外设地址uint32_t DMA_Memory0BaseAddr; //存储器 0 地址uint32_t DMA_DIR; //传输方向 uint32_t DMA_BufferSize; /…