JVM 垃圾回收机制和GC案例分析

1. 引言

Java 虚拟机(JVM)的垃圾回收(Garbage Collection, GC)机制,是自动内存管理的重要组成部分。它通过回收不再使用的对象,避免手动释放内存的麻烦。然而,随着系统复杂性的增加,GC 的效率对应用性能影响极大,特别是在电商交易系统中,GC 的延迟或频繁触发可能会导致系统响应时间变慢,影响用户体验。

本篇文章将深入探讨 JVM 的垃圾回收机制,重点介绍 年轻代老年代 的对象转化过程,常见的 垃圾回收器,以及如何在 Linux 系统中分析 Java 应用的 GC 过程,解决常见的 GC 问题。

2. JVM 垃圾回收模型概述

JVM 的内存结构分为两个主要区域:年轻代(Young Generation)老年代(Old Generation)。年轻代中对象生命周期较短,老年代中对象生命周期较长。JVM 的垃圾回收器通过分代回收策略,分别处理不同代中的对象。

2.1 年轻代(Young Generation)

年轻代存储新创建的对象,它又进一步划分为:

  • Eden 区:所有新创建的对象首先分配在 Eden 区。
  • Survivor 区:包括两个 Survivor 区,S0 和 S1,用于在 Minor GC 时保存存活的对象。

2.2 老年代(Old Generation)

当对象在年轻代中存活足够长时间,或者年轻代空间不足时,这些对象会被移到老年代。老年代主要存储生命周期较长的对象。

2.3 永久代/元空间

永久代(PermGen)存储类元数据,JDK 8 之后,永久代被元空间(Metaspace)替代。

3. 年轻代与老年代的对象转化过程

在 JVM 中,对象的生命周期管理基于“分代假说”,即大多数对象的生命周期都很短,少部分对象存活时间较长。基于这一假设,JVM 采用了年轻代与老年代的分代回收机制。

3.1 年轻代的对象分配

当使用 new 创建对象时,JVM 将对象分配到 Eden 区。若 Eden 区满,则会触发 Minor GC,GC 会检查存活的对象并将它们移到 Survivor 区。每次 Minor GC 后,存活的对象会在 S0 和 S1 之间来回交换。当对象在 Survivor 区存活到一定次数(达到 年龄阈值),就会被提升到老年代。

// 电商系统中的订单对象分配
Order newOrder = new Order();  // 新对象创建,存储在 Eden 区
3.2 从年轻代到老年代的晋升

当一个对象存活过多次 Minor GC 后,JVM 会将其从 Survivor 区提升到 老年代。老年代中的对象生命周期更长,回收频率较低。

在这里插入图片描述

3.3 垃圾回收的分代策略

年轻代对象的生命周期短、创建频繁,JVM 通过快速回收策略处理。老年代则存放生命周期较长的对象,垃圾回收发生频率低,但回收时间较长。Minor GC 主要清理年轻代,而 Major GC(或 Full GC) 则回收整个堆(包括年轻代和老年代)。

4. 常见垃圾回收器及其原理

JVM 提供了多种垃圾回收器,适用于不同的应用场景。下面是几种常见的垃圾回收器及其适用场景。

4.1 Serial GC

Serial GC 是最简单的垃圾回收器,适用于单线程环境。它会暂停所有应用线程,串行执行 GC,回收年轻代和老年代。

  • 优点:实现简单,适用于单线程应用。
  • 缺点:在多线程环境下表现较差,暂停时间较长。
# 使用 Serial GC
java -XX:+UseSerialGC -jar ecommerce-system.jar

4.2 Parallel GC

Parallel GC 是默认的多线程垃圾回收器,适用于多核 CPU 环境。它并行地执行 Minor GC,尽可能减少垃圾回收的时间开销。

  • 优点:适用于 CPU 密集型应用,吞吐量高。
  • 缺点:在 GC 过程中,应用仍会暂停。
# 使用 Parallel GC
java -XX:+UseParallelGC -jar ecommerce-system.jar

4.3 CMS(Concurrent Mark-Sweep)GC

CMS GC 是一种低延迟的垃圾回收器,专注于减少老年代的回收时间。它通过并发标记和清理,尽量避免应用长时间停顿。

  • 优点:减少老年代 GC 的停顿时间,适合低延迟应用。
  • 缺点:对 CPU 资源的消耗较高,可能会出现“浮动垃圾”问题。
# 使用 CMS GC
java -XX:+UseConcMarkSweepGC -jar ecommerce-system.jar

4.4 G1 GC

G1 GC 是适用于大堆内存的垃圾回收器,它通过划分堆内存为多个区域(Region),并根据回收的价值优先回收最大的区域。G1 GC 结合了并行和并发的优势,能够提供可预测的停顿时间。

  • 优点:适合大内存低延迟应用,能够提供可配置的最大停顿时间。
  • 缺点:实现复杂,对性能调优要求较高。
# 使用 G1 GC
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar ecommerce-system.jar
4.5 ZGC 和 Shenandoah GC

ZGCShenandoah GC 是近年来推出的超低延迟垃圾回收器。它们的目标是将 GC 停顿时间控制在 10ms 以内,适合需要极低延迟的应用场景。

# 使用 ZGC
java -XX:+UseZGC -jar ecommerce-system.jar

5. 在 Linux 系统中分析 GC 日志

在生产环境或性能测试中,分析 JVM 的垃圾回收日志可以帮助我们识别内存管理的瓶颈,从而优化应用程序性能。通过 GC 日志,我们能够了解垃圾回收器的运行情况,识别频繁的 Full GC 或 Minor GC,以及分析 GC 的停顿时间、对象分配和晋升情况等。

5.1 启用 GC 日志

首先,我们需要启用 GC 日志功能,通过以下 JVM 参数可以记录详细的 GC 日志:

java -Xlog:gc*:file=gc.log:time,tags -jar ecommerce-system.jar

这些参数的含义如下:

  • -Xlog:gc*:启用 GC 日志记录。
  • file=gc.log:将 GC 日志输出到指定文件 gc.log 中。
  • time,tags:添加时间戳和 GC 事件标签,方便后续分析。

5.2 常用 GC 日志选项

在生产环境中,除了基础的 GC 日志记录,还可以启用更细粒度的日志,帮助分析性能问题。常见的 GC 日志选项如下:

# 打印GC详细信息,包括停顿时间、回收内存大小等
java -Xlog:gc*:gc.log:time,uptime,level,tags
  • time:显示绝对时间戳。
  • uptime:显示从 JVM 启动到当前的时间。
  • level:记录日志的级别(如 info、debug)。
  • tags:显示日志类别(如 GC 类型、阶段)。

5.3 GC 日志结构

GC 日志的典型输出结构如下:

[2024-09-14T12:00:00.123+0000][gc,start       ] GC(0) Pause Young (G1 Evacuation Pause) (young) 10M->5M(50M) 30ms
[2024-09-14T12:00:00.153+0000][gc,end         ] GC(0) Pause Young (G1 Evacuation Pause) (young) 30ms
[2024-09-14T12:01:01.567+0000][gc,start       ] GC(1) Pause Full (G1 Full GC) 40M->20M(100M) 250ms
[2024-09-14T12:01:01.817+0000][gc,end         ] GC(1) Pause Full (G1 Full GC) 250ms

日志内容解释:

  • GC 类型Pause Young 表示年轻代 GC,Pause Full 表示 Full GC。
  • 内存变动:如 10M->5M(50M),表示 GC 前年轻代占用 10MB,GC 后剩余 5MB,总共 50MB 可用内存。
  • GC 耗时:GC 操作耗时,如 30ms 表示 GC 操作耗时 30 毫秒。

6. 性能测试中的 GC 分析

在性能测试中,GC 的频繁触发或长时间停顿会影响应用的响应速度和吞吐量。以下是一些常见的 GC 问题及其分析思路:

6.1 问题:频繁的 Minor GC

在性能测试中,若观察到频繁的 Minor GC,通常表示年轻代内存分配过于频繁,可能是因为系统频繁创建短生命周期的对象,导致 Eden 区被快速填满。

日志示例
[2024-09-14T12:05:00.123+0000][gc,start       ] GC(10) Pause Young (G1 Evacuation Pause) 40M->20M(60M) 25ms
[2024-09-14T12:05:05.123+0000][gc,start       ] GC(11) Pause Young (G1 Evacuation Pause) 45M->25M(60M) 30ms
解决方式
  • 增加年轻代空间:通过增加 Eden 区或 Survivor 区的大小,减少 Minor GC 频率。可以通过 -XX:NewRatio-Xmn 参数调整年轻代的大小。

    java -Xms512m -Xmx2g -Xmn512m -jar ecommerce-system.jar
    
  • 对象分配优化:如果 GC 频繁触发,检查系统是否存在频繁创建临时对象的情况,可以通过对象池减少对象的创建与销毁。

优化后日志

通过调整后,可以观察到 Minor GC 频率的降低:

[2024-09-14T12:10:00.123+0000][gc,start       ] GC(12) Pause Young (G1 Evacuation Pause) 40M->20M(80M) 20ms
[2024-09-14T12:15:00.123+0000][gc,start       ] GC(13) Pause Young (G1 Evacuation Pause) 45M->25M(80M) 22ms

6.2 问题:频繁的 Full GC

Full GC 会暂停整个应用线程,对系统性能影响较大。如果日志中出现频繁的 Full GC,说明老年代空间不足,或对象晋升到老年代过多。

日志示例
[2024-09-14T12:20:01.567+0000][gc,start       ] GC(20) Pause Full (G1 Full GC) 200M->150M(300M) 500ms
[2024-09-14T12:22:01.567+0000][gc,start       ] GC(21) Pause Full (G1 Full GC) 220M->180M(300M) 600ms
解决方式
  • 增加老年代空间:可以通过增加老年代的空间来减少 Full GC 的触发。使用 -Xmx 参数增加堆内存大小。

    java -Xms1g -Xmx4g -jar ecommerce-system.jar
    
  • 选择合适的垃圾回收器:在低延迟需求的系统中,考虑使用 G1 GCZGC 代替传统的 Parallel GC 或 CMS GC,以减少 Full GC 的停顿时间。

    java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar ecommerce-system.jar
    
优化后日志

通过增加堆内存或使用合适的垃圾回收器,Full GC 的频率会大幅降低:

[2024-09-14T12:30:00.123+0000][gc,start       ] GC(22) Pause Full (G1 Full GC) 150M->100M(400M) 250ms
[2024-09-14T12:35:00.123+0000][gc,start       ] GC(23) Pause Full (G1 Full GC) 170M->120M(400M) 270ms

6.3 问题:长时间 GC 停顿

长时间的 GC 停顿会影响应用的响应时间,通常与老年代的垃圾回收耗时过长有关。为了减少 GC 停顿时间,可以选择合适的垃圾回收器,并优化 GC 参数。

日志示例
[2024-09-14T12:45:00.123+0000][gc,start       ] GC(30) Pause Full (G1 Full GC) 500ms
[2024-09-14T12:55:00.123+0000][gc,start       ] GC(31) Pause Full (G1 Full GC) 600ms
解决方式
  • 使用 G1 GC 或 ZGC:这些低停顿的垃圾回收器能够有效减少 GC 停顿时间。

    java -XX:+UseZGC -jar ecommerce-system.jar
    
  • 调优 GC 参数:在 G1 GC 中,-XX:MaxGCPauseMillis 可以帮助控制最大 GC 停顿时间。

    java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar ecommerce-system.jar
    
优化后日志

通过调整垃圾回收器,GC 停顿时间会明显减少:

[2024-09-14T13:00:00.123+0000][gc,start       ] GC(32) Pause Full (G1 Full GC) 250ms

7. 使用 GC 分析工具

在分析 GC 日志时,使用专业的分析工具可以大大提高效率,尤其是面对复杂的 GC 行为和大量的日志数据时,手动分析可能会耗费大量时间和精力。一个热门且功能强大的开源工具是 GCViewer,它可以将 GC 日志进行可视化,帮助我们更直观地分析和优化 JVM 的垃圾回收行为。

7.1 介绍 GCViewer

GCViewer 是一个开源的 GC 日志分析工具,支持多种类型的 JVM 垃圾回收器,包括 G1、CMS、Parallel、ZGC 等。它可以将 GC 日志中的事件、停顿时间、内存使用情况等可视化,并生成详细的报告,帮助开发者快速定位 GC 问题。

GCViewer 的主要功能包括:

  • GC 停顿时间的可视化:可以直观地看到每次 GC 事件的停顿时间。
  • 内存使用趋势分析:展示堆内存和非堆内存的使用趋势,帮助识别内存泄漏或分配不合理的问题。
  • GC 事件统计:提供 GC 次数、总时间、平均时间等详细的统计信息。
  • 生成报告:可以导出详细的 GC 分析报告,方便调优。

7.2 下载和安装 GCViewer

要使用 GCViewer,首先需要从其 GitHub 仓库下载源代码或预编译的 JAR 文件。

下载 GCViewer:

  1. 访问 GCViewer GitHub 仓库。
  2. 下载最新的 gcviewer-*.jar 文件。

7.3 使用 GCViewer 进行 GC 日志分析

以下是使用 GCViewer 分析 GC 日志的具体步骤:

步骤 1:生成 GC 日志

在启动 Java 应用程序时,确保已经启用了 GC 日志记录功能。例如,使用以下 JVM 参数启动你的电商交易系统应用,并生成 GC 日志文件:

java -Xlog:gc*:file=gc.log:time,tags -jar ecommerce-system.jar

步骤 2:打开 GCViewer

下载并安装 GCViewer 后,使用以下命令启动它:

java -jar gcviewer-1.36.jar

步骤 3:加载 GC 日志

启动 GCViewer 后,点击界面上的 File -> Open,然后选择你生成的 GC 日志文件 gc.log。GCViewer 会自动解析日志文件并展示出以下几项关键数据:

  • GC 停顿时间图:展示每次 GC 事件的停顿时间,帮助你发现可能导致系统性能下降的 Full GC 或长时间的 GC 停顿。
  • 堆内存使用情况图:显示年轻代和老年代的内存使用情况,便于分析对象在堆中的分配和晋升过程。
  • GC 事件统计:包括 Minor GC 和 Full GC 的次数、平均时间、最大时间等数据。

步骤 4:分析 GC 停顿时间

在 GCViewer 中,你可以直观地看到每次 GC 停顿时间的分布。下图展示了一个典型的 GCViewer 界面:

+------------------------------------+
|            GCViewer 图示            |
+------------------------------------+
|      |                             |
|  GC  |   GC 停顿时间 (ms)          |
|  图  |   ┌────────────────────┐   |
|      |   │ ●●●                 │   |
|      |   │    ●●●              │   |
|      |   │        ●●           │   |
|      |   └────────────────────┘   |
|------------------------------------|
|      |                             |
|  内  |   堆内存使用情况 (MB)      |
|  存  |   ┌────────────────────┐   |
|  图  |   │  ████               │   |
|      |   │        ████         │   |
|      |   │             ████    │   |
|      |   └────────────────────┘   |
+------------------------------------+

通过观察图中的 GC 停顿时间分布,能够快速发现哪些 GC 事件消耗了较长的时间。如果某次 Full GC 耗时明显过长,那么可能需要进一步优化老年代的内存分配,或考虑使用更高效的垃圾回收器(如 G1 或 ZGC)。

步骤 5:分析内存使用情况

在 GCViewer 中,还可以观察堆内存的使用情况,包括 Eden 区、Survivor 区和老年代的使用情况。通过这部分数据,可以判断对象是否过快地从年轻代晋升到老年代,或是否存在老年代内存不足导致的频繁 Full GC。

  • 如果 Eden 区频繁触发 Minor GC,可能需要调整年轻代的大小,使用参数 -Xmn-XX:NewRatio 进行优化。
  • 如果老年代频繁触发 Full GC,可能需要增加老年代的大小或调整晋升阈值,使用参数 -XX:MaxTenuringThreshold 或增加总堆内存 -Xmx

步骤 6:生成报告

GCViewer 还支持生成详细的报告,帮助你总结 GC 分析的结果。点击 File -> Export,可以将分析结果导出为 CSV 文件或图片格式,用于后续的性能优化和报告展示。

7.4 实例分析:电商交易系统的 GC 优化

假设你在测试一个电商交易系统时,发现系统在高并发情况下响应时间明显变慢。通过 GC 日志分析,发现系统频繁触发 Full GC,每次 GC 的停顿时间接近 500 毫秒,严重影响用户体验。

解决方案

  1. 通过 GCViewer,观察到老年代的使用率较高,很多对象过早晋升到老年代。
  2. 使用 -Xmx 增加堆内存大小,并调整年轻代和老年代的比例。
  3. 通过 -XX:MaxTenuringThreshold=10 参数,延迟对象晋升到老年代。
  4. 更换垃圾回收器,选择 G1 GC 以减少 Full GC 频率和停顿时间。

经过调整后,再次使用 GCViewer 分析 GC 日志,发现 Full GC 频率明显降低,停顿时间也减少至 200 毫秒以内,系统的整体响应速度得到提升。

8. 总结

垃圾回收机制在 JVM 中扮演着至关重要的角色,理解其原理和调优方法对于优化 Java 应用性能至关重要。通过深入分析 年轻代与老年代 的对象生命周期管理、不同 垃圾回收器 的工作机制以及在 Linux 系统中实际 GC 日志的分析,我们可以有效地解决电商交易系统中的性能瓶颈问题,提供更流畅的用户体验。

这篇文章从底层实现逻辑到 GC 日志的实际分析,展示了如何应对和解决 JVM 内存管理中的各种问题,帮助开发者在复杂的应用场景中做出正确的性能优化决策。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

4.C_数据结构_队列

概述 什么是队列: 队列是限定在两端进行插入操作和删除操作的线性表。具有先入先出(FIFO)的特点 相关名词: 队尾:写入数据的一段队头:读取数据的一段空队:队列中没有数据,队头指针 队尾指针满队&#…

劳特巴赫ICD调试器CMM调用烧录框架固件研究之C语言版本

接到客户一个项目是基本GD32F301C8XX的,尝试用手上的劳特巴赫仿真器对它进行开发操作,发现总是提示“FLASH algorithm did not execute completely” 怀疑是底层调用用烧录固件“~~/demo/arm/flash/word/stm32f300.bin”与芯片不兼容造成的,于是有了这编研究文档,多的不说直…

Spring4-IoC2-基于注解管理bean

目录 开启组件扫描 使用注解定义bean Autowired注入 场景一:属性注入 场景二:set注入 场景三:构造方法注入 场景四:形参注入 场景五:只有一个构造函数,无注解 场景六:Autowired和Quali…

Tcl lnit error: Can’t find a usable init.tcl in the following directories 问题解决

这个问题出现在我用py2exe打包了一个包含tkinter的图形化界面,在当前电脑上运行无问题,在移动到新电脑上后提示报错、 这里吐槽一下,新电脑上报错信息一闪而过,我用的土法子解决的,就是录视频然后0.25倍速度暂定找到报…

Acrobat 9 安装教程

软件介绍 Adobe Acrobat 是由Adobe公司开发的一款PDF(Portable Document Format,便携式文档格式)编辑软件。借助它,可以以PDF格式制作和保存文档,以便于浏览和打印,同时还可以使用一些高级工具来创建、编辑…

Qt 菜单栏、工具栏、状态栏、标签、铆接部件(浮动窗口) 设置窗口核心部件(文本编辑控件)的基本使用

效果 代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QToolBar> #include<QDebug> #include<QPushButton> #include<QStatusBar> #include<QLabel> #include<QDockWidget> #include<QTextEdi…

将事物分为三教九流?不妨通过logistic回归

和多元线性回归一样&#xff0c;逻辑回归也是建立“多对一”型变量之间的线性关系——也即找出线性方程的近似解。有所不同的是&#xff0c;逻辑回归的解只能出现0~1之间&#xff08;亦或就是0/1两种结果&#xff09;&#xff0c;这倒是有点像bool型和int型之间的区别了。实际上…

S32K3 工具篇7:如何使用VScode编译EB MCAL工程

S32K3 工具篇7&#xff1a;如何使用VScode编译EB MCAL工程 1. VScode工具与配置2. 使用VScode编译RTD MCAL工程2.1 使用EB tresos生成配置2.2 VScode 打开工程2.3 修改mk文件2.4 编译文件2.5 debug生成好的elf文件 对于EB配置的MCAL代码&#xff0c;通常是基于RTD去做&#xff…

GEO IGEO MEO介绍 和 北斗导航系统使用三轨道原因

GEO IGSO MEO基本轨道知识 中地球轨道&#xff08;MEO&#xff1a;Middle Earth Orbit&#xff09; 轨道高度2000-36000kmGPS、GLONASS都属于此类轨道 地球同步轨道&#xff08;或称对地静止轨道&#xff09;[同步转动] 轨道高度约为36000 km&#xff1b;此轨道上卫星运行方…

情感识别系统源码分享

情感识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

发工资-python

题目要求&#xff1a; 代码&#xff1a; import random from random import randintmoney 10000 for i in range(1, 21):performance randint(1, 10)if performance < 5:print(f"员工{i},绩效分{performance},低于5,不发工资&#xff0c;下一位")continueif m…

每日学习一个数据结构-倒排表

文章目录 示意图倒排表的基本概念倒排表的数据结构示例 倒排表的优点应用场景 倒排表&#xff08;Inverted Index&#xff09;&#xff0c;也称为反向索引或倒排文件&#xff0c;在信息检索系统中是一种重要的数据结构。它主要用于快速搜索文档中的关键词&#xff0c;并找到包含…

字典+泛型的栈与队列+委托

字典 在System.Collections.Generic下&#xff0c;对应HashTable,添加了泛型的特性&#xff0c;性能更高更安全&#xff0c;在内存中散列排布&#xff0c;存储也是键值对。 Dictionary<键的数据类型&#xff0c;值的数据类型> 字典名new Dictionary<键的数据类型&am…

异常冲突行为和危险识别系统源码分享

异常冲突行为和危险识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Co…

教你搭建一个wifi贴系统

大家好&#xff0c;我是鲸天科技千千&#xff0c;大家都知道我是做小程序开发的&#xff0c;平时会给大家分享一些互联网相关的创业项目&#xff0c;感兴趣的可以跟我关注一下。 搭建一个首先就是要搭建一个自己的wifi贴小程序&#xff0c;我们自己的工作就是把这个小程序推广…

CAN BUS

CAN BUS 原理 网上资料非常丰富&#xff0c;是车载系统主要BUS之一。 我们关注如下方面 can bus 是什么网络结构CAN BUS 协议ECU node实现其他 What is CAN Bus? Control Area Network (CAN) bus is a serial communication protocol that allows devices to exchange dat…

JavaScript web API part3

web API DOM 日期对象 > 得到当前系统的时间 new这个操作就是实例化 语法 const date new Date() or const date new Date(2004-11-3 08:00:00) 可以指定时间 > 可应用于通过系统时间和指定时间实现倒计时的操作 //得到当前时间const date new Date()console.lo…

HTML贪吃蛇游戏

文章目录 贪吃蛇游戏 运行效果代码 贪吃蛇游戏 贪吃蛇是一款经典的休闲益智游戏。本文将通过HTML5和JavaScript详细解析如何实现一个简易版的贪吃蛇游戏。游戏的主要逻辑包括蛇的移动、碰撞检测、食物生成等功能。以下是游戏的完整代码及注释解析。&#xff08;纯属好玩&#…

【PyQt6 应用程序】应用程序携带数据源文件一并打包

在开发好应用程序打包之后给到其他用户会发现数据文件比如封面图片不见了。 例如这样,很影响用户使用。 这里介绍一个非常简单的打包方法,不光要在打包命令的时候添加对应数据文件,在源码中也要进行一些简单的修改。 修改需要添加打包文件的地方。首先需要添加一个绝对路径…

九九乘法表-while-python

i 1 while i < 9:#j 1&#xff0c;条件为j < ij 1while j < i:print(f"{j}*{i}{i*j}\t",end)#先输出jj 1print()i 1运行结果截图&#xff1a;