打从第一次接触性能测试开始,我在工具选型这件事上就没少纠结。LoadRunner太重、商用授权贵得离谱,Locust写起来灵活但对没多少编码基础的同事不太友好,最后兜兜转转还是回到了JMeter。原因很简单:Apache基金会背书、纯Java实现、跨平台、免费开源、插件生态成熟,而且不管是接口功能测试、压力测试还是简单的接口自动化回归,它都能扛下来。今天我就把Jmeter的安装、配置、中文环境、内存调优,一直到第一个能跑起来的压测脚本、参数化、断言、非GUI执行和结果报告,整套流程掰开揉碎讲一遍。这篇内容面向前后端开发、测试工程师,以及刚转行做性能测试的朋友,只要你会基本的上网操作,跟着做就能落地。下面所有步骤都是我在Windows环境下亲测跑通的,Linux上的差异我也会补充说明。
1. 为什么我最后选了JMeter:从一次接口压测说起
1.1 那次被逼上梁山的压测需求
几年前我还在做电商后端,产品上线前一周,运营突然跑过来说大促期间要预估一下订单接口的承载能力,问我能不能给个"QPS能到多少"的结论。当时团队没有任何性能测试的积累,连压测工具都没统一。我第一反应是找LoadRunner,结果发现license问题绕不过去。后来试了试JMeter,从下载到跑出第一份报告,前后不到一个下午。也就是从那次之后,JMeter就成了我工具箱里的固定成员。
我第一次用JMeter压的接口很简单,就是一个下单查询接口,压到500并发的时候服务端开始出现超时,查下来发现是数据库连接池只有20个连接,线程都在排队。这个问题如果不压测,上线后大概率会在大促当天爆出来。所以压测的价值从来不是"跑个工具显得专业",而是在流量真正到来之前把系统的拐点找出来。
1.2 JMeter的定位和能力边界
很多人对JMeter有个误解,以为它只能做HTTP压测。实际上它的能力范围比想象中广得多:
- 协议支持广泛:HTTP/HTTPS、FTP、JDBC、JMS、SOAP/REST、TCP、SMTP等,通过插件还能扩展MQTT、gRPC这些。
- 不只是压测:接口功能测试、参数化数据驱动测试、回归测试都能做,很多团队直接拿它当轻量的接口自动化框架。
- 可视化与脚本化并存:既能在GUI里点点点配置,也能用Groovy/BeanShell写自定义逻辑,灵活度足够。
- 可扩展:插件机制成熟,官方Plugins Manager可以一键装各种扩展,比如MQTT插件、自定义监听器。
我个人的经验是,JMeter最舒服的场景是中大型接口的性能基线测试和长期回归对比,尤其是需要和现有CI流程结合的时候。它的命令行模式(非GUI)配合HTML报告,可以很自然地塞进Jenkins流水线里。
1.3 学JMeter最容易走的弯路
新手学JMeter,弯路基本集中在三个地方。第一个是环境没配好就开始跑,JDK版本和JMeter版本不匹配,一启动就报错,然后开始怀疑人生。第二个是GUI模式下直接压高并发,JMeter的GUI本身很吃资源,几百个线程一起跑,GUI先卡死了,误以为是服务端扛不住。第三个是不区分功能测试和压测的脚本写法,把带大量调试监听器的脚本直接拿去压测,性能数据全被监听器拖垮。
这三点我会在后面分别展开讲,尤其是环境这一步,很多人栽在JDK版本上。记住一句话:JMeter本质是个Java程序,Java环境不对,后面全是白搭。
2. 环境准备:JDK与JMeter的版本搭配与安装
2.1 JDK版本到底选哪个
JMeter是基于Java开发的,所以必须要先有Java运行环境。这里是第一个大坑。
不同JMeter版本对JDK的最低要求是不一样的。早期JMeter 5.0以前,JDK 8就够用。但从JMeter 5.4开始,官方推荐用JDK 8以上,而JMeter 5.6之后的版本,我实测JDK 17跑起来体验最稳。表格整理一下:
| JMeter版本 | 推荐的JDK版本 | 说明 |
|---|---|---|
| 5.3及以前 | JDK 8 | 稳定,老项目多用这个组合 |
| 5.4 ~ 5.5 | JDK 8 / JDK 11 | 两者都行,JDK 11更推荐 |
| 5.6及以后 | JDK 17 | 官方推荐,性能和兼容性最好 |
我个人的建议是:如果你是新起项目,直接上JDK 17 + JMeter 5.6.3这个组合,省心。如果你所在公司现有系统都是JDK 8,那装JDK 8 + JMeter 5.4.1也没问题,两个JDK其实可以在同一台机器上共存,用环境变量切换就行。
2.2 JDK安装与环境变量配置
到Oracle官网或者采用OpenJDK(如Adoptium Temurin)下载对应版本的JDK安装包。安装路径我建议别放带中文和空格的目录,比如D:\dev\jdk-17,不然偶尔会遇到路径解析的诡异问题。
安装完之后,关键是配置环境变量。Windows下:
- 新建系统变量
JAVA_HOME,值为JDK安装目录,比如D:\dev\jdk-17。 - 编辑
Path变量,新增一条%JAVA_HOME%\bin。 - 打开cmd,执行
java -version,能打印出版本号就说明配置成功。
配置好之后,命令行输出大概是这样:
java version "17.0.9" 2023-10-17 LTS Java(TM) SE Runtime Environment (build 17.0.9+11-LTS-237) Java HotSpot(TM) 64-Bit Server VM (build 17.0.9+11-LTS-237, mixed mode, sharing)如果这一步报'java' 不是内部或外部命令,百分之九十是Path没配好,检查一下%JAVA_HOME%\bin有没有加进去。这里有个小技巧:Path里的顺序会影响生效的JDK。如果你机器上装了多个JDK,把想要的那个bin放前面。
2.3 JMeter下载与目录结构说明
JDK搞定之后,去Apache JMeter官网下载页,下载apache-jmeter-5.6.3.zip这个压缩包。注意别下成源码包(带-src后缀的),源码包解开是没法直接跑的。
解压到任意目录,比如D:\dev\apache-jmeter-5.6.3。解开之后的目录结构大致是这样:
| 目录/文件 | 作用 |
|---|---|
bin/ | 启动脚本目录,jmeter.bat(Windows)、jmeter.sh(Linux)都在这 |
lib/ | 核心依赖jar包,插件下载的jar也放这 |
lib/ext/ | 扩展jar和插件放这里 |
docs/ | 官方文档 |
extras/ | 一些辅助工具,比如ant任务 |
bin/jmeter.properties | 全局配置文件,后面调优会改它 |
同样,建议配置环境变量JMETER_HOME指向安装目录,然后把%JMETER_HOME%\bin加到Path里。这样在任何目录下敲jmeter就能启动,省得每次进到bin目录。
2.4 Linux上的安装差异要注意什么
在Linux服务器上装JMeter,步骤大同小异,但有几个点要留意。第一,用tar -zxvf解压,不是unzip。第二,启动脚本是jmeter.sh,需要给它可执行权限:chmod +x jmeter.sh。第三,服务器上通常不装图形界面,所以Linux下我们主要跑的是非GUI模式,这恰恰是压测最推荐的方式。
# 解压 tar -zxvf apache-jmeter-5.6.3.tgz -C /opt/ # 赋权 chmod +x /opt/apache-jmeter-5.6.3/bin/jmeter.sh # 启动GUI(有桌面的情况) /opt/apache-jmeter-5.6.3/bin/jmeter.sh我在生产压测机上部署的时候,通常会单独建一个jmeter用户,因为压测产生的临时文件和日志量不小,用root跑容易把文件权限搞乱。
3. 中文界面、内存与代理配置的实战调整
3.1 把界面改成中文的正确姿势
JMeter默认是英文界面。改成中文有两条路,我推荐第二种。
第一条路是临时改:启动后点Options->Choose Language->Chinese (Simplified)。但这种方式下次启动又变回英文了。
第二条路是永久改:打开bin/jmeter.properties,找到language=这一行(默认是注释掉的),改成:
language=zh_CN保存之后重启JMeter,界面就是中文的了。这里顺便说一嘴,虽然界面能改成中文,但我建议关键术语还是记英文,因为很多教程、报错信息、插件文档都是英文的,你搜问题的时候搜中文术语搜不到东西。比如"聚合报告"其实是"Aggregate Report","线程组"是"Thread Group"。
3.2 内存调优:别让JMeter自己先崩了
这是新手最容易忽略的一环。JMeter默认的堆内存比较小(早期版本512MB),压测并发一上去,JMeter自己就OOM了,报错往往是java.lang.OutOfMemoryError: Java heap space。
调整方式有两种。一种是改bin/jmeter.bat(Windows)或jmeter.sh(Linux)里的JVM参数,找到HEAP相关的那行:
# Windows 的 jmeter.bat set HEAP=-Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m # Linux 的 jmeter.sh HEAP="-Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m"-Xms是初始堆大小,-Xmx是最大堆大小。我一般把这两个设成一样的值,避免运行中频繁扩容影响性能。设多大合适?看压测机配置和并发量。我的一般经验:
| 压测机内存 | 建议堆大小 | 可承载的大致并发 |
|---|---|---|
| 4G | -Xmx2g | 500以内 |
| 8G | -Xmx4g | 1000左右 |
| 16G | -Xmx8g | 2000以上 |
注意:JMeter的并发能力还和脚本复杂度、监听器数量强相关。脚本里挂了一堆监听器,内存翻倍都不够用。所以调内存的同时,也要精简监听器。
另一种方式是直接编辑bin/jmeter.properties,不过新版JMeter更推荐改启动脚本里的HEAP参数,因为properties里改堆大小的方式在某些版本上不生效,容易踩坑。
3.3 代理配置与HTTPS录制证书
很多朋友问怎么用JMeter录制HTTPS脚本。核心原理是:JMeter作为一个代理服务器,浏览器把请求发到JMeter,JMeter转发出去。但HTTPS有证书校验,所以要装JMeter的根证书。
操作步骤:
- 在测试计划里添加
HTTP(S) Test Script Recorder(HTTP(S)测试脚本录制器)。 - 设置监听端口,默认8080。
- 点击
Start,JMeter会在bin目录下生成一个ApacheJMeterTemporaryRootCA.crt证书。 - 把这个证书导入操作系统或浏览器的受信任根证书。
- 浏览器设置代理指向
localhost:8080,然后正常操作,JMeter就录下来了。
这块新手最容易卡在证书导入上,导入的时候一定要选择"受信任的根证书颁发机构",导入到个人那一栏是不生效的。另外录完之后记得把浏览器代理关掉,不然上不了网。
3.4 插件管理与MQTT等扩展安装
JMeter本身不含插件管理器,需要单独下载jmeter-plugins-manager的jar包,放到lib/ext目录下,重启JMeter后在Options菜单里就能看到Plugins Manager。
装插件我强烈推荐走插件管理器,别手动往lib/ext里丢jar,版本冲突很难查。比如你要做物联网接口的MQTT压测,就在插件管理器里搜MQTT,装上就行。想扩展图表报告,就装jpgc系列插件。我的原则是:能用插件管理器解决的就别手动拷贝jar。
4. 从零搭建第一个HTTP压测脚本
4.1 测试计划的整体结构逻辑
打开JMeter,左边默认就有一个"测试计划"(Test Plan)。理解它的层级结构特别重要,我用一个生活类比:测试计划就像一本书,线程组是章节,取样器是段落里的句子,监听器是给这些句子做的批注。
层级关系是:
- 测试计划(Test Plan)
- 线程组(Thread Group):定义多少个用户、怎么并发
- 配置元件(Config Element):参数化、默认值等
- 取样器(Sampler):真正的请求
- 断言(Assertion):判断响应是否符合预期
- 前置/后置处理器(Pre/Post Processor):请求前后做处理
- 监听器(Listener):收集结果
- 线程组(Thread Group):定义多少个用户、怎么并发
理解了这个层级,写脚本就不会乱。我见过有同事把所有元件都堆在线程组下面平铺,结果作用域搞不清,参数化经常不生效,就是因为没理解层级。
4.2 线程组参数怎么设才合理
右键测试计划 -> 添加 -> 线程(用户)-> 线程组。里面有几个关键参数:
- 线程数(Number of Threads):就是并发用户数,模拟多少个虚拟用户。
- Ramp-Up时间(秒):多少秒内把这些线程全部启动完。
- 循环次数(Loop Count):每个线程跑多少次。
这里有个关键的理解:Ramp-Up不是"随便填的"。假设线程数100,Ramp-Up设10,意思是10秒内均匀启动100个线程,也就是每秒启动10个。如果你把Ramp-Up设成1,那就是1秒内100个线程全部启动,瞬间冲击,容易把服务打挂,也是不真实的用户行为。
我的经验是Ramp-Up时间设为线程数的1~2倍比较自然,比如100并发,Ramp-Up设100到200秒。除非你要测的是"秒杀瞬间洪峰"这种极端场景,才刻意把Ramp-Up压小。
另外,"调度器"勾上之后可以设定持续时长,比如"持续压测300秒",就比用循环次数来控制更直观。
4.3 HTTP请求取样器的填写要点
右键线程组 -> 添加 -> 取样器 -> HTTP请求。核心字段:
| 字段 | 填写说明 |
|---|---|
| 协议 | http 或 https |
| 服务器名称或IP | 接口域名,比如 api.example.com |
| 端口号 | 默认80,https一般443,不填也行 |
| 方法 | GET / POST / PUT / DELETE等 |
| 路径 | 接口路径,比如 /api/v1/order/query |
| 参数/消息体数据 | GET用参数表,POST JSON用"消息体数据" |
POST提交JSON的时候,一定要在HTTP信息头管理器里加Content-Type: application/json,否则后端解析不到,会返回400。这个细节坑过很多人。
{ "userId": 10001, "orderNo": "SO202401150001" }加信息头的方式:右键HTTP请求 -> 添加 -> 配置元件 -> HTTP信息头管理器,然后添加一行,名称填Content-Type,值填application/json。
4.4 第一次运行和结果查看
配置好之后,先加一个监听器看结果。右键线程组 -> 添加 -> 监听器 -> 查看结果树。然后点绿色的启动按钮跑起来。
第一次跑,我建议线程数设1,循环1次,先确认请求通了、返回正常。确认没问题之后再加大并发。查看结果树里能看到请求的请求头、响应数据、状态码,红色表示失败,绿色表示成功。
新手常见的情况是:跑了之后全红。这时候先看响应数据里的报错信息,多半是地址填错、参数缺了、或者需要登录token。定位问题就是看"请求"标签里实际发出去的是什么,和你在Postman里调通的请求对比一下,差异就是问题所在。
5. 参数化、断言与关联:让脚本接近真实业务
5.1 CSV参数化:让每个用户用不同数据
如果所有线程都用同一个用户ID去请求,那压测结果是不真实的,缓存一命中,数据全是假的。参数化就是让不同线程用不同数据。
最常用的是CSV Data Set Config。准备一个csv文件:
userId,token 10001,token_aaa 10002,token_bbb 10003,token_ccc然后右键线程组 -> 添加 -> 配置元件 -> CSV Data Set Config,配置:
- 文件名:csv的绝对路径
- 文件编码:UTF-8
- 变量名称:
userId,token - 分隔符:逗号
- 遇到文件结束符再次循环:True
- 遇到文件结束符停止线程:False
然后在HTTP请求里用${userId}、${token}引用。这里有个细节:变量名要和csv表头顺序一致,不是按名字匹配,是按顺序匹配的。所以变量名称填的顺序必须和csv列的顺序对上。
提示:csv文件建议用UTF-8无BOM编码,用Excel另存为csv的时候容易带上BOM,导致第一列变量名带个隐藏字符,解析出问题。
5.2 用户定义变量与跨请求关联
除了csv,还有两种参数化方式。一种是用户定义的变量,适合放全局常量,比如域名、公共token。它的作用域是整个测试计划,配一次处处可用。
另一种是关联,也就是从上一个请求的响应里提取值,传给下一个请求。这是接口测试里最常见的需求,比如先登录拿到token,再带着token去查询。
提取方式有几种:
- 正则表达式提取器:适合响应是文本的场景。
- JSON提取器:响应是JSON时,用它比正则直观得多。
举个JSON提取器的例子。登录接口返回:
{ "code": 0, "data": { "token": "eyJhbGciOiJI..." } }在登录请求下加JSON提取器,配置:
- 变量名:
loginToken - JSON Path表达式:
$.data.token - 默认值:
NotFound
然后在后续请求里用${loginToken}引用。JSON Path的语法和大多数JSON解析库一致,$是根,.data.token是路径,数组用[0]这种下标。
5.3 断言:判断请求到底是成功还是失败
默认情况下,JMeter只看HTTP状态码,200就算成功。但业务上200不代表业务成功,可能返回了{"code": 500, "msg": "系统异常"}。所以必须加断言。
最常用的是响应断言。右键请求 -> 添加 -> 断言 -> 响应断言:
- 测试字段:响应文本
- 匹配规则:包含 / 相等 / 匹配(正则)等
- 测试模式:填你期望出现的字符串,比如
"code":0
如果想判断得更灵活,可以用JSON断言,直接断言某个JSON Path的值。也可以用BeanShell断言或JSR223断言写脚本判断。BeanShell断言适合复杂逻辑,比如需要判断多个字段、做计算的情况:
import org.json.JSONObject; String response = prev.getResponseDataAsString(); JSONObject json = new JSONObject(response); int code = json.getInt("code"); if (code != 0) { Failure = true; FailureMessage = "业务返回码异常: " + code; }这里prev是JMeter内置对象,代表上一个取样器的结果,Failure和FailureMessage是BeanShell断言的固定变量,设置它们就能标记断言失败。
注意:BeanShell性能较差,高并发下会拖慢压测。新版本更推荐用
JSR223断言+ Groovy,Groovy性能比BeanShell好很多。如果你的断言逻辑简单,还是优先用响应断言这种内置的。
5.4 文件上传场景的脚本写法
有些接口需要上传文件。JMeter在HTTP请求里勾选对POST使用multipart/form-data,然后在"文件上传"标签页里填写:
| 参数名 | 说明 |
|---|---|
| 文件名称 | 上传文件的绝对路径 |
| 参数名称 | 后端接收文件的字段名,比如 file |
| MIME类型 | 比如 image/png、application/octet-stream |
我上传测试时习惯准备一个小文件(比如几KB的图片),别用几十MB的大文件压测,否则瓶颈会在网络传输上,测不准服务端。想测大文件场景另说。
6. 非GUI压测与结果报告解读
6.1 为什么正式压测一定用命令行
前面反复提过:GUI模式只适合调试脚本,正式压测必须用命令行(非GUI)模式。原因很直接——GUI本身要渲染图表、维护界面状态,非常消耗资源。同一台机器,GUI模式可能压到300并发就卡了,命令行模式能轻松上1000。
命令行模式的基本命令:
jmeter -n -t test_plan.jmx -l result.jtl -e -o ./report参数说明:
| 参数 | 含义 |
|---|---|
| -n | 非GUI模式运行 |
| -t | 指定脚本文件(.jmx) |
| -l | 指定结果文件(.jtl),会覆盖同名文件 |
| -e | 压测结束后生成HTML报告 |
| -o | 指定HTML报告输出目录,目录必须为空或不存在的 |
| -J | 设置JMeter属性,比如 -Jthreads=100 |
这里有几个坑要提醒。第一,-l指定的jtl文件如果已存在,会直接覆盖,所以每次压测换个文件名或者先删旧的。第二,-e -o一起用时,报告目录必须不存在或为空,否则会报错退出。第三,如果脚本里配置了结果输出路径,命令行参数会覆盖它。
6.2 HTML报告的打开与关键指标
压测跑完后,report目录下会生成index.html,双击用浏览器打开,报告相当完整。重点看几个指标:
- 响应时间(Response Times):关注平均、90%、95%、99%分位。平均值会被极端值拉偏,看95%和99%分位更有意义。
- 吞吐量(Throughput):单位是每秒完成的请求数,近似我们的QPS。
- 错误率(Error %):超过1%就得警惕了。
- 活跃线程数曲线:能看到压力加载的过程。
我判断一个接口能不能扛住的标准一般是:99%分位响应时间在可接受范围内(比如500ms以内),错误率低于0.1%,吞吐量达到目标QPS。三个指标是联动的,不能只看一个。
6.3 结果文件的后续加工
jtl结果文件本质是个csv或者xml,可以用Excel打开,也可以用脚本分析。我经常做的是把jtl导入到数据库,用SQL做更细的漏斗分析,比如按时间段看响应时间变化,找出性能抖动的时刻。
如果要精简jtl大小,可以在jmeter.properties里配置只记录需要的字段:
jmeter.save.saveservice.output_format=csv jmeter.save.saveservice.response_data=false jmeter.save.saveservice.samplerData=false把响应数据关掉能大幅减小文件,压测时性能也更好。需要排查具体请求的时候再临时打开。
6.4 分布式压测的初步思路
单台压测机的能力是有天花板的,想压几千上万并发,就要用分布式。JMeter支持一台master控制多台slave。
思路是:slave机上启动jmeter-server,master机在jmeter.properties里配置remote_hosts列出所有slave的IP,然后命令行用-R指定slave运行。脚本和数据文件要在所有slave上保持一致。
jmeter -n -t test_plan.jmx -R 192.168.1.101,192.168.1.102 -l result.jtl分布式也有坑:slave的时间要同步、网络要互通、脚本里的文件路径要一致。我一般先用1个slave跑通,再加更多。
7. 那些年踩过的坑:常见报错与排查思路
7.1 java.io.IOException: error writing to server
这个报错在热搜词里也出现了,非常典型。意思是在往服务端写数据的时候出错了。常见原因有这么几个:
- 连接被服务端提前关闭:可能是服务端处理超时、连接池耗尽,主动断开了连接。
- 上传的数据太大:发送大文件或者超长请求体时,超过服务端允许的限制。
- 网络不稳定:压测机到目标服务之间网络抖动。
- JMeter自身配置问题:比如
httpclient4.retrycount设置不合理。
排查思路:先看服务端日志,确认服务端有没有报错;再看是不是压测的并发太高超过了服务端承受能力;然后检查请求体大小是否合理;最后可以适当调整JMeter的HTTP客户端参数。这个错误本质上更多是服务端或网络的问题,而不是JMeter本身的bug。
7.2 内存溢出与堆栈调整
报OutOfMemoryError的时候,先别慌。按这个顺序处理:
- 确认是不是GUI模式下压测的,是的话换成命令行。
- 检查监听器是不是挂太多,尤其是"查看结果树"这种全量记录的,压测时务必关掉。
- 加大堆内存,改HEAP参数。
- 减少不必要的断言和脚本校验。
我遇到过一次,脚本里每个请求都挂了BeanShell断言,压到200并发直接OOM,改成JSR223 Groovy之后内存曲线就平稳了。所以脚本本身的效率很关键。
7.3 响应乱码怎么处理
响应中文乱码是最常见的"看着像bug其实不是bug"的问题。JMeter默认的编码有时候不对。解决方式:
- 在
jmeter.properties里设置sampleresult.default.encoding=UTF-8。 - 或者用后置处理器
BeanShell PostProcessor设置编码:prev.setDataEncoding("UTF-8")。 - 或者确认HTTP请求里的
内容编码填了UTF-8。
改完properties记得重启JMeter生效。
7.4 参数化数据不生效的排查链路
参数化不生效,我总结的排查顺序是:
- 变量引用写对没,是不是
${var}格式,花括号别忘了。 - CSV Data Set Config的作用域对不对,是不是放在了请求的父节点上。
- csv路径对不对,路径里的反斜杠在Windows下有时要转义。
- csv编码有没有BOM。
- 变量名顺序和csv列顺序对不对得上。
- 如果引用的变量在日志里显示
${var}原样,说明变量根本没被替换,那一定是配置没生效。
排查的时候打开日志(bin/jmeter.log),里面有详细的变量解析记录,比瞎猜快多了。
7.5 我的几条压测纪律
最后分享几条我个人定下的压测纪律,供参考:
- 压测前先和运维、开发打招呼,别突然给生产环境来一波,容易出事。
- 压测环境尽量和生产配置一致,否则数据没法参考。
- 先小并发验证脚本正确性,再逐步加压,别一上来就拉满。
- 每次压测只变一个变量,否则出了问题不知道是哪个因素导致的。
- 保留每次压测的脚本和报告,做趋势对比时非常有用。
这套流程我从零起步搭起来,中间踩过的坑基本都写在上面了。JMeter这东西,看着界面复杂,实际上把"线程组-取样器-断言-监听器"这条主线理清楚,剩下的就是不断练手。建议你现在就去找个测试接口,按第四节的步骤跑一个最简单的GET请求,跑通之后再一步步加参数化、断言、关联。真正上手跑一遍,比看十篇教程都管用。