常规⼋股
美团 Java ⾯试
最近有⼀些之 前在美团实习同学,跟我反馈在美团转正成功了,跟去年类似,只要在美团实习 60
天左右,就能拿到转正机会,⽽且转正⼤概率还是⽐较⼤的。
去年的美团普通档的校招薪资都有 35w 年薪,所以能转正成功,还是⾮常不错,⽽且秋招也相⽐
实习的时候会更卷⼀点,因为秋招是⼀个⼤规模的求职招聘,竞争的⼈也相对更多⼀些。
当然,如果运⽓不好,没有转正成功的同学,也不 必灰⼼,你已经有⼀段⼤⼚的实习经历了,在
秋招也是会有很⼤优势的。
这次,来跟⼤家分享⼀位同学的美团后端开发⾯经,同学反馈是他参加秋招⼀来压⼒最⼤的⼀场
⾯试,这个压⼒⼤倒不是说问题特别多和难,主要是问法都⽐较场景化。
⼀上来主要问了⼏个常规的⼋股,后⾯主要是问了⼀些⾮「常规」的⼋股⽂。
主要是⾃⼰准备的不够充分,⾯对⼀些⽐较场景化的⾯试题就⽐较慌张了,最后还是讲出了⾃⼰
的⼀些想法,⾯完 20 分钟之后,收到电话通知幸运的通关了⼀⾯。

常规⼋股
HTTP 常⻅状态码有哪些?

HTTP 状态码分为 5 ⼤类
1xx 类状态码属于提⽰信息,是协议处理中的⼀种中间状态,实际⽤到的⽐较少。
2xx 类状态码表⽰服务器成功处理了客⼾端的请求,也是我们最愿意 看到的状态。
3xx 类状态码表⽰客⼾端请求的资源发⽣了变动,需要客⼾端⽤新的 URL 重新发送请求获取资
源,也就是重定向。
4xx 类状态码表⽰客⼾端发送的报⽂有误,服务器⽆法处理,也就是错误码的含义。
5xx 类状态码表⽰客⼾端请求报⽂正确,但是服务器处理时内部发⽣了错误,属于服务器端的错
误码。
其中常⻅的具体状态码有:
200 :请求成功;
301 :永久重定向;302 :临时重定向;
404 :⽆法找到此⻚⾯;405 :请求的⽅法类型不⽀持;
500 :服务器内部出错。
MySQL 事务特性是什么 ?怎么实现的?
原⼦性(Atomicity ):⼀个事 务中的所有操作,要么全部完成,要么全部不完成,不会结束在
中间某个环节,⽽且事 务在执⾏过程中发⽣错误,会被回滚到事务开始前的状态,就像这个事
务从来没有执⾏过⼀样,就好⽐买⼀件商品,购买成功时,则给商家付了 钱,商品到⼿;购买
失败时,则商品在商家⼿中,消费者的钱也没花出去。
⼀致性(Consistency ):是指事务操作前和操作后,数据满⾜完整性约束,数据库保持⼀致性
状态。⽐如,⽤⼾ A 和⽤⼾ B 在银⾏分别 有 800 元和 600 元,总共 1400 元,⽤⼾ A 给⽤⼾ B
转账 200 元,分为两个 步骤,从 A 的账⼾扣除 200 元和对 B 的账⼾增加 200 元。⼀致性就是
要求上述步骤操作后,最后的结果是⽤⼾ A 还有 600 元,⽤⼾ B 有 800 元,总共 1400 元,⽽
不会出现⽤⼾ A 扣除了 200 元,但⽤⼾ B 未增加的情况(该情况,⽤⼾ A 和 B 均为 600 元,
总共 1200 元)。
隔离性(Isolation ):数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可
以防⽌多个事 务并发执⾏时由于交 叉执⾏⽽导致数据的不⼀致,因为多个事 务同时使⽤相同的
数据时,不会相互⼲扰,每个事 务都有⼀个完整的数据空间,对其他并发事务是隔离的。也就
是说,消费者购买商品这个事 务,是不影响其他消费者购买的。
持久性(Durability ):事务处理结束后,对数据的修改就是永久的,即便系统故障也不 会丢
失。
MySQL InnoDB 引擎通过什么 技术来 保证事务的这四个特性的呢?
持久性是通过 redo log (重做⽇志)来保证的;
原⼦性是通过 undo log (回滚⽇志) 来保证的;
隔离性是通过 MVCC (多版本并发控制) 或锁机制来保证的;
⼀致性则是通过持久性+原⼦性+隔离性来保证;
Java 线程池的核⼼参数有哪些?
线程池是为了 减少频繁的创建线程和销毁线程带来的性能损耗,线程池的⼯作原理如下图:

线程池分为核⼼线程池,线程池的最⼤容量,还有等待任务的队列,提交⼀个任务,如果核⼼线
程没有满,就创建⼀个线程,如果满了,就是会加⼊等待队列,如果等待队列满了,就会增加线
程,如果达到最⼤线程数量,如果都达到最⼤线程数量,就会按照⼀些丢 弃的策略进⾏处理。
线程池的构造函数有7个参数:

corePoolSize :线程池核⼼线程数量。默认情况下,线程池中线程的数量如果 <= corePoolSize ,那么即使这些线程处于空闲状态,那也不 会被销毁。maximumPoolSize :限制了线程池能创建的最⼤线程总数(包括核⼼线程和⾮核⼼线程),当
corePoolSize 已满 并且 尝试将新任务加 ⼊阻塞队列失败(即队列已满)并且 当前线程数 <maximumPoolSize ,就会创建新线程执⾏此任务,但是当 corePoolSize 满 并且 队列满 并且
线程数已达 maximumPoolSize 并且 ⼜有新任务提交时,就会触发拒绝策略。
keepAliveTime :当线程池中线程的数量⼤于corePoolSize ,并且某个线程的空闲时间超过了
keepAliveTime ,那么这个线程就会被销毁。
unit :就是keepAliveTime 时间的单位。
workQueue :⼯作队列。当没有空闲的线程执⾏新任务时,该任务就会被放⼊⼯作队列中,等
待执⾏。
threadFactory :线程⼯⼚。可以⽤来给线 程取名字等等
handler :拒绝策略。当⼀个新任务交给线 程池,如果此时线程池中有空闲的线程,就会直接执
⾏,如果没有空闲的线程,就会将该任务加 ⼊到阻塞队列中,如果阻塞队列满了,就会创建⼀
个新线程,从阻塞队列头部取出⼀个任务来执⾏,并将新任务加 ⼊到阻塞队列末尾。如果当前
线程池中线程的数量等于maximumPoolSize ,就不会创建新线程,就会去执⾏拒绝策略
你知道哪些 JVM 的 GC 机制?

Serial 收集器( 复制算法): 新⽣代单线程收集器, 标记和清理都是单线程,优点是简单⾼效;
ParNew 收集器 (复制算法): 新⽣代收并⾏集器, 实际上是Serial 收集器的多线程版本,在多核CPU 环境下有着⽐Serial 更好的表现;
Parallel Scavenge 收集器 (复制算法): 新⽣代并⾏收集器, 追求⾼吞吐 量,⾼效利⽤ CPU 。吞吐
量 = ⽤⼾线程时间/( ⽤⼾线程时间+GC 线程时间),⾼吞吐 量可以⾼效率的利⽤CPU 时间,尽快完成程序的运算任务,适合后台 应⽤等对交互 相应要求不⾼的场景;
Serial Old 收集器 (标记-整理算法): ⽼年代单线程收集器, Serial 收集器的⽼年代版本;Parallel Old 收集器 (标记-整理算法):⽼年代并⾏收集器, 吞吐 量优先,Parallel Scavenge 收集
器的⽼年代版本;
CMS(Concurrent Mark Sweep) 收集器( 标记-清除算法):⽼年代并⾏收集器, 以获取最短回收停顿时间为⽬标的收集器, 具有⾼并发、低停顿的特点,追求最短GC 回收停顿时间。
G1(Garbage First) 收集器 (标记-整理算法):Java 堆并⾏收集器, G1 收集器是JDK1.7 提供的⼀个新收 集器, G1 收集器基于“标记-整理”算法实现,也就是说不会产⽣内存碎⽚。此外,G1 收集器
不同于之 前的收集器的⼀个重要特点是:G1 回收的范围是整个Java 堆(包括新⽣代,⽼年代),⽽
前六种收集器回收的范围仅限于新⽣代或⽼年代
⾮「常规⼋股」
如果服 务应⽤部署在 Linux 上,CPU 打满后,想查看哪个进程导致的,⽤
什么 命令?
⽅式⼀ , top :这是⼀个实时监控系统性能的⼯具。你只需在终端中输⼊:
top

然后可 以按 P 键来按 CPU 使⽤率排序,查看哪些进程占⽤了最多的 CPU 资源。
⽅式⼆, ps :如果你想查看当前所有进程的 CPU 使⽤情况,可以使 ⽤:

这将显⽰ CPU 使⽤率最⾼的前10 个进程。
⽅式三,pidstat :如果你需要更详细的信息,可以使 ⽤ pidstat :
pidstat -u 1

这将每秒显⽰所有进程的 CPU 使⽤情况。
如果想查看是进程的哪个线程,⽤什么 命令?
在 ps 和 top 命令加⼀下参数,就能看到线程状态了:
ps -eT | grep <进程名或线程名>执⾏ top -H -p pid ,查看该进程下占⽤CPU ⾼的线程id ,⽐如我们定位到我们的占⽤CPU ⽐较⾼的
进程id 为 99770

我们会 看pid 为99803 的线程占⽤CPU 最⾼,将其转换为16 进制数,为 0x185db
想查看代码中哪个位置导致的 CPU ⾼,该怎么做?Java 应⽤怎么排查 CPU
或内存占⽤率过⾼的问题?
可以通过 jstack ⼯具具 体查看线程哪个位置导致 cpu 过⾼的原因。
- 将线程的 dump 输出到 dump.out ⽇志⽂件中
jstack 99770 > dump.out- 查看dump ⽇志,查找关键字0x185db (线程id 的 16 进制),刚才我 们定位的线程pid 的16 进制
表⽰:

当程序出现故障,往往 ⼀次 dump 的信息,还不⾜以确认问题。建议最好多 ⽣成⼏次 dump 信
息,⽐如3次,如果每次 dump 都指向同 ⼀个线程代码,我们才确定问题的典型性。
以上,就是如何使 ⽤jstack 命令查看CPU 使⽤率⾼的线程运⾏⽇志信息,定位到具体的代码⾏。
数据库翻⻚(limit )查询时,发现越往后查询越来越慢,为什么 ?该如何
修改 SQL 能解决?
数据库翻⻚查询时,尤其是使⽤ LIMIT 查询,有时会出现性能下降的问题。原因是当使⽤
limit x, y 时,数据库必须跳过 x 数量的⾏,这意味着它可能需要完全扫描前⾯的⾏,从⽽导
致查询变慢,⽐如 limit 1000,10 ,查询⽅式就是从第 1条记录扫描到第 1000 条记录之后,往右边
取 10 条记录返回,然后抛弃前⾯扫描的 1000 条记录。
解决⽅式,可以考虑使⽤基于主 键的分⻚。例如,对于某个表的时间戳或 ID 列,可以这样做:
-- 使用last_seen_id替代OFFSETSELECT * FROM your_table
WHERE id > last_seen_idORDER BY id
LIMIT 10;这种⽅式避免了 linut x ,y 深分⻚的问题,只获取相对清晰的上⼀⻚最后⼀个 ID 之后的记录。
Insert 或 Update ,不⽤两个 语句去 分别判 断,⽤⼀条语句实现存在就更
新,否则就插⼊?
在 MySQL 中,可以使 ⽤ INSERT ... ON DUPLICATE KEY UPDATE 语法:
INSERT INTO table_name (id, column1, column2)
VALUES (1, 'value1', 'value2')
ON DUPLICATE KEY UPDATE column1 = VALUES(column1), column2 = VALUES(column2);在这个语句中,如果 id (假设是主键或唯⼀索引)已经存在,则更新 column1 和 column2
的值;如果 id 不存在,则插⼊⼀条新的记录。
算法
⽆重复字符的最⻓⼦串
