一、简介

通过上一篇文章,已经了解了java中垃圾回收算法和各种垃圾收集器的特点和使用场景,其实java提供了很多参数供我们在运行时指定。下面介绍一些常用的JVM参数。

二、常见JVM参数

调整JVM、GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题。

常用JVM参数
JVM参数 含义 描述
-Xms 设置堆内存的初始大小 默认(MinHeapFreeRatio参数调整)空余堆内存小于40%时,JVM就会增大堆空间大小直到设置的堆最大空间(-Xmx)限制.  
-Xmx 设置堆内存的最大空间  默认(MaxHeapFreeRatio参数调整)空余堆内存大于70%时,JVM会减少堆直到设置的堆初始化空间( -Xms)大小限制. 
-Xmn 设置年轻代的空间大小 年轻代,包括Eden区域、S0区域和S1区域的空间大小之和,增大年轻代内存后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-XX:NewSize 设置年轻代的空间大小  
-XX:MaxNewSize 设置年轻代的最大空间大小  
-XX:PermSize 设置永生代的初始化空间大小  
-XX:MaxPermSize 设置永生代的最大空间大小   
-Xss  设置每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,该参数值根据应用的线程所需内存大小进行调整.在物理内存固定的情况下,-Xss的值越小,生成的线程数量越多。但是一个进程中的线程数量是有上限的,一般在3000-5000左右。一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。(-Xss参数与-XX:ThreadStackSize作用类似)
-XX:NewRatio 老年代空间大小与年轻代空间大小的比例 例如设置 -XX:NewRatio=2表示老年代与年轻代空间比例为4:1,所以年轻代占整个堆栈的1/5,但是Xms=Xmx并且设置了Xmn(每个线程的堆栈代销)的情况下,该参数不需要进行设置。
-XX:SurvivorRatio 新生代中Eden区域空间大小与两个Survivot区域空间大小的比例 SurvivorRadio = Eden区域大小/From区域大小 = Eden区域大小/To区域大小,   例如设置-XX:SurvivorRadio为8,则两个Survivor区域与Eden区域的比值为2:8,一个Survivor区域占整个年轻代大小的1/10
-XX:+DisableExplicitGC 关闭System.gc() 不建议设置该参数
-XX:MaxTenuringThreshold 设置新生代中的对象经过多少代之后被放入老年代对象内存空间中 如果设置-XX:MaxTenuringThreshold为0的话,Eden区域新创建的对象不会经过复制到Survivor区域直接进入老年代中,默认经过15代之后新生代中的对象会被复制到老年代中。
-Xnoclassgc 禁用垃圾回收 不建议设置该参数
-XX:PretenureSizeThreshold 设置某个对象超过多少字节数,则直接在老年代中分配内存  一般情况下,大数组对象而且数组中无外部引用对象时直接在老年代汇总分配内存,注意: 新生代采用Parallel Scavenge 垃圾回收器时设置该参数无效。
-XX:+CollectGen0First 设置发生Full GC之前是否先进行一次Young Generation GC 默认值是发生Full GC时不进行YGC|
  • 注意:JDK1.8取消了永久代(PermGen),取而代之的是Metaspace,所以PermSize和MaxPermSize参数失效,取而代之的是-XX:MetaspaceSize -XX:MaxMetaspaceSize。
 -XX:MetaspaceSize=64m      -XX:MaxMetaspaceSize=128m

下面是一些常用的垃圾回收器设置参数:

JVM参数 含义 描述
-XX:+UseParallelGC  设置GC时使用Parallel垃圾回收器    
-XX:+UseParNewGC 设置GC时使用ParNew 垃圾回收器,一般使用在新生代手机 多线程串行
-XX:ParallelGCThreads 设置并行收集器的线程数 一般情况下设置为与CPU数量相同,默认情况下也是等于CPU数量
-XX:+UseParallelOldGC 设置使用Parallel Old垃圾回收器|一般适用于在老年代中,使用标记-整理算法实现  
-XX:MaxGCPauseMillis 设置年轻代垃圾回收时的最大暂停时间  
-XX:+UseAdaptiveSizePolicy 并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等  
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比  
-XX:+ScavengeBeforeFullGC 设置进行Full GC前是否调用Young Generation GC(YGC) 默认值是true
-XX:+UseConcMarkSweepGC 设置使用CMS垃圾收集器进行垃圾回收  
-XX:CMSFullGCsBeforeCompaction 设置CMS垃圾收集器进行多少次GC以后对内存空间进行压缩,整理  
-XX+UseCMSCompactAtFullCollection CMS收集器在FULL GC的时候, 对年老代的压缩|由于CMS采用标记-清除算法,会产生大量内存碎片,通过这个参数可以整理内存碎片  

下面是一些设置GC日志等参数:

JVM参数 含义 描述
-XX:+PrintGC  打印GC日志信息  日志信息不够详细,格式如:[GC 110000K->100000K(150000K), 0.0094143 secs] 
-XX:+PrintGCDetails 打印GC日志详细信息  输出的日志信息比PrintGC更加详细,包括使用的垃圾收集器信息、堆内存使用情况、耗费时间等信息,格式大概如:[GC [DefNew: 1024K->0K(2048K), 0.0135 secs] 111100K->100000K(150000K), 0.0124633 secs]   
-XX:+PrintGCTimeStamps 显示垃圾回收的时间  
-XX:+PrintGCApplicationStoppedTime 显示垃圾回收过程中应用程序暂停的时间  
-XX:+PrintHeapAtGC |打印GC前后的详细堆栈信息  
-Xloggc:filename 把相关日志信息记录到文件以便分析.与上面几个配合使用  

下面是一个gc日志示例:

[GC (System.gc()) [PSYoungGen: 2477K->680K(15360K)] 2477K->688K(51200K), 0.0029335 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 680K->0K(15360K)] [ParOldGen: 8K->607K(35840K)] 688K->607K(51200K), [Metaspace: 3235K->3235K(1056768K)], 0.0113457 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
HeapPSYoungGen      total 15360K, used 355K [0x00000000eee00000, 0x00000000eff00000, 0x0000000100000000)eden space 13312K, 2% used [0x00000000eee00000,0x00000000eee58d78,0x00000000efb00000)from space 2048K, 0% used [0x00000000efb00000,0x00000000efb00000,0x00000000efd00000)to   space 2048K, 0% used [0x00000000efd00000,0x00000000efd00000,0x00000000eff00000)ParOldGen       total 35840K, used 607K [0x00000000cca00000, 0x00000000ced00000, 0x00000000eee00000)object space 35840K, 1% used [0x00000000cca00000,0x00000000cca97ea0,0x00000000ced00000)Metaspace       used 3252K, capacity 4496K, committed 4864K, reserved 1056768Kclass space    used 355K, capacity 388K, committed 512K, reserved 1048576K

三、JVM参数小结

参数分类:

【a】-XX开头:系统级别的参数,如:-XX:+PrintGCDetails

【b】非-XX开头:应用层面的参数。

+ 号表示启用; - 号表示禁用。

四、JVM参数设置示例

【a】模拟内存溢出,代码如下:

public class Student {private String pkid;private String name;private int age;public String getPkid() {return pkid;}public void setPkid(String pkid) {this.pkid = pkid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
/*** 模拟内存溢出 </br>* * @author: weishihuai* @date: 2019年1月24日下午1:45:43*/
public class TestOutOfMemory {public static void main(String[] args) {List<Student> studentList = new ArrayList<Student>();while (true) {// -Xmx1024m:最大堆内存空间// -Xms1024m:初始堆内存大小// -XX:+HeapDumpOnOutOfMemoryError: 当堆抛出OOM错误时,dump出当前的内存堆转储快照// -XX:HeapDumpPath=d:\oom.hprof 该参数的含义是指定dump的文件目录,d:\oom.hprof// -XX:+PrintGCDetails: 打印垃圾收集的日志详细信息studentList.add(new Student());}}}

我们配置一下运行时VM参数,如果通过命令行启动java程序的话,可以通过java -Xmx1024m -Xms1024m -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath=d:\oom.hprof TestOutOfMemory方式指定,也可以使用IDE - > run configuration 进行配置,这种方式比较方便,如下图:

这里我们指定了初始化堆空间为1G,最大堆内存为1G,并且配置了在发生内存溢出时生成堆内存转储快照,文件存储在D:\oom.hprof中。这里为了模拟发生内存溢出,写了一个无限循环,运行程序观察运行结果:

运行结构发生了内存溢出,并且在D盘生成堆内存快照信息,

oom.hprof是内存溢出的快照信息,我们通过一些工具分析内存溢出发生的原因,这里使用Eclipse Memory Analyzer(MAT)进行分析,可以在Eclipse中装插件或者直接下载客户端也行,客户端下载地址:https://www.eclipse.org/mat/downloads.php。

打开MAT,打开oom.hprof文件,这时候MAT会输出分析报告,如果中途遇到报错:

An internal error occurred during: "Parsing heap dump from XXX”

这说明需要导出的hprof文件大小比MAT中配置的内存大,需要适当调大MAT内存,一种方法是修改启动参数 MemoryAnalyzer.exe-vmargs -Xmx6g,另一种方法是编辑文件 MemoryAnalyzer.ini,在里面添加类似信息 -vmargs– Xmx6g

下面是MAT解析出来的结果:

点击Dominator Tree,可以查看到发生内存溢出的具体信息和占比等,如下图所示:

点击Leak Suspects:

在实际工作中,可以借助一些工具分析程序在运行过程的内存情况。

【b】JVM参数配置示例一

public class TestJVMParam {public static void main(String[] args) {printMemoryInfo();System.out.println("#################################");byte[] buffer = new byte[1024 * 1024];printMemoryInfo();System.out.println("#################################");byte[] buffer2 = new byte[4 * 1024 * 1024];printMemoryInfo();}private static void printMemoryInfo() {System.out.println("最大内存maxMemory-->" + Runtime.getRuntime().maxMemory());System.out.println("空闲内存freeMemory-->" + Runtime.getRuntime().freeMemory());System.out.println("总内存totalMemory-->" + Runtime.getRuntime().totalMemory());}}

这是JVM默认的配置运行的结果,下面我们配置一些运行JVM参数:

-Xms5m
-Xmx20m
-XX:+PrintGCDetails
-XX:+UseSerialGC
-XX:+PrintCommandLineFlags

运行程序,结果如下:

-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
最大内存maxMemory-->20316160
空余内存freeMemory-->5285416
总内存totalMemory-->6094848
#################################
[GC (Allocation Failure) [DefNew: 790K->192K(1856K), 0.0024597 secs] 790K->526K(5952K), 0.0027767 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
最大内存maxMemory-->20316160
空闲内存freeMemory-->4473272
总内存totalMemory-->6094848
#################################
[GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0022628 secs][Tenured: 1550K->1550K(4096K), 0.0030606 secs] 1583K->1550K(5952K), [Metaspace: 2572K->2572K(1056768K)], 0.0054079 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
最大内存maxMemory-->20316160
空闲内存freeMemory-->4541600
总内存totalMemory-->10358784
Heapdef new generation   total 1920K, used 52K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)eden space 1728K,   3% used [0x00000000fec00000, 0x00000000fec0d0f0, 0x00000000fedb0000)from space 192K,   0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)to   space 192K,   0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)tenured generation   total 8196K, used 5646K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)the space 8196K,  68% used [0x00000000ff2a0000, 0x00000000ff823900, 0x00000000ff823a00, 0x00000000ffaa1000)Metaspace       used 2578K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 286K, capacity 386K, committed 512K, reserved 1048576K

我们发现,程序打印出GC垃圾回收的日志信息,以及GC垃圾回收各个区域的内存回收情况。

【c】JVM参数配置示例二

public class TestJVMParam2 {public static void main(String[] args) {//第一次配置:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC//第二次配置:-Xms20m -Xmx20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC//第三次配置:-Xms20m -Xmx20m -XX:NewRadio=2 -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGCbyte[] buffer= null;//连续向系统申请10M内存空间for (int i = 0; i < 10; i++) {buffer = new byte[1024 * 1024];}}
}
  • 【a】第一次配置:
-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

运行结果:

[GC (Allocation Failure) [DefNew: 512K->256K(768K), 0.0018686 secs] 512K->427K(20224K), 0.0019409 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation   total 768K, used 492K [0x00000000fec00000, 0x00000000fed00000, 0x00000000fed00000)eden space 512K,  46% used [0x00000000fec00000, 0x00000000fec3b070, 0x00000000fec80000)from space 256K, 100% used [0x00000000fecc0000, 0x00000000fed00000, 0x00000000fed00000)to   space 256K,   0% used [0x00000000fec80000, 0x00000000fec80000, 0x00000000fecc0000)tenured generation   total 19456K, used 10412K [0x00000000fed00000, 0x0000000100000000, 0x0000000100000000)the space 19456K,  53% used [0x00000000fed00000, 0x00000000ff72b000, 0x00000000ff72b000, 0x0000000100000000)Metaspace       used 2576K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 286K, capacity 386K, committed 512K, reserved 1048576K

由上图可见,Eden区域大小/From区域大小 = 512 / 256 = 2 (-XX:SurvivorRatio=2)

  • 【b】第二次配置:
-Xms20m -Xmx20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

运行结果:

[GC (Allocation Failure) [DefNew: 2934K->1549K(5376K), 0.0024504 secs] 2934K->1549K(18688K), 0.0025159 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 4690K->1024K(5376K), 0.0020234 secs] 4690K->1548K(18688K), 0.0020552 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 4157K->1024K(5376K), 0.0271431 secs] 4681K->1548K(18688K), 0.0271812 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
Heapdef new generation   total 5376K, used 3208K [0x00000000fec00000, 0x00000000ff300000, 0x00000000ff300000)eden space 3584K,  60% used [0x00000000fec00000, 0x00000000fee222c8, 0x00000000fef80000)from space 1792K,  57% used [0x00000000ff140000, 0x00000000ff240010, 0x00000000ff300000)to   space 1792K,   0% used [0x00000000fef80000, 0x00000000fef80000, 0x00000000ff140000)tenured generation   total 13312K, used 524K [0x00000000ff300000, 0x0000000100000000, 0x0000000100000000)the space 13312K,   3% used [0x00000000ff300000, 0x00000000ff383128, 0x00000000ff383200, 0x0000000100000000)Metaspace       used 2576K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 286K, capacity 386K, committed 512K, reserved 1048576K

由上图可见,Eden区域大小/From区域大小 = 3584 / 1792 = 2 (-XX:SurvivorRatio=2),并且新生代大小为(3584 + 1792 + 1792) = 7M

  • 【c】第三次配置:
-Xms20m -Xmx20m -XX:NewRatio=1 -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

运行结果:

[GC (Allocation Failure) [DefNew: 5039K->1549K(7680K), 0.0028197 secs] 5039K->1549K(17920K), 0.0028959 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 5744K->1024K(7680K), 0.0024885 secs] 5744K->1548K(17920K), 0.0025315 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation   total 7680K, used 3215K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)eden space 5120K,  42% used [0x00000000fec00000, 0x00000000fee23c20, 0x00000000ff100000)from space 2560K,  40% used [0x00000000ff100000, 0x00000000ff200010, 0x00000000ff380000)to   space 2560K,   0% used [0x00000000ff380000, 0x00000000ff380000, 0x00000000ff600000)tenured generation   total 10240K, used 524K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)the space 10240K,   5% used [0x00000000ff600000, 0x00000000ff683128, 0x00000000ff683200, 0x0000000100000000)Metaspace       used 2576K, capacity 4486K, committed 4864K, reserved 1056768Kclass space    used 286K, capacity 386K, committed 512K, reserved 1048576K

由上图可见,Eden区域大小/From区域大小 = 5120 / 2560= 2 (-XX:SurvivorRatio=2),并且新生代大小/老年代大小 = (5120 + 2560 + 2560) / 10240 = 1(-XX:NewRatio=1).默认情况下,老年代大小是新生代大小的两倍,即-XX:NewRatio=2,如果项目中新对象比较多,可以适当增加新生代的大小,如果项目中对象的存活时间都比较长,那么可以适当增加老年代的大小。

五、总结

本文总结了一些常见的JVM参数设置,在实际工作中发生内存溢出等问题时,可以根据具体需求配置相应的参数,没有配置参数的公式,只能配置最合适项目要求的JVM参数,通过三个小示例演示了如何配置JVM参数并通过Eclipse Memory Analyzer分析内存情况,更多JVM参数设置可以在这里https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html查看。本文是笔者在学习JVM时的一些总结和见解,仅供大家学习参考,希望能对大家有所帮助。

JVM初识之常用参数配置相关推荐

  1. JVM调优常用参数配置

    2019独角兽企业重金招聘Python工程师标准>>> 堆配置 -Xms:初始堆大小 -Xms:最大堆大小 -XX:NewSize=n:设置年轻代大小 -XX:NewRatio=n: ...

  2. JVM调优和参数配置

    1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots    答:什么是垃圾:简单的说就是内存中已经不再被使用到的空间就是垃圾        要进行垃圾回收,如何判断一个对象是否可以被回 ...

  3. [Hive_add_8] Hive 常用参数配置

    0. 说明 记录 Hive 常用参数的配置 1. 设置本地模式 让 Hive 自动使用 Hadoop 的本地模式运行作业,提升处理性能 适合小文件,一般用于测试 set hive.exec.mode. ...

  4. JVM基础(6)-常用参数总结

    参考文章: 并发编程网:http://ifeve.com/useful-jvm-flags-part-4-heap-tuning/ 一.参数分类 HotSpot JVM 提供了三类参数. 第一类包括了 ...

  5. HIVE常用参数配置

    HIVE参数配置 --查看参数使用:set  或者set -v; --设置参数使用:set  key=value; --设置reduce个数(默认-1) mapreduce.job.reduces=- ...

  6. CMS常用参数配置指令

    参数配置 说明 -XX:+UseConcMarkSweepGC 启用CMS -XX:ConcGCThreads 配置并发的GC线程数 -XX:+UseCMSCompactAtFullCollectio ...

  7. soot基础 -- 常用参数配置

    在soot的学习过程中会遇到大量的和配置相关的一些内容,这些内容设置的不正确会很让人感到苦恼,为此做出以下总结. 使用soot进行编程的流程 基本的流程图如下: [类加载]在类加载阶段,可以设置加载哪 ...

  8. hive常用参数配置设置

    hive.exec.mode.local.auto  决定 Hive 是否应该自动地根据输入文件大小,在本地运行(在GateWay运行)  true hive.exec.mode.local.auto ...

  9. JVM调优之参数配置: -Xms -Xmx -Xmn -XX:+PrintGCDetails -XX:UseSerialGC -XX:SurvivorRadio -XX:NewRadio

    JVM提供了诸多的参数进行JVM各个方面内存大小的设置,为Java应用进行优化提供了诸多的工具,本文将会详细分析各个参数的功能与使用. 1.常见参数说明: -Xms: //堆内存初始化大小 -Xmx: ...

  10. ansible playbook play常用参数配置

    我们知道ansible playbook可由多个play组成,而每个play又可以由多个task组成,如果不熟悉playbook play概念的可参考ansible playbook基本概念 下面是一 ...

最新文章

  1. c语言加密算法头文件下载(base64、md5、sha1)
  2. 【PS】WBS结算到建工程问题
  3. Java第八天听课总结--jar 包的使用(1)
  4. SVM_GUI_3.1[mcode]{by faruto}的安装及使用
  5. Codeforces Round #547 (Div. 3) D
  6. 南通大学计算机专业分数线2020,2020南通大学录取分数线_历年各专业分数线(2017-2019)_各省投档线_一品高考网...
  7. 个人笔记 Vue.js, Framework7, and Cordova / PhoneGap Template with Babel, Webpack and Hot Reloading...
  8. 网上书店模板asp与html,一个简单的网上书城的例子(三)_asp实例
  9. 使用ReportNG更好看的TestNG HTML测试报告– Maven指南
  10. 删除wallet里面登机牌_登机牌丢失问题
  11. matlab中函数的公式计算,MATLAB怎样定义函数(入门) 有一函数 f(x,y)=x^2+sinxy+2y , 写一程序, 输入自变量的值,输出函数值....
  12. 百度分享自定义内容和图片
  13. community 计算模块度_聚苯乙烯泡沫模块可以用在哪些建筑上?
  14. Machine Learning - X. Advice for Applying Machine Learning机器学习算法的诊断和改进 (Week 6)
  15. CodeForces 632C The Smallest String Concatenation(水)
  16. python条件、循环、终止
  17. CARNIVAL包的介绍(根据生信技能树Jimmy老师分享的R包资料整理)
  18. php mov格式转换,mov格式怎么转换成mp4 如何将mov转换成mp4
  19. 基于微信小程序的单词记忆系统(Java+SSM+MySQL)
  20. 用户行为分析系统架构

热门文章

  1. Pyspark:随机森林
  2. JAVA常见的异常6_Java常见异常总结
  3. 295.数据流的中位数
  4. JavaWeb程序的目录结构(2)
  5. 你的设备中缺少重要的安全和质量修复_2020华富管道非开挖修复工程施工欢迎前来咨询...
  6. 张雨石:关于深度学习中的dropout的两种理解
  7. 跟阿铭学linux书摘
  8. sas导入txt出现中文乱码解决方案
  9. 一次性掌握计算机中常见的六类指令
  10. 【2017-2018 ACM-ICPC, Central Europe Regional Contest (CERC 17)】Justified Jungle【树上思维题】