diff --git a/README.md b/README.md index 34a2d25..976def3 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,10 @@ |名称|内容| |----|----| -|压力测试|评估应用程序在峰值负载和正常情况下的行为。| -|负载测试|负载测试的目的是通过不断地、稳定地增加系统的负载来测试系统,直到达到阈值。它是性能测试的一个子集。| +|压力测试|评估应用程序在峰值负载和正常情况下的行为| +|并发测试|并发访问同一个应用、同一个模块或者数据记录时是否存在死锁或其者他性能问题| +|负载测试|负载测试的目的是通过不断地、稳定地增加系统的负载来测试系统,直到达到阈值| +|配置测试|调整软硬件的配置,了解其对系统的性能影响程度,从而找到系统最优配置| #### 按照是否执行程序分类 @@ -234,6 +236,32 @@ ### 在开发和测试存在不合作甚至对立的情况下,你如何平衡和协调工作? +先简述不合作甚至对立的几个可能原因: + +1. 出现的缺陷容易阻塞测试流程的正常进行 +2. 修复完并验证完关闭后的bug重复出现 +3. 测试人员发现同一个模块缺陷太多,抱怨开发质量太差 +4. 轻易复现的bug,开发硬是复现不了 +5. 测试时间紧迫 +6. 开发提供的文档非常粗略,无法下手测试 +7. 开发抱怨测试提过来的bug根本不是bug,而是没有认真查看相关文档 +8. 测试提的bug质量不高,没写前提,步骤不清晰,日志截图也没提供等 +9. 测试提的bug需要极端情况才出现,用户一般不会这么用 +10. 等等 + +应对方案: + +- 从项目立项开始,做好全盘计划,包括需求设计时间,开发编码时间,测试时间,运维部署等等,其中任何一个非测试节点出现延误,都不应该导致测试的时间被压缩 +- 制定完整的项目流程,从开发提测的要求,到测试提交bug要求,更加规范和约束开发与测试的行为 +- 每一个参加开发的开发人员,和参加测试的测试人员都要尽可能的参加到需求评审会,开发设计,架构设计,测试用例评审会 +- 测试人员提供冒烟测试用例给开发,开发执行完再移交给测试人员,测试人员执行冒烟不通过,打回重新开发 +- 规范开发提供的文档,提测内容包括文档 +- 规范测试提交的bug,规定的提交内容必须有,其他可提供的内容尽可能提供帮助开发更快的定位问题 +- 重复reopen的缺陷记录在案,并在项目总结会上提出:适当的施加开发压力 +- 阻塞测试流程的bug优先解决 +- 不易复现的bug,多复现几次,写明出现概率,以及提供当时的环境和日志信息等 +- 更长远的看,开发/测试应该多提高自身的工作质量,提高技术水平,沟通能力,情商等等 + ## 自动化测试问题 ### 你认为适合做自动化测试的标准是什么? @@ -332,26 +360,144 @@ UI变更频率更高,细小的改动也会导致测试脚本大范围的改动 ### 什么是性能测试?为什么要进行性能测试? +性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。通过负载测试,确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时,系统各项性能指标的变化情况。压力测试是通过确定一个系统的瓶颈或者不能接受的性能点,来获得系统能提供的最大服务级别的测试。 + ### 性能测试的类型有哪些? -### 列举下用户会面对的性能问题和性能瓶颈? +|名称|内容| +|----|----| +|压力测试|评估应用程序在峰值负载和正常情况下的行为| +|并发测试|并发访问同一个应用、同一个模块或者数据记录时是否存在死锁或其者他性能问题| +|负载测试|负载测试的目的是通过不断地、稳定地增加系统的负载来测试系统,直到达到阈值| +|配置测试|调整软硬件的配置,了解其对系统的性能影响程度,从而找到系统最优配置| + +### 列举用户会面对的性能问题和性能瓶颈 + +软件工程中,应用程序或者系统的容量通常会因为单个组件的限制,出现瓶颈,瓶颈在事物路径的所在部分中具有最低的吞吐量。系统设计人员尝试避免瓶颈,并直接努力查找和调整现有瓶颈。可能的瓶颈:处理器,通信链路,磁盘 IO 等。 + +- 硬件 + +|缺陷|描述| +|--|--| +|磁盘|磁盘空间不足,磁盘读写慢| +|CPU|占用过高,程序运行变慢| +|IO 读写速率|数据处理时的 I/O 速率,页交换等| +|内存|占用过高,程序运行变慢| + +- 网络 + +|缺陷|描述| +|--|--| +|带宽|网络资源竞争,超时| +|延时|物理层面的延时,无法避免,但可减轻| +|丢包|硬件故障,线路故障,网络拥塞| + +- 应用 + +|缺陷|描述| +|--|--| +|JVM|1.堆内存分配:一般不超过系统内存的 25%
2.垃圾回收算法:根据具体情况选择合适的垃圾回收策略
3.OOM:内存泄露,GC不完整,内存耗尽| +|代码层|不合理的线程引用和内存分配| +|JDK 版本|尽可能和生产环境版本一致| +|底层配置|OS,服务器硬件的配置不合理,带来的性能瓶颈| +|参数配置|系统架构设计,不同参数配置带来的性能瓶颈| + +- 数据库 + +|缺陷|描述| +|--|--| +|索引|合适的索引,减少磁盘 IO 与数据库执行时间| +|锁|并发场景下的表锁,行锁,页锁| +|表空间|不合理的表结构设计| +|慢 SQL|1.未命中索引
2.select *
3.like %
4.使用大事务的 SQL| +|数据量|数据量不同,性能差异大| + +- 中间件 + +|缺陷|描述| +|--|--| +|超时|设置合理的超时时间,需要配置测试来设定| +|线程池|太小,容易被使用完
太大,造成浪费,配置测试来设定| +|缓存策略|缓存失效,缓存穿透| +|最大连接数|连接数少导致队列等待、超时,多则浪费资源| +|通信方式|同步或者异步| +|负载均衡|集群,负载均衡策略选择| ### 列举下性能测试中常涉及的性能计数? +1. 资源指标 + +  - CPU使用率:指用户进程与系统进程消耗的CPU时间百分比,长时间情况下,一般可接受上限不超过85%。 +  - 内存利用率:内存利用率=(1-空闲内存/总内存大小)*100%,一般至少有10%可用内存,内存使用率可接受上限为85%。 +  - 磁盘I/O: 磁盘主要用于存取数据,因此当说到IO操作的时候,就会存在两种相对应的操作,存数据的时候对应的是写IO操作,取数据的时候对应的是是读IO操作,一般使用% Disk Time(磁盘用于读写操作所占用的时间百分比)度量磁盘读写性能。 +  - 网络带宽:一般使用计数器Bytes Total/sec来度量,Bytes Total/sec表示为发送和接收字节的速率,包括帧字符在内。判断网络连接速度是否是瓶颈,可以用该计数器的值和目前网络的带宽比较。 + +2.系统指标 + +  - 并发用户数:某一物理时刻同时向系统提交请求的用户数。 +  - 在线用户数:某段时间内访问系统的用户数,这些用户并不一定同时向系统提交请求。 +  - 平均响应时间:系统处理事务的响应时间的平均值。事务的响应时间是从客户端提交访问请求到客户端接收到服务器响应所消耗的时间 +  - 90%响应时间:所有样本的响应时间按照从小到大排列,取全部的第90%位的数值作为响应时间,该值在Jmeter也是一种标准统计指标(This is a standard statistical measure)。如果面试问到使用平均还是90%,尽可能答`90%`,或者把两者都说出来并带出两者时间的差异 +  - 事务成功率:性能测试中,定义事务用于度量一个或者多个业务流程的性能指标,如用户登录、保存订单、提交订单操作均可定义为事务 +  - 超时错误率:主要指事务由于超时或系统内部其它错误导致失败占总事务的比率 +  - 吞吐率:样本数量除以计数时间;三种计数方式:页面/s、请求数/s、Bytes/s,不同场景采取方式不同,或者同时使用多个 + ### 性能测试中并发用户点击量是什么?如何实现? +并发用户点击量:通过工具模拟用户的操作来实现多个用户同时使用系统的场景。 + +通过Jmeter添加多线程,然后线程数设置>1,再添加该任务对应的请求、断言等,启动即可。 + ### 性能测试进入和结束的标准是什么? +性能测试与其他测试活动一样,都是需要需求的提出与相应的评审活动的。 + +需求阶段 + +- 提出需求:产品人员,开发人员,测试人员,运维人员都可以提出 +- 需求评审:这一步是出具测试计划的关键 +- 需求调研:主要是对测试实施细节沟通和确认 + +在需求阶段需要评估该性能需求可行性,资源调配,细节再次确认。再三确认可行后,便是进入性能测试的号角。 + +结束标志:测试场景测试完毕、发现的问题已全部修复、结果达到预期、满足上线要求,并且出具对应的测试报告。 + ### 说一下在选择性能测试工具之前需要考虑哪些东西? +1. 根据压测场景:根据压测场景是什么来选择。如果说是一次性单接口的场景就可以使用 AB。如果说是复杂事物多接口需要业务场景的话,就会选择 JMeter 这类工具可以构造丰富的场景能满足需求。 +2. 需要提供多大压力:是1000 QPS还是万级以上的。压力很大的话就要考虑压力测试工具是否支持分布式,能否快速扩展 agent。对于 JMeter 来讲就很好的支持了。 +3. 周期性需求:业务可能频繁上线,服务随时变动。可能会有一个周期性需求,按月巡检。需要一个场景文件,可以去做数据驱动,实时跟进数据改变。最后希望结果落库。 +4. 二次开发需求:JMeter 开源插件化思想,支持 Thrift,Dubbo 等多种协议。可以快速平台化。 +5. 问题支持:是否具备完善的文档支持,是否具备强大的开放社区,是否已广泛使用,工具成熟度等,有问题的时候能够快速获得答案。 + ### 在性能测试中,如何识别性能瓶颈? +[性能瓶颈与调优](images/性能瓶颈与调优.png) + ### 在对应用程序进行性能测试期间一般会执行哪些活动? +|工作内容|描述| +|--|--| +|环境准备|运维提供适当的物理服务器或者虚拟机环境| +|应用部署|开发提供无明显功能缺陷的代码分支,运维帮忙部署到对应的测试服务器| +|数据准备|铺底数据:作为基础数据,模拟真实环境数据量
测试数据:用来发起并发的数据,可以事先准备或者动态生成
参数化数据:数据多样化,覆盖尽可能多的业务场景| +|脚本开发|针对性能测试工具进行对应的脚本开发,覆盖对应的测试场景| +|压测执行|不断执行压测脚本,以及脚本调整,改进测试结果| +|服务监控|狭义:单应用的监控,接口性能和错误监控,分布式调用链接追踪,其他用户诊断(内存,线程)的监控信息
广义:APP端监控,网页端监控,容器/服务器监控,其他中间件、数据库层面的监控| +|瓶颈定位|对日志,监控数据进行分析,定位瓶颈并做出优化| +|优化验证|对优化的软件再次执行压测,验证问题是否已经得到解决或者性能得到提升,标准是需求评审阶段的业务性能指标| + ### 性能测试中吞吐量是什么? +在一次性能测试活动中网络传输的数据量的总和。吞吐量一般是不具备参考价值的,应当用吞吐率。 + +吞吐率:单位时间内传输的数据量,可以用 页面/s、请求/s、Bytes/s等来统计。一般针对不同场景下的性能测试要求来使用不同的统计方式。 + ### 解释下什么是耐力测试和尖峰测试? +耐力测试:有时也叫浸泡测试,是一种非功能性测试。耐力测试是`长时间测试具有预期负载量的系统`,以验证系统的行为是否正常。 +尖峰测试:尖峰测试用于确定系统在负载(比如用户请求数)突然变化时的系统行为。这种测试是通过突然增加或减少由用户产生的负载来观察系统的行为。测试的目标是确定性能在这样的场景下是否会受损,系统是否会失败,或者是否能够处理负载的显著变化。尖峰测试的核心是负载变化的突然性,所以也算是一种压力测试。 + ## 数据库问题 ### MySql @@ -366,34 +512,31 @@ UI变更频率更高,细小的改动也会导致测试脚本大范围的改动 ### 进程和线程是什么?它们有什么区别和联系? +|差别|进程|线程| +|--|--|--| +|定义|程序在某数据集合上一次运行活动|进程中一个执行路径| +|角色|系统资源分配的单位|系统调度的单位| +|资源|不同进程资源不能共享|共享同一个进程地址空间和其他资源
线程有自己的栈和栈指针,程序计数器等寄存器| +|独立性|独立的地址空间|必须依赖进程而存在| + ## 算法问题 ## Java面试题 -### 基础知识 - -### 面向对象编程有哪些特性? - -1. 抽象 -2. 继承 继承是使用已存在的类作为基础--建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率 - - - 子类拥有父类非private的属性和方法。 - - 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 - - 子类可以用自己的方式实现父类的方法 - - 学习继承一定少不了这三个东西:构造器、protected关键字、向上转型 +### 面向对象三特征 -3. 封装, 把数据和逻辑封装在类里,通过创建对象去访问这个类里的方法和属性,封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,作用:可以更改好的保护类的内部成员 -4. 多态性 +1. 继承:继承是使用已存在的类作为基础--建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率 +2. 封装, 把数据和逻辑封装在类里,通过创建对象去访问这个类里的方法和属性,封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,作用:可以更改好的保护类的内部成员 +3. 多态性 - 依赖继承,重写方法 - 同一行为具有不同的实现,通过继承父类,在子类中进行方法的实现 - 通过创建不同子类的对象,去调用不同的方法 ### 重写和重载的区别是什么?为什么重载不能根据返回值判断? -`重写` @Override(不同类),用于子类继承父类,在子类去重写方法,对父类进行扩充或改造 -ps:父类的方法只有是public或者protected的,子类才能重写方法,其他的如父类是私有或者default,子类都是不可见的,所以不能重写方法。子类的方法名,参数,返回值都跟父类相同,重写的方法修饰符大于等于父类的方法 +`重写`:@Override(不同类),用于子类继承父类,在子类去重写方法,对父类进行扩充或改造。父类的方法只有是public或者protected的,子类才能重写方法,其他的如父类是私有或者default,子类都是不可见的,所以不能重写方法。子类的方法名,参数,返回值都跟父类相同,重写的方法修饰符大于等于父类的方法 -`重载`,同一个类中,存在相同的方法名,但是方法的参数一定不同 +`重载`:同一个类中,存在相同的方法名,但是方法的参数一定不同 ### 为什么重载不能根据返回值判断? @@ -405,31 +548,115 @@ ps:父类的方法只有是public或者protected的,子类才能重写方法 ### 抽象类和接口的异同? +构造函数:抽象类可以有;接口不能有 +方法实现:java8之前接口不可有方法实现,值可有方法签名;抽象类可以有非抽象的方法 +修饰符:接口方法默认是public;抽象类可以是public、protected、default +设计层面:抽象是对象的抽象,是一种模板设计;接口是对行为的抽象,是一种行为规范 + ### Java中的异常有哪几类?分别怎么使用? +受检异常和非受检异常。 + +受检异常:必须在代码里面主动捕获或者在方法签名里抛出 +非受检异常:runtime异常,不需要主动捕获或者抛出 + +一般来说Error不属于异常,而是属于`错误`,会导致程序异常或者停止。 + ### 常用的集合类有哪些以及各自有什么特点? +Map: key value 映射集合,key不可以重复 +List: 单元素集合,元素不可重复,且可以为null +Set: 单元素集合,元素不可重复,不可以为null +Queue:队列,先进先出 + ### ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和优缺点? +数据结构:A为数组,内存连续;L为链表,内存不连续,从它的命名很好判断 +访问效率:A比L随机访问效率高,因为L是线性数据存储 +增删效率:非首尾增删,L比A效率高,因为A会涉及到元素移位 +使用场合:读多用A,增删多用L + ### 内存溢出是怎么回事?请举几个可能出现内存溢出的场景? +[JVM内存分布](images/JVM内存分布.png) + +`内存溢出`(Out Of Memory,简称OOM)是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。 + +内存溢出场景: + +1. 堆内存溢出:启动JVM时,堆内存最大内存配置比较小,在对象初始化的时候,超过堆内存最大值(本来应该将对象放置于新生代,但是由于新生代内存不够放),导致堆内存溢出 +2. 堆内存泄露:正常情况下,对象如果没有被root路径引用,则会被GC,但是由于错误的引用而导致某些对象无法被GC,而无限期存在堆内存中 +3. 栈内存溢出:线程都有自己私有的栈,每一个方法都会在栈内创建栈帧,若是方法存在递归,则会导致栈顶部生成新的栈帧,从而可能导致栈内存溢出 +4. 直接内存溢出:非JVM内部内存,在程序内不断分配直接内存,而并未正常回收时会导致该问题 + ### ==和equals的区别? +==:基础数据类型比较数值是否相等;而引用类型比较的是两者的地址是否一样 +equals:本质上同==一致,部分引用类型重写equals方法,对应的值相等即可 + ### hashCode方法的作用是什么? +hashCode()方法的默认行为是堆上的对象产生独特值;重写该方法可以使得地址不等的两个对象也可通过eqauls()方法判断为相等。若是不重写,则同类的不同实例一定不等。 + ### HashMap实现原理,如何保证HashMap的线程安全? +[Java-HashMap工作原理](https://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/) + +如何保证HashMap线程安全: + +- 使用 java.util.Hashtable 类,此类是线程安全的。 +- 使用 java.util.concurrent.ConcurrentHashMap,此类是线程安全的。推荐! +- 使用 java.util.Collections.synchronizedMap() 方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。 +- 自己在程序的关键方法或者代码段加锁,保证安全性,当然这是严重的不推荐。 + ### Java中一个字符占多少个字节,扩展再问int, long, double占多少字节 +|类型|占用字节|占用位数| +|--|--|--| +byte|1|8 +short|2|16 +int|4|32 +long|8|64 +float|4|32 +double|8|64 +char|2|16 + +- `boolean类型`被编译为int类型,等于是说JVM里占用字节和int完全一样,int是4个字节,于是boolean也是4字节 +- `boolean数组`在Oracle的JVM中,编码为byte数组,每个boolean元素占用8位=1字节 + ### 创建一个类的实例都有哪些办法? +1. 用new 语句创建对象,这是最常用的创建对象方法 +2. 运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法 +3. 调用对象的clone()方法 +4. 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。 + ### final/finally/finalize的区别? +final: 修饰符,类上不可被集成,方法上不可以被重写,变量上不可以被重新赋值(不可以指向另外的内存地址) +finally:用在 try catch finally 异常处理上 +finalize:对象销毁的时候,调用的方法,一般来说不需要另外申明该方法 + ### String/StringBuffer/StringBuilder的区别? +String:不可变对象 +StringBuffer/StringBuilder:可变对象,前者线程安全,后者线程不安全 + ### 什么是java序列化,如何实现java序列化? -### JVM +将Java对象转变成可以传输的IO数据流。 + +|实现Serializable接口|实现Externalizable接口| +|系统自动存储必要的信息|程序员决定存储哪些信息| +|Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持|必须实现接口内的两个方法| +|性能略差|性能略好| + +虽然Externalizable接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现Serializable接口进行序列化。 + +作者:9龙 +链接:https://juejin.cn/post/6844903848167866375 + +## JVM ### JVM内存结构,为什么需要GC? diff --git "a/images/JVM\345\206\205\345\255\230\345\210\206\345\270\203.png" "b/images/JVM\345\206\205\345\255\230\345\210\206\345\270\203.png" new file mode 100644 index 0000000..79a2ee1 Binary files /dev/null and "b/images/JVM\345\206\205\345\255\230\345\210\206\345\270\203.png" differ diff --git "a/images/\346\200\247\350\203\275\347\223\266\351\242\210\344\270\216\350\260\203\344\274\230.png" "b/images/\346\200\247\350\203\275\347\223\266\351\242\210\344\270\216\350\260\203\344\274\230.png" new file mode 100644 index 0000000..912179b Binary files /dev/null and "b/images/\346\200\247\350\203\275\347\223\266\351\242\210\344\270\216\350\260\203\344\274\230.png" differ