B站视频地址
Java虚拟机规范/语言规范
虚拟机参数
官方垃圾收集器文档
虚拟机架构模型:
- 基于寄存器:指令少
- 基于栈:指令小而多
JVM架构模型:
- 基于栈设计的,原因:跨平台型的考虑,因为不同操作系统的寄存器不同
类加载器分两类
- 引导类加载器(Bootstrap Class Loader),C语言实现
- 自定义类加载器(继承自ClassLoader的加载器),Java实现
Method Area(方法区)主要存储类型信息、常量、静态变量、即时编译器编译后的代码缓存等
对象实例化方式*
new
方式Class
的newInstance()
:只能调用public的无参构造器Constructor
的newInstance(xxx)
:无权限要求,可以调用带参数构造器clone()
- 反序列化
- 第三方库
Objenesis
,使用字节码技术实现
内存布局
- 对象头(Header)
- 运行时元数据(Mark Word)
- 哈希值
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
- 类型指针:指向类元数据InstanceClass,确定该对象所属的类型
- 运行时元数据(Mark Word)
- 实例数据(Instance Data):对象真正存储的有效信息,包括程序代码定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
- 相同宽度的字段总是被分配在一起
- 父类中定义的变量会出现在子类之前
- 如果CompactFields参数为true(默认值):子类的窄变量可能插入到父类变量的空隙
- 对齐填充(Padding):不是必须的,也没特别含义,仅仅起到占位符的作用
GC Roots包括以下几类元素
- 虚拟机栈中引用的对象
- 比如: 各个线程被调用的方法中使用到的参数,局部变量等
- 本地方法栈内JNI(通常说的本地方法)引用的对象
- 方法区中类静态属性引用的对象
- 比如: Java类的引用类型静态变量
- 方法区中常量引用的对象
- 比如: 字符串常量池(String Table)里的引用
- 所有被同步锁synchronized持有的对象
- Java虚拟机内部的引用
- 基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException,OutOfMemoryError),系统类加载器
- 反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存等.
- 除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象"临时性"地加入,共同构成完整GC Roots集合.比如: 分代收集和局部回收(Partial GC).
- 如果只针对Java堆中的某一块区域进行垃圾回收(比如: 典型的只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域对象也加入GC Roots集合中去考虑,才能保证可达性分析的准确性.
小技巧: 由于Root采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个Root
虚拟机中的对象有三种状态
可触及的:从根节点开始,可以到达这个对象
可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活
不可触及的:对象的finalize()被虚拟机调用过,并且没有复活
垃圾收集器分类
- 按线程数
- 串行垃圾回收器
- 并行垃圾回收器
- 按工作模式
- 并发式垃圾回收器
- 独占式垃圾回收器
- 按碎片处理方式
- 压缩式垃圾回收器
- 再分配对象空间使用
指针碰撞
- 再分配对象空间使用
- 非压缩式垃圾回收器
- 再分配对象空间使用
空闲列表
- 再分配对象空间使用
- 压缩式垃圾回收器
- 按工作的内存区间
- 年轻代垃圾回收器
- 老年代代垃圾回收器
7款经典的垃圾收集器
线程数
- 串行回收器
- Serial
- Serial Old
- 并行回收器
- ParNew
- Parallel Scavenge
- Parallel Old
- 并发回收器
- CMS
- G1
工作内存区间
- 新生代收集器
- Serial
- ParNew
- Parallel Scavenge
- 老年代收集器
- Serial Old
- Parallel Old
- CMS
- 整堆收集器
- G1
不同收集器的组合
CMS垃圾回收器
- CMS的优点:
- 并发收集
- 低延迟
- CMS的弊端
- 会产生内存碎片,导致并发清除后,用户线程可用的空间不足.无法分配大对象的情况下,不得不提前出发Full GC
- CMS收集器对CPU资源非常敏感.并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低
- CMS收集器无法处理浮动垃圾.可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生.在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交叉运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间
G1垃圾收集器
G1垃圾收集器的特点
- 并行与并发
- 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力.此时用户线程STW
- 并发性:G1拥有与应用程序交替执行的能力
- 分代收集
- 将对空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代
- 和之前的各类回收器不同,它同时兼顾年轻代和老年代
- 空间整合
- G1将内存划分为一个个的region.内存的回收是以region作为基本单位的.Region之间是复制算法,但整体上实际可看作是标记-压缩算法.这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC
- 可预测的停顿时间模型(即:软实时soft real-time),能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
- 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
- G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region.保证G1收集器在有限的时间内可以获取尽可能最高的收集效率
- 相比于CMS,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多(内存空间大于6GB时G1性能更优)
字节码指令
- 局部变量压栈指令
xload、xload_<n>(x为i、l、f、d、a,n为0到3) - 常量入栈指令
- 指令const系列
- iconst_<i>(i从-1到5)
- lconst_<l>(l从0到1)
- fconst_<f>(f从0到2)
- dconst_<d>(d从0到1)
- aconst_null
- 指令push系列
- bipush接收8位整数作为参数
- sipush接收16位整数
- ldc系列
- ldc, 接收一个8位的参数
- ldc_w, 接收2个8位的参数
- ldc2_w, 压入的是long或double类型
- 指令const系列
- 出栈装入局部变量表指令
xstore、xstore_<n>(x为i、l、f、d、a,n为0到3);xastore(x为i、l、f、d、a、b、c、s) - 扩充局部变量表的访问索引指令
wide
在尖括号之间的字母指定了指令隐含操作数的数据类型,<n>代表非负整数,<i>代表int类型数据,<l>代表long类型,<f>代表float,<d>代表double类型
按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的生命周期包括如下7个阶段
- 加载(Loading)
- 链接(Linking)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
在Java中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载
双亲委派机制
双亲委派机制优势
- 避免类的重复加载,确保一个类的全局唯一性
- 保护程序安全,防止核心API呗随意篡改
双亲委派机制弊端
- 检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader
通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中为应用类。
按照这种模式, 应用类访问系统类自然是没问题, 但是系统类访问应用类就会出现问题。
比如 在系统类中提供了一个接口, 该接口需要在应用类中得以实现, 该接口还绑定一个工厂方法, 用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时, 就会出现该工厂方法无法创建由应用类加载器加载的应用实例问题。
由于Java虚拟机规范并没有明确要求类加载器的加载机制一定要使用双亲委派模型,这只是建议采用这种方式而已。
比如在Tomcat中,类加载器所采用的加载机制就和传统的双亲委派模型有一定区别,当缺省的类加载器接收到一个类的加载任务时,首先会由它自行加载,当加载失败时,才会将类的加载任务委派给它的超类加载器去执行,这同时也是Servlet规范推荐的一种做法。
历史上三次双亲委派机制的破坏
-
第一次破坏双亲委派机制
- 发生在双亲委派模型出现之前,由于双亲委派模型在JDK1.2之后才被引入,而类加载器的概念和抽象类ClassLoader则在Java的第一个版本中就已经存在
- 面对已经存在的用户自定义类加载器的代码,Java设计者们引入双亲委派模型时不得不做出一些妥协。
- 为了兼容这些已有代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,只能在JDK1.2之后的ClassLoader中添加一个新的protected方法findClass(),并引导用户编写类加载逻辑时尽可能去重写这个方法。
-
第二次破坏双亲委派机制
- 第二次破坏是由于这个模型自身缺陷导致的,双亲委派模型不能解决当基础类要调用回用户代码的情况。
- 一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器来完成加载。但是JNDI存在的目的就是对资源进行查找和集中管理,它需要调用由其他厂商实现并部署在应用程序的ClassPath下的JNDI服务提供者接口(Service Provider Interface, SPI)的代码,现在问题来了,启动类加载器是绝不可能认识,加载这些代码的。
- 为了解决这个困境,Java的设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过Thread类的setContextClassLoader()方法进行设置。如果创建线程时还未设置,它会从父线程中集成一个,默认应用程序类加载器。JNDI服务使用这个线程上下文类加载器就可以去加载所需要的SPI服务代码了
- 当SPI的服务提供者多于一个时,代码就只能根据具体提供者的类型来硬编码判断,为了消除这种极不优雅的实现方式,在JDK1.6时,JSK提供了ServiceLoader类,以META-INF/services中的配置信息,辅以责任链模式,这才算是给SPI的加载提供了一种相对合理的解决方案
-
第三次破坏双亲委派机制
- 第三次破坏是由于用户对程序动态性的追求而导致的。如:代码热替换(Hot Swap),模块热部署(Hot Deployment)等
车遥遥,马憧憧。
君游东山东复东,安得奋飞逐西风。
愿我如星君如月,夜夜流光相皎洁。
月暂晦,星常明。
留明待月复,三五共盈盈。
JVM调优
-
背景说明
- 生产环境中的问题
- 生产环境发生了内存溢出该如何处理?
- 生产环境应该给服务器分配多少内存合适?
- 如何对垃圾回收器的性能进行调优?
- 生产环境CPU负载飙高该如何处理?
- 生产环境应该给应用分配多少线程合适?
- 不加log,如何确定请求是否执行了某一行代码?
- 不加log,如何实时查看某个方法的入参与返回值?
- 为什么要调优?
- 防止出现OOM
- 解决OOM
- 减少Full GC出现的频率
- 不同阶段的考虑
- 上线前
- 项目运行阶段
- 线上出现OOM
- 生产环境中的问题
-
调优概述
- 监控的依据
- 运行日志
- 异常堆栈
- GC日志
- 线程快照
- 堆转储快照
- 调优的大方向
- 合理地编写代码
- 充分并合理的使用硬件资源
- 合理地进行JVM调优
- 监控的依据
-
性能调优的步骤
- (发现问题):性能监控
- GC频繁
- CPU LOAD过高
- 内存泄露
- 死锁
- 程序响应时间较长
- (排查问题):性能分析
- 打印GC日志,通过GCviewer或者http://gceasy.io来分析日志信息
- 灵活运用命令行工具:jstack、jmap、jinfo等
- dump出堆文件,使用内存分析工具分析文件
- 使用阿里Arthas,或者jconsole,JVisualVM来实时查看JVM状态
- jstack查看堆栈信息
- (解决问题):性能调优
- 适当增加内存,根据业务背景选择垃圾回收器
- 优化代码,控制内存使用
- 增加机器,分散节点压力
- 合理设置线程池线程数量
- 使用中间件提高程序效率,比如缓存,消息队列等
- 其他
- (发现问题):性能监控
-
性能评价/测试指标
- 停顿时间(或响应时间)
- 吞吐量
- 对单位时间内完成的工作量(请求)的量度
- 在GC中:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)吞吐量为1-1/(1+n)。-XX:GCTimeRatio=n
- 并发数:同一时刻,对服务器有实际交互的请求数
- 内存占用:Java堆区所占的内存大小
监控及诊断工具-命令行
-
jps(Java Process Status):查看正在运行的Java进程
- options参数(可组合)
q
只显示进程号l
显示main()方法所在类的全限定名m
显示main()方法参数v
显示虚拟机参数
- hostid参数
- 如果想要远程监控主机上的java程序,需要安装
jstatd
- 对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可
- 主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击
- 如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作时不运行
jstatd
服务器,而是在本地使用jstat
和jps
工具
- 如果想要远程监控主机上的java程序,需要安装
- options参数(可组合)
-
jstat(JVM Statistics Monitoring Tool):查看JVM统计信息
- 用于见识虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
- 在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄露问题。
-
官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
- option参数
- 类装载相关
-class
显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
- 垃圾回收相关
-gc
:显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。- 新生代相关
- S0C、S1C是幸存者区的大小(字节)
- S0U、S1U是幸存者区已使用的大小(字节)
- EC是Eden空间的大小(字节)
- EU是Eden空间已使用的大小(字节)
- 老年代相关
- OC、OU
- 方法区(元空间)相关
- MC、MU:方法区大小、方法区已使用的大小
- CCSC、CCSU:压缩类空间
- 其它
- YGC是指从应用程序启动到采样时young gc次数
- YGCT是指从应用程序启动到采样时young gc消耗的时间(秒)
- FGC、FGCT:full gc
- GCT
- 新生代相关
-gccapacity
:显示内容与-gc
基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。-gcutil
:显示内容与-gc
基本相同,但输出主要关注已使用空间占总空间的百分比-gccause
:与-gcutil
功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生原因。-gcnew
:显示新生代GC状况-gcnewcapacity
:显示内容与-gcnew
基本相同,输出主要关注使用到的最大、最小空间-gcold
:显示老年代GC状况-gcoldcapacity
:显示内容与-gcold
基本相同,输出主要关注使用到的最大、最小空间-gcpermcapacity
:显示永久代使用到的最大、最小空间
- 类装载相关
- interval参数:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
- count参数:用于指定查询的总次数
- t参数:可以输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒
- h参数:可以在周期性数据输出时,输出多少行数据后输出一个表头信息
-
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数
- 查看
jinfo -sysprops PID
可以查看由System.getProperties()获取的参数jinfo -flags PID
查看曾经赋过值的一些参数jinfo -flag 具体参数 PID
查看某个java进程的具体参数
- 修改
- 针对boolean类型:
jinfo -flag [+|-]具体参数 PID
- 针对非boolean类型:
jinfo -flag 具体参数=具体值 PID
- 针对boolean类型:
- 查看
-
jmap(JVM Memory Map):导出内存映像文件&内存使用情况
- 作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等
- 指令
jmap [option] <pid>
jmap [option] <executable core>
jmap [option] [server_id@]<remote server IP or hostname>
-
官方帮助文档:https://docs.oracle.com/en/java/javase/11/tools/jmap.html
- 基本语法
-dump
生成Java堆转储快照:dump文件。特别的,-dump:live只保存堆中的存活对象-heap
输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等-histo
输出堆中对象的统计信息,包括类、实例数量和合计容量。特别的,-histo:live只统计堆中的存活对象-permstat
以ClassLoader为统计口径输出永久代的内存状态信息,仅linux/solaris平台有效-finalizerinfo
显示在F-Queue中等待Finalize线程执行finalize方法的对象,仅linux/solaris平台有效-F
当虚拟机进程对-dump选项没有任何响应时,可使用此选项强制执行生成dump文件,仅linux/solaris平台有效
- 导出内存映射文件
- 手动的方式
jmap -dump:format=b,file=<filename.hprof> <pid>
jmap -dump:live,format=b,file=<filename.hprof> <pid>
- 自动的方式
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=<filename.hprof>
- 手动的方式
- 显示堆内存相关信息
jmap -heap pid
显示堆空间信息等jmap -histo pid
显示存活对象类型和占用空间等信息
- 其它作用
jmap -permstat pid
查看系统的ClassLoader信息jmap -finalizerinfo
查看堆积在finalizer队列中的对象
-
由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。这可能导致基于该堆快照的分析结果存在偏差。
-
与jstat不同,jstat是实时的,垃圾回收期会主动将jstat所需要的摘要数据保存至固定位置中,jstat只需要直接读取即可
-
jhat(JVM Heap Analysis Tool):用于分析jmap生成的heap dump文件,在JDK9、JDK10中已经被删除,官方建议用VisualVM代替
-
jstack(JVM Stack Trace):打印JVM中线程快照
- option参数
-F
当正常输出的请求不被响应时,强制输出线程堆栈-l
除堆栈外,显示关于锁的附加信息-m
如果调用到本地方法的话,可以显示C/C++的堆栈
- option参数
-
jcmd:多功能命令行
- 在JDK1.7以后新增的命令行工具,是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。
- 比如:导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。
- jcmd拥有jmap的大部分功能,并且Oracle的官方也推荐使用jcmd代替jmap
-
官方帮助文档:https://docs.oracle.com/en/java/javase/11/tools/jcmd.html
- 基本语法
jcmd -l
列出所有的JVM进程jcmd pid help
针对指定的进程,列出支持的所有命令jcmd pid 具体命令
- 在JDK1.7以后新增的命令行工具,是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。
-
jstatd:远程主机信息收集
- 之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了弃用远程监控,则需要配合使用jstatd工具。
- 命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机
源码:https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools
监控及诊断工具-GUI
- JDK自带的工具
- jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
- 从Java5开始,在JDK中自带的java监控和管理控制台
- 用于对JVM内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。
- 官方教程: https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html
- Visual VM:用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息
- 基本概述
- 是一个功能强大的多合一故障诊断和性能监控的可视化工具
- 集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
- 在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布
- 首页:https://visualvm.github.io/index.html
- 插件的安装
- 推荐安装:Visual GC
- 插件地址:https://visualvm.github.io/pluginscenters.html
- 连接方式
- 本地连接
- 远程连接
- 确定远程服务器的ip地址
- 添加JMX(通过JMX技术具体监控远端服务器哪个Java进程)
- 修改bin/catalina.sh文件,连接远程的tomcat
- 在…/conf中添加jmxremote.access和jmxremote.password文件
- 将服务器地址改为公司ip地址
- 设置阿里云安全策略和防火墙策略
- 启动tomcat,查看tomcat启动日志和端口监听
- JMX中输入端口号、用户名、密码登录
- 基本概述
- JMC:Java Mission Control,内置Java Flight Recorder。能够以极低的性能开销收集Java虚拟机的性能数据
- jconsole:JDK自带的可视化监控工具。查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等
- 第三方
- MAT:Memory Analyzer Tool,基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄露和减少内存消耗
- JProfiler:商业软件,需要付费。功能强大。与VisualVM类似
- Arthas:Alibaba开源的Java诊断工具。神兽开发者喜爱。
-
官网:https://arthas.aliyun.com/zh-cn
-
- Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。
- 其他工具
- Flame Graphs(火焰图)
-
来源: https://www.brendangregg.com/flamegraphs.html
-
- TProfiler
-
https://github.com/alibaba/TProfiler
-
- Btrace
- YourKit
- JProbe
- Spring Insight
- Flame Graphs(火焰图)
JVM运行时参数
- JVM参数选项类型
- 标准参数选项
- 特点:比较稳定,以-开头
java -h
可查看
-X
参数选项- 特点:非标准化参数;比较稳定;但官方说后续版本可能会变更
java -X
可查看
-XX
参数选项- 特点:非标准化;使用最多;属于实验性,不稳定
java -XX:+PrintFlagsFinal -version | grep manageable
查看被标记为manageable的参数,可修改
- 标准参数选项