10个经典又容易被人疏忽的JVM面试题

前言

整理了10个经典又容易被疏忽的JVM面试题,谢谢阅读,大家加油哈.

1. 对象一定分配在堆中吗?有没有了解逃逸分析技术?

「对象一定分配在堆中吗?」 不一定的,JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。

  • 「什么是逃逸分析?」

逃逸分析(Escape Analysis),是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。

逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸。

  • 「一个逃逸分析的例子」

/***  @author 捡田螺的小男孩*/
public class EscapeAnalysisTest {public static Object object;//StringBuilder可能被其他方法改变,逃逸到了方法外部。public StringBuilder  escape(String a, String b) {//公众号:捡田螺的小男孩StringBuilder str = new StringBuilder();str.append(a);str.append(b);return str;}//不直接返回StringBuffer,不发生逃逸public String notEscape(String a, String b) {StringBuilder str = new StringBuilder();str.append(a);str.append(b);return str.toString();}//外部线程可见object,发生逃逸public void objectEscape(){object = new Object();}//仅方法内部可见,不发生逃逸public void objectNotEscape(){Object object = new Object();}
}

「逃逸分析的好处」

  • 栈上分配,可以降低垃圾收集器运行的频率。

  • 同步消除,如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。

  • 标量替换,把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,一、减少内存使用,因为不用生成对象头。二、程序内存回收效率高,并且GC频率也会减少。

2.虚拟机为什么使用元空间替换了永久代?

「什么是元空间?什么是永久代?为什么用元空间代替永久代?」 我们先回顾一下「方法区」吧,看看虚拟机运行时数据内存图,如下:

方法区和堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

「什么是永久代?它和方法区有什么关系呢?」

如果在HotSpot虚拟机上开发、部署,很多程序员都把方法区称作永久代。可以说方法区是规范,永久代是Hotspot针对该规范进行的实现。在Java7及以前的版本,方法区都是永久代实现的。

「什么是元空间?它和方法区有什么关系呢?」

对于Java8,HotSpots取消了永久代,取而代之的是元空间(Metaspace)。换句话说,就是方法区还是在的,只是实现变了,从永久代变为元空间了。

「为什么使用元空间替换了永久代?」

  • 永久代的方法区,和堆使用的物理内存是连续的。

「永久代」是通过以下这两个参数配置大小的~

  • -XX:PremSize:设置永久代的初始大小

  • -XX:MaxPermSize: 设置永久代的最大值,默认是64M

对于「永久代」,如果动态生成很多class的话,就很可能出现「java.lang.OutOfMemoryError: PermGen space错误」,因为永久代空间配置有限嘛。最典型的场景是,在web开发比较多jsp页面的时候。

  • JDK8之后,方法区存在于元空间(Metaspace)。物理内存不再与堆连续,而是直接存在于本地内存中,理论上机器「内存有多大,元空间就有多大」

可以通过以下的参数来设置元空间的大小:

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

「所以,为什么使用元空间替换永久代?」

表面上看是为了避免OOM异常。因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限,但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误。当使用元空间时,可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制啦。

3.什么是Stop The World ?  什么是OopMap?什么是安全点?

进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为「Stop The World」

在HotSpot中,有个数据结构(映射表)称为「OopMap」。一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap。在即时编译过程中,也会在「特定的位置」生成 OopMap,记录下栈上和寄存器里哪些位置是引用。

这些特定的位置主要在:

  • 1.循环的末尾(非 counted 循环)

  • 2.方法临返回前 / 调用方法的call指令后

  • 3.可能抛异常的位置

这些位置就叫作「安全点(safepoint)。」 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停。

4.说一下JVM 的主要组成部分及其作用?

JVM包含两个子系统和两个组件,分别为

  • Class loader(类装载子系统)

  • Execution engine(执行引擎子系统);

  • Runtime data area(运行时数据区组件)

  • Native Interface(本地接口组件)。

  • 「Class loader(类装载):」 根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区的方法区中。

  • 「Execution engine(执行引擎)」:执行class的指令。

  • 「Native Interface(本地接口):」 与native lib交互,是其它编程语言交互的接口。

  • 「Runtime data area(运行时数据区域)」:即我们常说的JVM的内存。

首先通过编译器把 Java源代码转换成字节码,Class loader(类装载)再把字节码加载到内存中,将其放在运行时数据区的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

5. 守护线程是什么?守护线程和非守护线程的区别是?守护线程的作用是?

「守护线程」是区别于用户线程哈,「用户线程」即我们手动创建的线程,而守护线程是程序运行的时候在后台提供一种「通用服务的线程」。垃圾回收线程就是典型的守护线程。

「守护线程和非守护线程的区别是?」 我们通过例子来看吧~

    /*** 关注公众号:捡田螺的小男孩*/public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()-> {while (true) {try {Thread.sleep(1000);System.out.println("我是子线程(用户线程.I am running");} catch (Exception e) {}}});//标记为守护线程t1.setDaemon(true);//启动线程t1.start();Thread.sleep(3000);System.out.println("主线程执行完毕...");}

运行结果:

可以发现标记为守护线程后,「主线程销毁停止,守护线程一起销毁」。我们再看下,去掉 t1.setDaemon(true)守护标记的效果:

    public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()-> {while (true) {try {Thread.sleep(1000);System.out.println("我是子线程(用户线程.I am running");} catch (Exception e) {}}});//启动线程t1.start();Thread.sleep(3000);System.out.println("主线程执行完毕...");}

所以,当主线程退出时,JVM 也跟着退出运行,守护线程同时也会被回收,即使是死循环。如果是用户线程,它会一直停在死循环跑。这就是「守护线程和非守护线程的区别」啦。

守护线程拥有「自动结束自己生命周期的特性」,非守护线程却没有。如果垃圾回收线程是非守护线程,当JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬。这就是「为什么垃圾回收线程需要是守护线程啦」

6.WeakHashMap了解过嘛?它是怎么工作的?

「WeakHashMap」 类似HashMap ,不同点在WeakHashMap的key是「弱引用」的key。

谈到「弱引用」,在这里回顾下四种引用吧

  • 强引用:Object obj=new Object()这种,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

  • 软引用: 一般情况不会回收,如果内存不够要溢出时才会进行回收

  • 弱引用:当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

  • 虚引用:为一个对象设置虚引用的唯一目的只是为了能在这个对象被回收时收到一个系统的通知。

正是因为WeakHashMap使用的是弱引用,「它的对象可能随时被回收」。WeakHashMap 类的行为部分「取决于垃圾回收器的动作」,调用两次size()方法返回不同值,调用两次isEmpty(),一次返回true,一次返回false都是「可能的」

WeakHashMap「工作原理」回答这两点:

  1. WeakHashMap具有弱引用的特点:随时被回收对象。

  2. 发生GC时,WeakHashMap是如何将Entry移除的呢?

WeakHashMap内部的Entry继承了WeakReference,即弱引用,所以就具有了弱引用的特点,「随时可能被回收」。看下源码哈:

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {V value;final int hash;Entry<K,V> next;/*** Creates new entry.*/Entry(Object key, V value,ReferenceQueue<Object> queue,int hash, Entry<K,V> next) {super(key, queue);this.value = value;this.hash  = hash;this.next  = next;}......

「WeakHashMap是如何将Entry移除的?」  GC每次清理掉一个对象之后,引用对象会放到ReferenceQueue的,接着呢遍历queue进行删除。WeakHashMap的增删改查操作,就是直接/间接调用expungeStaleEntries()方法,达到及时清除过期entry的目的。可以看下expungeStaleEntries源码哈:

  /*** Expunges stale entries from the table.*/private void expungeStaleEntries() {for (Object x; (x = queue.poll()) != null; ) {synchronized (queue) {@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) x;int i = indexFor(e.hash, table.length);Entry<K,V> prev = table[i];Entry<K,V> p = prev;while (p != null) {Entry<K,V> next = p.next;if (p == e) {if (prev == e)table[i] = next;elseprev.next = next;// Must not null out e.next;// stale entries may be in use by a HashIteratore.value = null; // Help GCsize--;break;}prev = p;p = next;}}}}

7. 是否了解Java语法糖嘛?说下12种Java中常用的语法糖?

语法糖(Syntactic Sugar),也称糖衣语法,让程序更加简洁,有更高的可读性。Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等12种。

  • 语法糖一、switch 支持 String 与枚举

  • 语法糖二、 泛型

  • 语法糖三、 自动装箱与拆箱

  • 语法糖四 、 方法变长参数

  • 语法糖五 、 枚举

  • 语法糖六 、 内部类

  • 语法糖七 、条件编译

  • 语法糖八 、 断言

  • 语法糖九 、 数值字面量

  • 语法糖十 、 for-each

  • 语法糖十一 、 try-with-resource

  • 语法糖十二、Lambda表达式

感兴趣的朋友,可以看下这篇文章哈:不了解这12个语法糖,别说你会Java!

8. 什么是指针碰撞?什么是空闲列表?什么是TLAB?

一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是“「指针碰撞」”。

如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是“「空闲列表」

对象创建在虚拟机中是非常频繁的行为,可能存在线性安全问题。如果一个线程正在给A对象分配内存,指针还没有来的及修改,同时另一个为B对象分配内存的线程,仍引用这之前的指针指向,这就出「问题」了。

可以把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,这就是「TLAB(Thread Local Allocation Buffer,本地线程分配缓存)」 。虚拟机通过-XX:UseTLAB设定它的。

9.CMS垃圾回收器的工作过程,CMS收集器和G1收集器的区别。

CMS(Concurrent Mark Sweep) 收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:「初始标记,并发标记,重新标记,并发清除」,收集结束会产生大量空间碎片。如图(下图来源互联网):

「CMS收集器和G1收集器的区别:」

  • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;

  • G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;

  • CMS收集器以最小的停顿时间为目标的收集器;

  • G1收集器可预测垃圾回收的停顿时间

  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片

  • G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

10.JVM 调优

JVM调优其实就是通过调节JVM参数,即对垃圾收集器和内存分配的调优,以达到更高的吞吐和性能。JVM调优主要调节以下参数

「堆栈内存相关」

  • -Xms 设置初始堆的大小

  • -Xmx 设置最大堆的大小

  • -Xmn 设置年轻代大小,相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值

  • -Xss  每个线程的堆栈大小

  • -XX:NewSize 设置年轻代大小(for 1.3/1.4)

  • -XX:MaxNewSize 年轻代最大值(for 1.3/1.4)

  • -XX:NewRatio 年轻代与年老代的比值(除去持久代)

  • -XX:SurvivorRatio Eden区与Survivor区的的比值

  • -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代。

  • -XX:MaxTenuringThreshold设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代

「垃圾收集器相关」

  • -XX:+UseParallelGC:选择垃圾收集器为并行收集器。

  • -XX:ParallelGCThreads=20:配置并行收集器的线程数

  • -XX:+UseConcMarkSweepGC:设置年老代为并发收集。

  • -XX:CMSFullGCsBeforeCompaction=5 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行5次GC以后对内存空间进行压缩、整理。

  • -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

「辅助信息相关」

  • -XX:+PrintGCDetails 打印GC详细信息

  • -XX:+HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,排查问题用

  • -XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题.

  • -XX:+PrintTLAB 查看TLAB空间的使用情况

参考与感谢

  • [JVM的逃逸分析] (https://segmentfault.com/a/1190000023475016)

  • [面试官 | JVM 为什么使用元空间替换了永久代?] (https://my.oschina.net/u/3471412/blog/4426430)

  • [Metaspace 之一:Metaspace整体介绍(永久代被替换原因、元空间特点、元空间内存查看分析方法)] (https://www.cnblogs.com/duanxz/p/3520829.html)

  • [深入理解WeakHashmap] (https://blog.51cto.com/mikewang/880775)

  • [一文搞懂WeakHashMap工作原理] (https://baijiahao.baidu.com/s?id=1666368292461068600&wfr=spider&for=pc)

  • [谈谈什么是守护线程及作用] (https://www.cnblogs.com/quanxiaoha/p/10731361.html)

  • [浅析java中的TLAB] (https://www.jianshu.com/p/8be816cbb5ed)

  • 《深入理解Java虚拟机》


往期推荐

1.3w字,一文详解死锁!


聊聊Spring事务失效的12种场景,太坑了


不重启JVM,替换掉已经加载的类,偷天换日?



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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

duration java_Java Duration类| ofDays()方法与示例

duration java持续时间Class ofDays()方法 (Duration Class ofDays() method) ofDays() method is available in java.time package. ofDays()方法在java.time包中可用。 ofDays() method is used to represent the given number of days in this Duration. ofDays()方法用于表…

面试官:怎么解决MySQL中的死锁问题?

咱们使用 MySQL 大概率上都会遇到死锁问题&#xff0c;这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍&#xff0c;对常见的死锁案例进行相关分析与探讨&#xff0c;以及如何去尽可能避免死锁给出一些建议。话不多说&#xff0c;开整&#xff01;什么是死锁死锁是并…

java的equals方法_Java Duration类| 带示例的equals()方法

java的equals方法持续时间类equals()方法 (Duration Class equals() method) equals() method is available in java.time package. equals()方法在java.time包中可用。 equals() method is used to identifies whether this Duration and the given object are equal or not. …

ubuntu双系统导致进windows花屏

2019独角兽企业重金招聘Python工程师标准>>> 5600U的集成显卡&#xff0c;装了ubuntu的双系统&#xff0c;居然导致进win7的时候花屏&#xff0c;度娘狗哥都不得求解 网上很多解决方法都说在启动时加上nomodeset&#xff0c;发现对ubuntu15没用&#xff0c;且失去了…

stl resize函数_vector :: resize()函数以及C ++ STL中的示例

stl resize函数C vector :: resize()函数 (C vector::resize() function) vector::resize() is a library function of "vector" header, it is used to resize the vector, it accepts the updated number of elements and a default value (optional) and resizes…

工作总结:日志打印的15个建议

前言 日志是快速定位问题的好帮手&#xff0c;是撕逼和甩锅的利器&#xff01;打印好日志非常重要。今天我们来聊聊日志打印的15个好建议~1. 选择恰当的日志级别 常见的日志级别有5种&#xff0c;分别是error、warn、info、debug、trace。日常开发中&#xff0c;我们需要选择恰…

MFC属性页对话框

属性页对话框 分类 分页和引导 类 CPropertyPage-父亲CDialog类别&#xff0c;所谓的属性页或网页对话框。 CPropertySheet-父类是CWnd&#xff0c;称为属性表单。 一个完整的属性页对话框由一个属性表单多个属性页组成。属性页嵌套在属性表单内。 标签式属性页的创建步骤&…

vector cbegin_vector :: cbegin()函数以及C ++ STL中的示例

vector cbeginC vector :: cbegin()函数 (C vector::cbegin() function) vector::cbegin() is a library function of "vector" header, it is used to get the const iterator pointing to the first element of the vector. vector :: cbegin()是“ vector”头文件…

面试官:ConcurrentHashMap为什么放弃了分段锁?

今天我们来讨论一下一个比较经典的面试题就是 ConcurrentHashMap 为什么放弃使用了分段锁&#xff0c;这个面试题阿粉相信很多人肯定觉得有点头疼&#xff0c;因为很少有人在开发中去研究这块的内容&#xff0c;今天阿粉就来给大家讲一下这个 ConcurrentHashMap 为什么在 JDK8 …

C语言函数指针的应用——自制谐波分析软件

文章目录函数指针简介格式介绍颜色头文件计算机仿真使用说明完整代码部分效果图函数指针简介 如果在一个大型C语言程序中要反复调用函数&#xff0c;而调用的函数又不明确时&#xff0c;函数指针就是一个非常有用的东西。如果你的函数体内可以传递不同的函数&#xff0c;那就非…

PHP5.5四种序列化性能对比

2019独角兽企业重金招聘Python工程师标准>>> 结论&#xff1a; 1、小数组用msgpack,无论空间和性能都最好 2、大数组&#xff0c;考虑空间用igbinary,考虑性能用msgpack json_encode&#xff0c;serialize&#xff0c;igbinary&#xff0c;msgpack四种序列化方式&am…

多线程循环输出abcc++_C ++循环| 查找输出程序| 套装2

多线程循环输出abccProgram 1: 程序1&#xff1a; #include<iostream>using namespace std;int main(){ for(;;){cout<<"Hello ";}return 0;}Output: 输出&#xff1a; Hello Hello .... Infinite loopExplanation: 说明&#xff1a; In the above co…

MyBatis Plus 批量数据插入功能,yyds!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone最近 Review 小伙伴代码的时候&#xff0c;发现了一个小小的问题&#xff0c;小伙伴竟然在 for 循环中进行了 insert &#xff08;插入&a…

C语言打印彩色字符——以(枚举法+字符串查找)为例展示

文章目录C语言颜色头文件——自制非常简单的调用函数实战演练——一个基础的枚举变量小程序牛刀小试——查找字符小程序C语言颜色头文件——自制非常简单的调用函数 显然&#xff0c;C语言是不会提供打印彩色字符的标准函数&#xff0c;而我们有时候为了强调C语言打印的部分字…

人工智能ai 学习_学习代理| 人工智能

人工智能ai 学习Learning is an important part of human behavior. It is the first step in the development phase of any human. When the concept of Artificial Intelligence was proposed, the main approach of the developers was to build a system which could reac…

sql server中同时执行select和update语句死锁问题

原始出处 http://oecpby.blog.51cto.com/2203338/457054 最近在项目中使用SqlServer的时候发现在高并发情况下&#xff0c;频繁更新和频繁查询引发死锁。通常我们知道如果两个事务同时对一个表进行插入或修改数据&#xff0c;会发生在请求对表的X锁时&#xff0c;已经被对方持有…

再见 Spring Task,这个定时任务框架真香!

最近有朋友问到定时任务相关的问题。于是&#xff0c;我简单写了一篇文章总结一下定时任务的一些概念以及一些常见的定时任务技术选型。希望能对小伙伴们有帮助&#xff01;个人能力有限。如果文章有任何需要补充/完善/修改的地方&#xff0c;欢迎在评论区指出&#xff0c;共同…

C语言实现动画控制

文章目录原材料说明一场革命原材料 下载原材料网址: https://www.easyx.cn/downloads/ 下载easyx2014冬至版&#xff0c;将lib文件放在编译器默认的lib文件夹&#xff0c;h头文件放在编译器默认的include文件夹即可 说明 C语言可以用系统内部的定时函数sleep和usleep定时(需…

mcq 队列_MCQ | 8086微处理器中的寻址模式

mcq 队列Question 1: 问题1&#xff1a; You are given the following instruction: ADD AX , [1024] You are provided the following data: DS 3423H ; SS 1234H ; CS 4567H Find the effective address location for the given instruction. 您得到以下指示&#xff1a;…

聊聊redis分布式锁的8大坑

前言在分布式系统中&#xff0c;由于redis分布式锁相对于更简单和高效&#xff0c;成为了分布式锁的首先&#xff0c;被我们用到了很多实际业务场景当中。但不是说用了redis分布式锁&#xff0c;就可以高枕无忧了&#xff0c;如果没有用好或者用对&#xff0c;也会引来一些意想…