当前位置: 首页 > news >正文

linux perf

perf是Linux性能分析工具的集合,它提供了丰富的命令来收集和分析程序运行时的性能数据。perf能够报告CPU使用率、缓存命中率、分支预测成功率等多种硬件级别的事件,同时也支持软件级别的事件,如页面错误、任务切换等。perf是理解程序性能瓶颈、进行性能优化的重要工具。

perf的主要作用

  1. 性能分析:通过监控硬件和软件事件,帮助开发者理解程序的性能瓶颈。
  2. 热点函数定位:找出程序中CPU使用时间最多的函数。
  3. 系统监控:监控系统级别的性能指标,如CPU使用率、上下文切换次数等。
  4. 调用栈分析:提供函数调用栈信息,帮助开发者理解函数调用关系。

perf的基本用法

  1. 安装:在大多数Linux发行版中,perf工具包含在linux-tools包中。可以通过包管理器安装,例如,在Ubuntu上使用sudo apt-get install linux-tools-common linux-tools-generic

  2. 列出可用事件:使用perf list命令可以查看所有可监控的事件。

  3. 性能统计perf stat命令用于收集和报告程序的性能统计信息。例如,perf stat ./your_program会运行your_program并报告其性能统计。

  4. 记录性能事件perf record命令用于记录特定性能事件。例如,perf record -e cycles ./your_program会记录程序运行时的CPU周期事件。

  5. 生成报告perf report命令用于分析perf record收集的数据,并生成报告。这个报告可以帮助开发者定位性能瓶颈。

  6. 注解源代码perf annotate命令用于将性能事件映射到源代码上,帮助开发者理解哪些代码行是热点。

  7. 查看调用栈:使用perf record时加上-g选项可以记录调用栈信息,然后使用perf report查看,这对于理解函数间的调用关系非常有用。

示例

  • 收集性能统计

    perf stat -e cache-misses,cache-references,instructions,cycles ./your_program
    

    这个命令会运行your_program,并报告缓存未命中次数、缓存引用次数、指令数和CPU周期数。

  • 记录和报告性能数据

    perf record -g ./your_program
    perf report
    

    这会记录your_program的性能数据,并生成一个性能报告,报告中包含了热点函数和调用栈信息。

通过使用perf工具,开发者可以深入了解程序的运行时性能,从而进行有效的性能优化。

分析Java项目的性能时,perf可以帮助你理解底层的系统和硬件层面的性能问题,但由于Java运行在虚拟机(JVM)之上,直接使用perf可能不足以提供足够的信息来定位到具体的Java方法。不过,通过结合使用perf和其他工具,可以有效地分析Java应用的性能。以下是一些步骤和技巧:

1. 开启JVM的本地符号

为了让perf能够识别Java方法,需要确保JVM启动时带有适当的参数来导出本地符号。对于HotSpot JVM,可以使用以下参数:

-XX:+PreserveFramePointer -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints

这些参数的作用是:

  • -XX:+PreserveFramePointer:保留帧指针,以便perf可以构建调用栈。
  • -XX:+UnlockDiagnosticVMOptions:解锁诊断选项。
  • -XX:+DebugNonSafepoints:确保即使在非安全点也能生成足够的调试信息。
2. 使用perf record收集性能数据

运行你的Java应用,并使用perf record来收集性能数据。例如:

perf record -F 99 -p $(pgrep -n java) -g -- sleep 30

这个命令会:

  • -F 99:以每秒99次的频率采样。
  • -p $(pgrep -n java):指定要监控的进程ID,这里使用pgrep来找到Java进程的PID。
  • -g:记录调用栈信息,以便分析。
  • -- sleep 30:收集30秒的性能数据。
3. 使用perf report分析性能数据

收集完数据后,使用perf report来查看性能报告。这将显示CPU使用最多的函数和调用栈。

4. 使用perf-map-agentFlameGraph工具

由于perf默认可能无法解析Java方法名称,你可以使用perf-map-agent来生成Java方法的映射文件,然后perf就能识别Java方法了。perf-map-agent是一个开源工具,可以在GitHub上找到。

perf默认情况下可能无法解析Java方法名称,因为Java方法在运行时是由Java虚拟机(JVM)动态编译的,而不是直接作为二进制代码存在。为了让perf能够识别Java方法,可以使用perf-map-agent来生成一个包含Java方法名称和相应内存地址的映射文件。这个映射文件随后可以被perf使用,以便在性能分析报告中显示具体的Java方法名称。

使用perf-map-agent的步骤
    1. 安装perf-map-agent
    • 首先,确保你有一个编译环境(如gccmake),因为安装perf-map-agent可能需要编译一些源代码。
    • 克隆perf-map-agent的GitHub仓库:
      git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
      
    • 进入perf-map-agent目录,并使用cmakemake编译项目:
      cd perf-map-agent
      cmake .
      make
      
    1. 生成映射文件
    • 在运行你的Java应用的同时,使用perf-map-agent生成映射文件。这通常涉及到运行一个特定的脚本或命令,该命令会附加到运行中的JVM进程,并生成映射文件/tmp/perf-<pid>.map,其中<pid>是JVM进程的ID。
    • 例如,使用create-java-perf-map.sh脚本(这个脚本随perf-map-agent提供):
      ./create-java-perf-map.sh <pid>
      
      这里<pid>是你的Java应用的进程ID。
    1. 使用perf进行性能分析
    • 现在,当你使用perf recordperf report进行性能分析时,perf将能够利用生成的映射文件来解析Java方法名称。
    • 进行性能分析:
      perf record -F 99 -p <pid> -g -- sleep 30
      perf report
      
    1. 查看性能报告
    • 使用perf report查看性能报告时,你应该能看到具体的Java方法名称,而不仅仅是内存地址。这使得分析Java应用的性能问题变得更加直观和容易。
注意事项
  • 确保你的perf版本和Linux内核版本兼容,以便正确地解析映射文件。
  • 使用perf-map-agent时,可能需要根据你的系统环境调整编译命令或脚本。
  • 生成的映射文件仅在Java应用运行期间有效。如果Java应用重启,你需要重新生成映射文件。

通过使用perf-map-agentperf工具能够提供更加详细和有用的性能分析数据,帮助开发者优化Java应用的性能。

生成映射文件后,你还可以使用Brendan Gregg的FlameGraph工具来生成火焰图,这是一种直观展示性能瓶颈的图表。

FlameGraph是一个强大的工具,用于可视化和分析软件性能,特别是CPU使用情况。对于Java项目,结合perfperf-map-agent和FlameGraph,可以有效地识别性能热点和瓶颈。以下是如何使用这些工具来分析和优化Java项目的步骤:

FlameGraph使用步骤
步骤1:收集性能数据
  1. 确保Java应用以允许性能分析的模式运行。这通常意味着需要使用特定的JVM参数,如-XX:+PreserveFramePointer,以便perf能够获取到更准确的调用栈信息。

  2. 使用perf收集性能数据。找到Java应用的进程ID(PID),然后运行:

    perf record -F 99 -p [PID] -g -- sleep [duration]
    

    其中[PID]是Java进程的ID,[duration]是收集数据的时间长度(秒)。

步骤2:生成映射文件
  1. 安装perf-map-agent。按照之前的指导安装并编译perf-map-agent

  2. 生成映射文件。在Java应用运行的同时,使用perf-map-agentperf生成映射文件:

    cd perf-map-agent/out
    java -cp attach-main.jar:$JAVA_HOME/lib/tools.jar net.virtualvoid.perf.AttachOnce [PID]
    

    这将为perf生成一个映射文件,使其能够解析Java方法名称。

步骤3:生成FlameGraph
  1. 使用perf生成堆栈折叠文件

    perf script > out.perf
    
  2. 使用FlameGraph工具生成火焰图

    cd FlameGraph
    ./stackcollapse-perf.pl ../out.perf > out.folded
    ./flamegraph.pl out.folded > flamegraph.svg
    

    这将生成一个名为flamegraph.svg的火焰图文件,你可以使用任何支持SVG格式的浏览器或图像查看器打开它。
    图片示例在这里插入图片描述

步骤4:分析火焰图
  • 查看火焰图:火焰图的每一个“火焰”代表一个函数调用栈,其中的宽度表示该函数及其子函数占用CPU时间的比例。
  • 识别热点:火焰图的顶部是CPU使用最多的函数。寻找“宽”的火焰,这些是你的性能热点。
  • 深入分析:从热点函数开始,向下追踪其调用栈,了解性能瓶颈的来源。
步骤5:优化代码
  • 优化热点函数:针对火焰图中识别出的热点函数,考虑算法优化、减少不必要的计算、优化数据结构等方法。
  • 重构代码:如果性能瓶颈是由于不合理的代码结构引起的,考虑重构代码以提高效率。
  • 利用并发:对于可以并行处理的任务,考虑使用Java的并发和多线程功能来提高性能。
5. 结合使用jstack

对于Java应用,jstack是一个非常有用的工具,它可以生成Java线程的堆栈跟踪。通过将perf的输出与jstack的输出相结合,可以更准确地定位性能问题。
jstack 是一个用于生成 Java 线程堆栈跟踪的工具,它可以帮助开发者分析和诊断 Java 应用程序的性能问题,特别是线程相关的问题。以下是如何使用 jstack 分析 Java 项目的步骤:

jstack使用步骤
步骤1:找到 Java 进程 ID

首先,你需要找到正在运行的 Java 应用程序的进程 ID(PID)。你可以使用 jpsps 命令来找到 PID。

jps -l

或者:

ps -ef | grep java
步骤2:生成线程堆栈跟踪

使用 jstack 命令生成指定 Java 进程的线程堆栈跟踪。假设 PID 是 12345,你可以运行:

jstack -l 12345 > thread_dump.txt

这将生成一个包含所有线程堆栈跟踪的文件 thread_dump.txt

步骤3:分析线程堆栈跟踪

打开 thread_dump.txt 文件,分析其中的线程堆栈信息。以下是一些常见的分析方法:

  1. 查找死锁jstack 会自动检测死锁并在输出中报告。如果存在死锁,你会在输出中看到类似 “Found one Java-level deadlock” 的信息。

  2. 分析线程状态:每个线程的堆栈跟踪信息会包含线程的状态(如 RUNNABLEBLOCKEDWAITING 等)。通过分析线程状态,可以了解线程是否在等待资源、被阻塞或正在运行。

  3. 查找热点代码:如果某些线程的堆栈跟踪信息显示它们在相同的方法中花费了大量时间,这些方法可能是性能瓶颈。你可以重点分析这些方法,寻找优化的机会。

  4. 分析锁争用:如果多个线程在等待相同的锁,可能会导致性能问题。通过分析堆栈跟踪信息中的锁信息,可以识别锁争用的情况。

示例分析

以下是一个简单的 jstack 输出示例:

"main" #1 prio=5 os_prio=0 tid=0x00007f8c5400a000 nid=0x1b03 runnable [0x00007f8c58bfe000]java.lang.Thread.State: RUNNABLEat com.example.MyClass.myMethod(MyClass.java:123)at com.example.MyClass.run(MyClass.java:456)at java.lang.Thread.run(Thread.java:748)"Thread-1" #2 prio=5 os_prio=0 tid=0x00007f8c5400b000 nid=0x1b04 waiting on condition [0x00007f8c58cff000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x00000000d5c6c6c8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)at com.example.MyClass.run(MyClass.java:789)at java.lang.Thread.run(Thread.java:748)

在这个示例中:

  • 主线程(main)处于 RUNNABLE 状态,正在执行 com.example.MyClass.myMethod 方法。
  • Thread-1 线程处于 WAITING 状态,正在等待一个 ReentrantLock
优化建议
  1. 优化热点代码:对于处于 RUNNABLE 状态的线程,分析其堆栈跟踪信息,找出占用大量时间的方法,并进行优化。

  2. 减少锁争用:对于处于 BLOCKEDWAITING 状态的线程,分析其等待的锁,尝试减少锁的持有时间或使用更细粒度的锁。

  3. 避免死锁:如果检测到死锁,分析相关线程的堆栈跟踪信息,找出死锁的原因,并修改代码以避免死锁。

通过使用 jstack 生成和分析线程堆栈跟踪信息,可以有效地诊断和优化 Java 应用程序的性能问题。

总结

虽然perf主要用于分析底层系统和硬件性能,但通过上述方法,它也可以结合使用perf-map-agentFlameGraphjstack工具,成为分析Java应用性能的有力工具。

相关文章:

linux perf

perf是Linux性能分析工具的集合&#xff0c;它提供了丰富的命令来收集和分析程序运行时的性能数据。perf能够报告CPU使用率、缓存命中率、分支预测成功率等多种硬件级别的事件&#xff0c;同时也支持软件级别的事件&#xff0c;如页面错误、任务切换等。perf是理解程序性能瓶颈…...

Linux--网络层IP

IP协议 IP协议&#xff0c;全称Internet Protocol&#xff08;互联网协议&#xff09;&#xff0c;是TCP/IP协议族中的核心协议之一&#xff0c;用于在互联网络上进行数据的传输。IP协议的主要功能是确保数据从一个网络节点&#xff08;如计算机、服务器、路由器等&#xff09…...

浅谈vite之import.meta

一. 解析 开发者使用一个模块时&#xff0c;有时需要知道模板本身的一些信息&#xff08;比如模块的路径&#xff09;。现在有一个提案&#xff0c;为 import 命令添加了一个元属性 import.meta&#xff0c;返回当前模块的元信息。 import.meta只能在模块内部使用&#xff0c;如…...

【Pytorch实用教程】Pytorch中nn.Sequential的用法

nn.Sequential 是 PyTorch 中用于构建神经网络的一种容器类,它可以按顺序封装多个子模块(层),并依次将输入数据传递给这些子模块。这样可以简化模型的定义,使得代码更加简洁和易读。 文章目录 基本用法方法一:直接传递子模块方法二:使用 `OrderedDict`动态构建模型优点注…...

Shopify被封?Shopify店铺开店到防封全面指南

Shopify&#xff0c;作为独立电商建站领域的佼佼者&#xff0c;其SaaS模式简化了建站流程&#xff0c;无需编程背景即可创建线上店铺&#xff0c;吸引了众多商家的目光。本文将详细讲解Shopify店铺从注册、运营到防封的每一个关键环节&#xff0c;为商家提供一站式指导&#xf…...

11. 盛最多水的容器

一题目&#xff1a; 二&#xff1a;代码&#xff1a; class Solution { public:int maxArea(vector<int>& height) {int l0;int rheight.size()-1;int ans0;while(l<r){int a(r-l)*min(height[l],height[r]);ansmax(ans,a);if(height[l]<height[r]) l;else r-…...

react如何父子组件传参

在React中&#xff0c;父子组件之间的传参主要通过props&#xff08;属性&#xff09;来实现。子组件通过props接收来自父组件的数据&#xff0c;而父组件则可以通过在子组件标签上设置属性&#xff08;即props&#xff09;来传递数据。下面是一个简单的例子来说明这个过程。 …...

【C++】二维数组 数组名

二维数组名用途 1、查看所占内存空间 2、查看二维数组首地址 针对第一种用途&#xff0c;还可以计算数组有多少行、多少列、多少元素 针对第二种用途&#xff0c;数组元素、行数、列数都是连续的&#xff0c;且相差地址是有规律的 下面是一个实例 #include<iostream&g…...

【蘑菇书EasyRL】强化学习,笔记整理

【蘑菇书EasyRL】强化学习&#xff0c;笔记整理 1.笔记整理1.1 学习和决策代码框架 2. 遇到的buggym 环境&#xff0c;新版本python无法使用env_specs envs.registry.all() 报错 蘑菇书的教程地址&#xff1a; https://datawhalechina.github.io/easy-rl/#/chapter1/chapter1?…...

尚硅谷谷粒商城项目笔记——三、安装docker【电脑CPU:AMD】

三、安装docker 注意&#xff1a; 因为电脑是AMD芯片&#xff0c;自己知识储备不够&#xff0c;无法保证和课程中用到的环境一样&#xff0c;所以环境都是自己根据适应硬件软件环境重新配置的&#xff0c;这里的虚拟机使用的是VMware。 首先关闭防火墙和安全策略 systemctl…...

【8-9月份唯一机械电气计算机主题的IEEE会议】第七届机电一体化与计算机技术工程国际学术会议(MCTE 2024,8月23-25)

由广东博士创新发展促进会、输变电装备技术全国重点实验室联合主办&#xff0c;重庆大学电气工程学院、AEIC学术交流中心协办的第七届机电一体化与计算机技术工程国际学术会议&#xff08;MCTE 2024&#xff09;将于2024年8月23-25日在中国广州隆重举行。 大会诚挚邀请您投递相…...

YOLOv8改进 | 主干网络 | 简单而优雅且有效的VanillaNet 【华为诺亚方舟】

秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录 :《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有80+篇内容,内含各种Head检测头、损失函数Loss、…...

Tomcat高可用集群(实例详解)

一.环境准备 虚拟机的版本&#xff1a;VMware-workstation-full-15.5.6-16341506.exe系统镜像版本&#xff1a;CentOS-6.10-x86_64-bin-DVD1.iso&#xff0c;全新安装&#xff0c;桌面版&#xff0c;可上网系统内存大小&#xff1a;1GB系统硬盘大小&#xff1a;20GB连接工具版…...

搭建自己的金融数据源和量化分析平台(五):更新两市退市股票信息

在前面的股票列表设计中&#xff0c;我们有一个list_status字段&#xff0c;可能的值为L上市 D退市 P暂停上市。 由于股票可能会被退市&#xff0c;因此需要该字段来维护上市状态。 深市爬虫&#xff1a; # 读取深交所最新退市股票列表 def get_delisted_stock_list():cache_f…...

Redis复习总结

之前写的博客太杂,最近想把Redis的知识点再系统的过一遍,带着自己的理解使用简短的话把一些问题总结一下,尤其是开发中和面试中的高频问题,基础知识点参考–>Redis入门、Spring Cache,这篇不再赘述。 目录 基础简介;与Memcached的区别;为什么作为mysql缓存?如何保证R…...

基于JSP的医院挂号系统

你好&#xff0c;我是专注于医疗信息系统的计算机专业毕业生。如果您对医院挂号系统感兴趣或有相关需求&#xff0c;欢迎随时联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;MyEclipse 系统展示 首页 管理员功能模…...

Chainlit快速实现AI对话应用1 分钟内实现聊天数据的持久化保存

概述 默认情况下&#xff0c;Chainlit 应用不会保留其生成的聊天和元素。即网页一刷新&#xff0c;所有的聊天记录&#xff0c;页面上的所有聊天记录都会消失。但是&#xff0c;存储和利用这些数据的能力可能是您的项目或组织的重要组成部分。 一旦启用&#xff0c;数据持久性…...

STM32DMA数据传输

我估计大多数人学这么久连听说都没听说过DMA&#xff0c;更不用提知道它是干嘛的。其实DMA的本质就是一个数据的搬运工。平常的时候当我们没有配置的时候&#xff0c;一直都是CPU在搬运数据&#xff0c;但是这个活又累又没有技术含量&#xff0c;所以DMA的重要性还是有的。 目…...

Python学习笔记50:游戏篇之外星人入侵(十一)

前言 本篇文章接着之前的内容&#xff0c;继续对游戏功能进行优化&#xff0c;主要是优化游戏状态以及对应的处理。 状态 一个游戏包含多种状态&#xff0c;这个状态是一个可以很复杂也可以很简单的内容。条件所限&#xff0c;我们这个游戏的状态就比较简单&#xff1a; 未…...

vue3踩坑问题记录

//vue3element-plus //1、placeholder换行显示 const startTxt ref() const contentText ref<any>() startTxt.value "请描述问题内容、例如&#xff1a;" historyData.prompt.forEach((el:any)>{contentText.value \n${el.question}}) <ElInputv-mo…...

Python 爬虫实战:Scrapy 框架详解与应用

&#x1f6e0;️ Scrapy 框架基本使用 Scrapy 是一个强大的 Python 爬虫框架&#xff0c;提供了用于提取和处理网页数据的功能。以下是 Scrapy 的基本使用步骤&#xff1a; 安装 Scrapy pip install scrapy创建 Scrapy 项目 scrapy startproject myproject这将生成一个基础…...

60 函数参数——关键参数

关键参数主要指调用函数时的参数传递方式&#xff0c;与函数定义无关。 通过关键参数可以按参数名字传递值&#xff0c;明确指定哪个值传递给哪个参数&#xff0c;实参顺序可以和形参顺序不一致&#xff0c;但不影响参数值的传递结果&#xff0c;避免了用户需要牢记参数位置和…...

wps 最新 2019 专业版 下载安装教程,解锁全部功能,免费领取

文章目录 前言软件介绍软件下载安装步骤激活步骤小福利&#xff08;安卓APP&#xff09;软件介绍软件下载安装步骤 前言 本篇文章主要针对WPS2019专业版的安装下载进行详细讲解&#xff0c;软件已激活&#xff0c;可放心使用&#xff1b;并且可以进行账号登录&#xff0c;进行…...

前端(三):Ajax

一、Ajax Asynchronous JavaScript And XML&#xff0c;简称Ajax&#xff0c;是异步的JavaScript和XML。 作用&#xff1a;数据交换&#xff0c;通过Ajax可以给服务器发送请求&#xff0c;并获取服务器响应的数据。异步交互&#xff1a;可以在不重新加载整个页面的情况下&…...

启动 /使用/关闭 Redis 服务器

1. Linux 启动 Linux 系统启动 Redis 有两种方法&#xff0c;分别是前台启动&#xff0c;后台启动&#xff0c;两者各有差异&#xff1b; &#xff08;1&#xff09;前台启动 首先&#xff0c;需要进入 bin 路径(安装路径不同输入的命令也不同); 个人的命令&#xff08;一般…...

Linux系统中的高级SELinux安全策略定制技术

随着信息技术的发展&#xff0c;计算机系统的安全性变得越来越重要。在开源世界中&#xff0c;Linux作为一种广泛应用的操作系统&#xff0c;其安全性一直备受关注。其中&#xff0c;SELinux&#xff08;Security-Enhanced Linux&#xff09;作为Linux系统中的一个安全模块&…...

使用 Ansible Blocks 进行错误处理

注&#xff1a;机翻&#xff0c;未校。 How to Use Ansible Blocks Make your Playbooks more readable and maintainable using Blocks feature in Ansible. 使用 Ansible 中的块功能使 Playbook 更具可读性和可维护性。 Jul 15, 2024 — LHB Community How to Use Ansible…...

java中的静态变量和实例变量的区别

java中的静态变量和实例变量的区别 在Java中&#xff0c;静态变量&#xff08;也称为类变量&#xff09;和实例变量是两种不同类型的变量&#xff0c;它们在多个方面存在显著的区别。以下是它们之间的一些主要区别&#xff1a; 存储位置 静态变量&#xff1a;存储在方法区&am…...

【Effecutive C++】条款02 尽量以const, enum, inline替换 #define

Prefer consts, enums, and inline to #define. 这个条款或许改为“宁可以编译器替换预处理器”比较好&#xff0c;因为或许#define不被视为语言的一部分。那正是它的问题所在。当你做出这样的事情&#xff1a; #define ASPECT_RATIO 1.653记号名称ASPECT_RATIO也许从未被编译…...

leetcode-226. 翻转二叉树

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,3,1]…...