高级测试工程师面试题集合


1、请你说一下死锁的原因,以及如何打破,如何查看死锁进程状态
    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁的四个必要条件

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

java中产生死锁可能性的最根本原因是:
1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;
2)默认的锁申请操作是阻塞的。 如,线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,在获得了锁L1,并且没有释放锁L1的情况下,
又去申请获得锁L2,这个是产生死锁的最根本原因。

    避免死锁
? 方案一:破坏死锁的循环等待条件。
? 方法二:破坏死锁的请求与保持条件,使用lock的特性,为获取锁操作设置超时时间。这样不会死锁(至少不会无尽的死锁)
? 方法三:设置一个条件遍历与一个锁关联。该方法只用一把锁,没有chopstick类,将竞争从对筷子的争夺转换成了对状态的判断。
仅当左右邻座都没有进餐时才可以进餐。提升了并发度。

死锁如何查看死锁进程
linux中查看死锁进程状态 使用pstack 和 gdb 工具对死锁程序进行分析 pstack 进程号 查看各个线程的堆栈信息 当进程吊死的时候,多次使用,
死锁的线程将一直处于等锁的状态,确定某些线程一直没有变化,一直处于等锁的状态。那么这些线程很可能是死锁了。如果怀疑哪些线程发生死锁了,
可以采用gdb 进一步attach线程并进行分析。 执行命令gdb attach 进程号,进入gdb调试终端 运行:(gdb) info thread

2、手写代码:统计排序数组中出现次数最多的元素出现的次数?

from collections import Counter
nums = [1, 2, 2, 2]
counts = Counter(nums)
print(counts)
# 输出
Counter({2: 3, 1: 1})
# 代码
print(max(counts.keys(), key=counts.get))
# 输出
2
# 代码
print(counts.most_common(1))
# 输出
[(2, 3)]

3、请问J2EE上request请求先经过谁 然后再转交到 SpringMVC上

答:DispatcherServlet

4、查看端口号、进程的指令是?动态查看日志的指令?怎么判断一个端口存不存在,磁盘满了怎么处理,删除一个目录下的txt文件,你还熟悉其他什么linux指令?

答:Netstat

ps –ef

tail –f

netstat –an | grep 端口号

不删除目录,删除目录下所有文件:

rm -f 目录/*

find 目录 -name '*.txt' -type f -print -exec rm -rf {} \;

find 目录-type f | xargs rm -f

5、请你说一下垃圾回收机制

垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。 引用:如果Reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。

(1)强引用(Strong Reference):如“Object obj = new Object()”,这类引用是Java程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。 
 (2)软引用(Soft Reference):它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2之后提供了SoftReference类来实现软引用。 
 (3)弱引用(Weak Reference):它也是用来描述非须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。 
 (4)虚引用(Phantom Reference):最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。

6、请问多线程是什么?

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。

7、请你说一说有哪几种垃圾回收算法

Tracing算法(Tracing Collector) 标记—清除算法:分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。

 Compacting算法(Compacting Collector)标记—整理算法:标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表。

Copying算法(Copying Collector):将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次清理掉。 Adaptive算法(Adaptive Collector):监控当前堆的使用情况,并将选择适当算法的垃圾收集器。

8、简单说明一下GC root

gc中不能被回收的对象,叫做gc root,?可以作为GC ROOT的对象包括:
1. 栈中引?的对象
2. 静态变量、常量引?的对象
3. 本地?法栈native?法引?的对象

9、请你说一下内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

以产生的方式来分类,内存泄漏可以分为四类:

常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。

偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

10、请你说一下内存泄漏的原因

当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。内存泄漏是造成应用程序OOM的主要原因之一。

 常见的内存泄漏:

1、单例造成的内存泄漏

2、非静态内部类创建静态实例造成的内存泄漏。

3、Handler造成的内存泄漏

4、线程造成的内存泄漏

5、资源未关闭造成的内存泄漏

6、使用ListView时造成的内存泄漏

7、集合容器中的内存泄露

8、WebView造成的泄露

11、请问如何判断一个单向链表存在回路

首先创建两个指针p1和p2(在Java里就是两个对象引用),让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针p1每次向后移动1个节点,让指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。

public static boolean isLinkCycle(Node head){

       Node p1 = head;

       Node p2 = head;

       while(p2 != null && p2.next != null){

              p1 = p1.next;

              p2 = p2.next.next;

              if(p1 == p2){

                     return true;

              }

              return false;

12、如何排查系统的性能问题?

为了能快速的排查分析,我们建议排查顺序为:从表及里,从易到难。
按照下面这个图的话,建议由左到右,由上到下

可能会出现瓶颈的地方
硬件瓶颈:
一般指的是CPU、内存、磁盘I/O 方面的问题,分为服务器硬件瓶颈、网络瓶颈
中间件性能瓶颈:
web软件,数据库,缓存等
应用程序瓶颈:
JVM参数不合理,容器配置不合理,慢SQL,慢事务,数据库设计不合理,程序架构规划不合理,程序本身设计有问题(串行处理、请求的处理线程不够、无缓冲、无缓存、生产者和消费者不协调等),造成系统在大量用户方位时性能低下而造成的瓶颈。
操作系统瓶颈:
连接数,虚拟内存,内核参数等
网络设备瓶颈:
包括但不限于SLB/WAF/高防IP/CDN/全站加速等
?

几种常见的瓶颈
1、TPS波动较大
原因解析:出现TPS波动较大问题的原因一般有网络波动、其他服务资源竞争以及垃圾回收问题这三种。

性能测试环境一般都是在内网或者压测机和服务在同一网段,可通过监控网络的出入流量来排查;
其他服务资源竞争也可能造成这一问题,可以通过Top命令或服务梳理方式来排查在压测时是否有其他服务运行导致资源竞争;
垃圾回收问题相对来说是最常见的导致TPS波动的一种原因,可以通过GC监控命令来排查,命令如下:
1 # 实时打印到屏幕
2 jstat -gc PID 300 10
3 jstat -gcutil PID 300 10
4 # GC信息输出到文件
5 jstat -gc PID 1000 120 >>/path/gc.txt
6 jstat -gcutil PID 1000 120 >>/path/gc.txt


调优方案:

网络波动问题,可以让运维同事协助解决(比如切换网段或选择内网压测),或者等到网络较为稳定时候进行压测验证;
资源竞争问题:通过命令监控和服务梳理,找出压测时正在运行的其他服务,通过沟通协调停止该服务(或者换个没资源竞争的服务节点重新压测也可以);
垃圾回收问题:通过GC文件分析,如果发现有频繁的FGC,可以通过修改JVM的堆内存参数Xmx,然后再次压测验证(Xmx最大值不要超过服务节点内存的50%!)


2、高并发下大量报错
原因解析:出现该类问题,常见的原因有短连接导致的端口被完全占用以及线程池最大线程数配置较小及超时时间较短导致。
调优方案:

  短连接问题:修改服务节点的tcp_tw_reuse参数为1,释放TIME_WAIT scoket用于新的连接;
  线程池问题:修改服务节点中容器的server.xml文件中的配置参数,主要修改如下几个参数:
    # 最大线程数,即服务端可以同时响应处理的最大请求数
    maxThreads="200"
    # Tomcat的最大连接线程数,即超过设定的阈值,Tomcat会关闭不再需要的socket线程
    maxSpareThreads="200"
    # 所有可用线程耗尽时,可放在请求等待队列中的请求数,超过该阈值的请求将不予处理,返回Connection refused错误
    acceptCount="200"
    # 等待超时的阈值,单位为毫秒,设置为0时表示永不超时
    connectionTimeout="20000"

3、集群类系统,各服务节点负载不均衡
原因解析:出现这类问题的原因一般是SLB服务设置了会话保持,会导致请求只分发到其中一个节点。
调优方案:如果确认是如上原因,可通过修改SLB服务(F5/HA/Nginx)的会话保持参数为None,然后再次压测验证;

4、并发数不断增加,TPS上不去,CPU使用率较低
原因解析:出现该类问题,常见的原因有:SQL没有创建索引/SQL语句筛选条件不明确、代码中设有同步锁,高并发时出现锁等待;
调优方案:

SQL问题:没有索引就创建索引,SQL语句筛选条件不明确就优化SQL和业务逻辑;
同步锁问题:是否去掉同步锁,有时候不仅仅是技术问题,还涉及到业务逻辑的各种判断,是否去掉同步锁,建议和开发产品同事沟通确认;
5、压测过程中TPS不断下降,CPU使用率不断降低
原因解析:一般来说,出现这种问题的原因是因为线程block导致,当然不排除其他可能;
调优方案:如果是线程阻塞问题,修改线程策略,然后重新验证即可;

6、其他
除了上述的五种常见性能瓶颈,还有其他,比如:connection reset、服务重启、timeout等,当然,分析定位后,你会发现,我们常见的性能瓶颈,

导致其的原因大多都是因为参数配置、服务策略、阻塞及各种锁导致。。。