1. 自我介绍,介绍自己在学校的主要经历以及自己做过的项目中给自己提升最大的项目,并谈谈从这个项目中得到的启示
2. 说一说你使用的是什么样的线程池,自定义线程池的几个参数是什么
使用自定义线程池,即使用ThreadPoolExecutor
创建的线程池,因为这样创建的线程池可以自定义阻塞队列的参数,如果使用Executors
创建线程,则创建出来的线程池很容易发生OOM。
参数有七个,分别为:线程池核心线程、线程池最大线程、线程池超时时间、线程池超时时间单位、线程池阻塞队列、线程工厂、线程池拒绝策略
3. 说一说线程池的执行流程(什么时候添加线程、什么时候添加队列、什么时候执行拒绝策略)
如下图所示
4. 你的项目中定义的核心线程数、最大线程数和队列大小是如何定义的?为什么这样定义?
核心线程数=
最大线程数=物理CPU核心数=逻辑线程的一半
队列大小=评估进程执行的平均时间,根据总数、任务书、执行进程数来确定
通过多次 unit test 确定你要运行的某一个监听进程的执行周期 life cycle 的大体时间,然后通过压测 或者 客户要求的 QoS 计算一个应用程序的目标执行 TPCC 要求值 有了目标性能值 有了每个单元的平均执行周期 就可以预测 总线城池大小和并行线程池的数量
2020-09-02更新
参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:
• tasks,每秒需要处理的最大任务数量
• tasktime,处理第个任务所需要的时间
• responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。
参数设置:
1)corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即taskstasktime个线程数。假设系统每秒任务数为1001000,每个任务耗时0.1秒,则需要1000.1至10000.1,即10100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。
2)maxPoolSize:
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)(20/200),即60个线程,可将maxPoolSize设置为60。
3)queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)responsetime: (20/0.1)2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。
以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,应根据实际情况进行调整
3. 如何防止超卖现象?
- 使用mysql的事务+排它锁
- 使用redis维护库存(但是无法保证redis和数据库的数据一致性)
- 使用redis维护下订单的整个流程
- 使用mysql的无锁操作,即下面一道题
4. 你的下订单的sql语句如何编写的?(主要是where后面要加一个库存>0)
1 | update t_item set stock = stock -1 where stock > 0 |
5. 如果有用户反馈说整个系统的反馈比较慢,你是如何排查和如何解决问题的?
- 首先自己主动访问几次,看是否会出现用户所描述的情况。如果没出现了,可以多换几次环境,如果仍然没问题,那就是用户的问题。如果出现问题,那就是服务端的问题。
- 在访问比较慢的环境下,打开浏览器的调试功能,查看访问比较慢的网络请求,如果是图片等静态资源加载比较慢,可以考虑将静态资源放到CDN上,如果是动态数据加载比较慢,那就是服务器后端的问题。
- 登录到远程服务器,通过
top
命令查看电脑的内存、带宽等的消耗情况,查看是不是有其他的进程占用了较大的内存或CPU,如果不是其他程序或内存占用不高的话,那就是程序内部的问题。 - 检查程序日志,比如mysql日志,检查一下是不是有某些mysql的语句耗时太长,如果是的话,可以考虑sql语句优化、分库分表、读写分离等方式进行优化。
- 如果使用了缓存,检查一下缓存命中率,看是不是出现了缓存透穿或缓存雪崩
6. 影响系统响应慢的原因有哪些?
网络情况、网络带宽、服务器性能、sql语句、缓存雪崩、线程阻塞、静态资源、大文件等
7. 算法题:给定一个无序数组和一个目标值,找出数组中两个数之和等于目标值的所有组合,并指出其时间复杂度(回溯法或者使用hashmap)
参考https://leetcode-cn.com/problems/two-sum/
8. 了解过JVM内存模型么?说说哪些是线程独享、哪些是线程共享的
JVM内存模型分为:PC寄存器、Java堆、Java栈、方法区、运行时常量池、本地方法栈
线程独享:Java栈、PC寄存器、本地方法栈
线程共享:Java堆、运行时方法区、运行时常量池
9. 详细说一下Java堆的分区,他们的比例,以及如果把比例调大以后会有什么样的后果
Java堆分为新生代和老年代,新生代又分为一个Eden区和两个Suvivor区,他们的比例是8:1:1
如果把比例调为20:1:1,会出现Eden区经历简单GC后存活的对象无法在Suvivor区获得内存,导致OOM,同时Suvivor区的对象只有很少一部分能进入到老年代,导致老年代有很大一部分的空闲内存,最终结果是,虽然内存使用不多,但是仍然会抛出OOM异常,同时会执行很多次的FullGC
10. 说一下JVM的GC算法,以及为什么“标记-整理”法更好一些,他能解决什么样的问题
JVM现在主要用的是“标记-整理”算法,这种算法是将所有对象标记完成后,将对象统一向前对齐,组成连续的内存空间,然后将这段连续空间内存外的内存区域直接清空。
“标记-清除”算法会让系统产生很多的内存空间随便,最终导致虽然有内存空间,但是无法分配给一个大对象或者一个数组;
“复制”算法会让内存空间一分为二,系统在运行时候总有一半的内存空间无法使用,属于性能浪费
11. 了解过Java类加载机制么?一共有哪几种类加载器?(我回答了没有了解,所以这个后面没有后续了)
参考http://www.hollischuang.com/archives/201
Java类加载过程分为加载->链接(验证->准备->解析)->初始化
- 加载:开发人员可以通过系统提供的类加载器来完成加载,也可以自定义自己的类加载器来实现加载,加载阶段主要做了下面三个事情
- 通过一个类的全限定名 来获取其定义的二进制流(网络、磁盘或者其他来源)
- 将二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构(将类信息存储在运行时方法区)
- 在Java堆中生成一个代表这个类的
java.lang.Class
对象,作为方法区中这些数据的访问入口
- 验证:目的是为了确保Class文件字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟机自身的安全
- 文件格式验证
- 元数据验证(语义分析),可以理解为对类层面的信息验证
- 字节码验证,对方法层面的信息验证
- 符号引用验证,验证根据引用能否找到对应的类、方法、字段
- 准备:在方法区中为类的静态变量分配内存并初始化
- 如果类字段的字段属性表中存在ConstantValue属性,即静态常量(static final)修饰,那么在准备阶段变量就会被初始化为常量所指定的值
- 解析:将常量池内的符号引用替换为直接引用的过程
- 初始化:当一个Java类第一次被真正用到的时候,JVM会进行该类的初始化操作,类的初始化分为两步:如果基类没有被初始化,初始化基类;有类构造函数,则执行类构造函数
- 假如这个类还没有被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类
- 加入类中有初始化语句,则系统一次执行这些初始化语句
类加载器一共有四种,分别为:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)以及自定义加载器
双亲委派模型:
- 首先,检查一下指定名称的类是否已经贾在国,如果加载过了,就不需要再加载,直接返回
- 如果此类没有被加载过,那就判断一个是否有父加载器,如果有父加载器(即调用parent.loadClass(name,false)),或者是调用bootstrap加载器来加载
- 如果父加载器及bootstrap加载器都没有找到指定的类,那么调用当前类的findClass方法来完成类加载
- 如果还是找不到,那就直接抛出ClassNotFoundException异常
12. 如果你想查看线上正在运行的服务的GC日志,你需要输入什么指令?
jstat -gc
13. 了解过HashMap么,说一下HashMap的原理,如果两个对象hashCode相同,则一定是相同对象么,如果是两个相等的对象,那么他们的hashCode相同么;如何解决hash冲突
- hashMap创建时候的默认大小是16,扩容因子是0.75,扩容大小为size=size<<1
- hashMap计算hash值的方式是hashCode的高16位和低16位进行异或操作(
(hashCode>>>16)^hashCode
) - hashMap底层是使用数组+链表的方式实现的
- hashMap获取下标的方式是index=(leng-1)&hash;
- hashMap发生hash碰撞时候,采用链表的方式解决,如果链表大于一定值后,会自动升级会红黑树
如果两个对戏那个hashCode相同,不一定是相同对象,相同对象必须能满足使用==返回ture的条件,如果是两个相等对象,则他们的hashCode一定相同
解决hash冲突的几种办法
- 开放地址法:如果遇到了hash冲突,则寻找下一个位置填入元素
- 再hash法:如果遇到了hash冲突,则在对结果进行一次hash
- 链地址法:就是讲节点做成链表进行维护
- 建立一个公共溢出区:具体实现不太清楚
14. HashMap是线程安全的么,如何把HashMap变成线程安全的类?
HashMap不是线程安全的,如果要把HashMap做成线程安全的类,有如下几种方式
- 将所有有关该HashMap对象的操作全部用
synchroized
关键字标注 - 使用
Collections
工具类中的synchroizedMap()
方法封装 - 使用hashtable封装
- 使用concurrentHashmap封装
15. 说一说MySQL数据库有哪些数据库引擎?
一般就两种,一种是MyISAM和InnoDB
16. InnoDB的索引的数据结构是什么,说一说他的原理
InnoDB的索引是一颗B+树,B+树原理解释如下:
- 首先是一个概念,计算机应用程序在读取变量的时候,从硬盘中读取的速度要慢于从内存中读取的速度,但是不可能将所有的硬盘数据存储到内存中。所以我们需要找一种方式,让计算机应用程序仅将一部分所需要的数据加载到内存中就好了。以上便是创建索引的一个原因。
- B+树是一棵平衡的N叉树,即树的每个根节点都有N个子节点的树
- B+树的非叶子节点不保存数据,只保存索引的范围,叶子节点保存索引值和数据
- B+树的叶子节点维护了的是双向链表,该链表还与其左右叶子节点通过指针相互连接
- 在查找时候,首先根据非叶子节点找到所需数据的左或者右边界,然后通过叶子节点的左右指针找到另外一个边界,然后将此部分数据加载到内存中,以此来提高数据库的性能
17. 数据库事务等级以及他们解决了什么问题,请举例说明一下
- 读未提交:会产生”脏读”的问题
- A事务在T1的时间向银行卡号存钱,但是没有提交,后来觉得不对,又在T2时间把钱取出来了,B事务却在T2时间之前,T1时间之后读取到了A事务存钱的信息
- 读提交:解决了“脏读“的问题,但还会产生”不可重复读”的问题
- A事务在T1时间查询余额,在T2时间进行消费,B事务在T2时间之前,T1时间之后把钱全部取了出来,使得A事务即使查到了有余额但是仍然无法消费
- 可重复读取:解决了“不可重复读”的问题,但还会会产生“幻读”的问题
- A事务在T1时间查询余额,在T2时间将所有的钱都取了出来,B事务在T2时间之前,T1时间之后存入了一笔钱,使得A事务取出来的钱大于查到的钱(世上要有这种好事那该多好啊)
- 串行化:将数据库的事务全部变为串行化操作
18. MySQL默认的事务等级是什么
可重复读
19. MySQL如何开启慢查询
1 | set global slow_query_log=1 |
或在my.ini中添加代码和参数如下
1 | show_query_log = 1 |
20. 说一下Spring主要包含哪些模块?(SpringFramework里面的主要模块)
spring-core
:Spring的基础API模块,如资源管理、泛型处理spring-beans
:SpringBean相关,如依赖查找、依赖注入spring-aop
:Spring AOP 处理,如动态代理,AOP字节码提升spring-context
:Spring上下文模块,如事件驱动、注解驱动、模块驱动spring-expression
:Spring表达式语言模块
21. 说一下你对Spring的IOC容器的理解以及启动流程
22. 有了解过RPC框架或分布式的东西么(我回答只了解过SpringCloud,然后就没有问)
23. 你在学校的成绩排名是怎样的?
24. 你在学校的动手能力你觉得你排名多少?
25. 大概说一下你写的博客
26. 说一下你读过的有关Java的书籍
2020/03/06的阿里一面,一个多小时的电话面试终于结束了,上面就是整个面试过程中面试官问我的问题,可以看到,阿里还是比较注重基础的,尤其是Java基础和数据库基础。很多东西我也是最近两天才突击复习了解的,所以实战经验几乎为0,导致很多东西说的上原理但是没有用过(比如查看GC日志的指令就一次都没有用过)。上述的问题,个人感觉回答上来80%就差不多能过阿里的一面了。不过还是,如果想去大厂的话,一定要早准备,并且一定要理论结合实际,因为面试官都是根据你前面的回答来提问你的,形式并不固定,如果只是知道原理,不知道实战如何使用,那可能会不尽如人意。
最后补充一点,第24问面试官问我对RPC框架或者分布式的东西了解么,这个问题主要是考察了你个人对于互联网发展的一个关注程度,RPC和分布式都是现在互联网行业比较热门和前沿的东西,作为一个Java开发工程师,一定要对它有一定的了解,知道自己以后的大概方向是什么,对于应届生来说,了解过这个东西就足矣,不会让你深究其原理,当然,熟知原理并经常使用肯定会加分的。