美团 日常实习一面凉经
时间线:12.04 官网投递,12.09发了邮件选择约面时间,12.10一面。面试时间70分钟,已挂。
个人总结反思
这次面试碰到的面试官非常非常好,很感谢这位面试官。写算法题过程中会引导思路,考察八股时也会告诉我不会的该怎么答,以及纠正错误。整体面试体验是这几场以来最好的一次,可惜最后挂了。
面试过程暴露了很多新的问题,最大的锅是自己算法写凉凉了。当天晚上面完之后估计已经挂了,心情一塌糊涂。又匆匆地开始预习编译原理准备12.11晚上的期末考试,一整个晚上都没怎么睡着。
之前有很长一段时间没写链表类的题了,一直在刷图论和动态规划。结果碰到这么两个链表题,当时心里就一惊。现在面试过程中答八股和项目相关问题时不再那么紧张,但是写算法的时候还是冷汗直冒。
第一个算法写了10分钟没写完,面试官提出换题。结果第二个写了整整二十分钟,最后还是有bug。面试结束之后,我去重新看了一下代码,检查了近十分钟发现是翻转链表的子函数漏了一行很关键的代码。补上这行就跑对了,真是草了。后续刷lc上的算法必须得直接在网页端编写代码和寻找bug,之前一直在vscode上写顺手了,面试过程中那种直接搁平台上写的情况还是有些不适应。
美团的Java技术栈考察重点有相当一部分是JVM和JUC相关,问的很多。这部分之前背过一些八股,但JVM自己忘了不少,导致面试的时候很多答不上来。八股这东西真是背了忘忘了背,难搞的很。需要再过个1-2轮,才能到达和面试官吟唱的程度。
面试内容如下
1)面试官介绍部门业务
2)自我介绍
3)课程是都结束了吗?
4)你这几个项目和技术栈都是通过什么渠道去学习的?
这边答了通过各种技术博客、网站,以及B站和YouTube上的网课学习。也会和一起找工作的哥们交流学习进度和经验。
5)向你们大学期间,Java相关的技术栈和MySQL相关的知识,基本都是学过了是吗
答了是这样,基本都是靠自学,学校教的鸟用没有
6)都是通过自学,就是看一些书籍和网课视频去自学的是吧
7)然后你这个项目我看都放github上了,我点开看了一下。
这边我和面试官都绷不住笑了,估计是看到了我github主页里自学技术栈过程中的commit的一些吐槽和学习记录,以及心态转变、日记记录。
项目相关
1)首先这个校园生活服务平台,它提到了缓存的一个处理。我想了解一下哪些数据加了缓存?
答了主要是商铺数据信息,还有博客主页的一些带货链接,放在缓存里方便用户后续查询跳转。
ps:这边其实不太记得啥东西加缓存了,缓存里一般放的是高频热点数据和首页推荐流
2)这里面提到了基于 Cache Aside(旁路缓存)模式,你还了解其他的模式吗?
答了Cache Aside对应的读写策略(自己忘记剩下那两个模式叫什么了),“先删缓存,再更新数据库”可能导致脏数据读入缓存的问题。
ps:剩下那两个分别是Read-Through/Write-Through(读写穿透)和Write-Behind(异步写入/写回)。
3)你的这个缓存有设置过期时间吗?过期的机制是怎么实现的?
答了设置过期时间TTL,并且采用惰性删除的策略。(但这边表述的还是不太流畅)
ps:Redis的底层过期机制:
惰性删除:客户端访问key时,Redis检查是否过期,过期则删除。
定期删除:Redis后台线程每隔一段时间随机抽取部分设置了过期时间的key进行检查和删除。 这样结合既避免了CPU空转,也防止了过期数据长期占用内存。”
4)热点商户信息使用互斥锁机制,这个可以介绍一下吗?是什么场景下使用的?
答了热点商户在实际生产中会有大量用户查询。加锁是为了保证同一时刻只有一个线程去查询数据库,防止缓存击穿。
ps:此处答的不太好,应该提到在热点key缓存过期的瞬间,如果有海量的并发请求打过来,会全部穿透到数据库,造成数据库宕机。
5)扣减库存的问题,这个你是怎么实现的?
答了这个问题和超卖问题,使用乐观锁CAS机制并加上版本号进行校验,去扣减库存。
算法考察
1)lc 25:k个一组反转链表2)lc 445:两数相加 II
JavaSE & JVM
1)Java集合包含哪些类?
答了有list、Set、Map之类的(这边也没答全)
2)Map的话哪些是线程安全的,哪些是线程不安全的?
答了HashMap不安全,ConcurrentHashMap安全。
3)ConcurrentHashMap 是怎么实现线程安全的?
这边答了底层通过CAS机制,在每个搜索位连接链表或红黑树。对头节点加锁。(但其实不太对)
ps:ConcurrentHashMap在 JDK 1.8 之后,采用 CAS + synchronized 来保证并发安全。锁的粒度更细,只锁住当前哈希桶的头结点。如果该桶为空,就采用CAS插入;如果不为空,就使用 synchronized 去锁住头结点进行链表或者红黑树的操作,这大大提高了并发性能。
4)Java里的反射和动态代理一般是在哪些场景会使用呢?
这边答了AOP底层利用动态代理。JDK 动态代理针对实现了接口的类,CGLIB针对没实现接口的类,通过反射创建子类重写方法。
ps:这样答感觉不太恰当,反射的话主要用户框架开发,比如Spring IOC容器实例化Bean;或者说JDBC加载驱动。
5)Java中的异常分类
这个忘了,然后随便扯了IOException和OOM(完全答错了)
ps:查了一下异常Exception应该分为运行时异常RuntimeException和非运行时异常(IOException、ClassNotFound等等)
6)JVM的内存管理(内存分区)可以介绍一下吗?
这边答了分为栈、堆、方法区、元空间、程序计数器。堆是线程共享,栈是线程私有。
ps:有很多地方答错了,没记太清。JVM 运行时数据区主要分为线程私有和线程共享这两个部分。线程私有的包含程序计数器、虚拟机栈、本地方法栈;线程共享的包含堆和方法区(JDK 8中采用元空间实现)
7)CMS垃圾回收器的过程讲一下,分了哪几个阶段?
不会
ps:查了一下,CMS收集器分为初始标记、并发标记、重新标记和并发清除四个阶段。
8)常见的垃圾回收算法你了解哪些呢?
这边吟唱了标记清除、标记整理和复制算法(忘记答分代回收算法了,问题出在我忘记“分代回收”这个名词上面)
9)标记待清除的对象是根据什么策略来标记的?
这边吟唱了引用计数法和可达性分析算法
10)类加载器的分类,有哪几种类加载器?
不会(完全忘了)
ps:应该是启动类加载器、扩展类加载器、系统类加载器/应用程序类加载器、自定义类加载器
11)类加载机制里,双亲委派机制了解吗?
答了创建对象时委托父类去创建,不用重复加载,解决安全问题,比如防止黑客篡改 System 类。(这边答的时候说岔了,把加载类说成创建对象)
ps:更恰当的说法应该是:双亲委派模型指的是一个类加载器收到类加载请求后,它自己不会首先去加载,而是把请求委托给父类加载器。层层向上,直到顶层的启动类加载器。只有当父加载器反馈无法完成加载时,子加载器才会尝试开始自己加载。其好处是安全性(防止核心API被篡改)和避免重复加载(父类加载过的类,子类无需再加载)。
12)外部引入的jar包是通过哪个类加载器加载的?jdk原生的类是谁加载的?
不会(我一开始都没听懂他在问什么,还让面试官重复了一下问题。这边直接把面试官唐笑了,我自己也没绷住笑了。面试官然后和我提到了Extension ClassLoader和AppClassLoader)
ps:查了一下,外部引入的jar包是由最底层的 应用程序类加载器(AppClassLoader)加载的,而 JDK 原生的类是通过顶层的启动类加载器(BootStrap ClassLoader)加载。
Java 并发
1)线程池有哪些常用的参数?
答了7个参数:核心线程数、总线程数(最大线程数)、阻塞队列、时间单位、存活时间、拒绝策略、线程工厂
2)其中的拒绝策略有哪几种?
这边完全忘了,然后答了有一种是直接抛出异常(实际上是AbortPolicy:直接抛出一个任务被线程池拒绝的异常)
ps:有四种预置的拒绝策略:CallerRunsPolicy:使用线程池的调用者所在的线程去执行被拒绝的任务AbortPolicy:直接抛出一个任务被线程池拒绝的异常DiscardPolicy:不做任何处理,静默去拒绝提交的任务DiscardOldestPolicy:抛弃最老的任务,然后执行该任务
3)如果核心线程数已经占满了,如果来了新的任务他会怎么处理?
这边答的一坨,说如果核心线程数小于最大线程数,会创建救急线程去处理。(严重答错)
ps:实际上是会先扔到阻塞队列里等待,如果阻塞队列也满了,线程数 < 最大线程数,会创建非核心线程(救急线程)来处理;否则如果最大线程数也满了,就执行拒绝策略)
4)AQS是如何实现的?可以介绍一下吗?
答了AQS是叫Abstract Queued Synchronizer,它底层是有一个status去记录是否被锁进行占用。这个变量是能够保证同一个线程可重入的。其次它里面有个队列,底层是用双向链表去实现。然后又提了一下后续线程唤醒策略决定锁是否公平
ps:复习了一下,AQS实现核心分为三个部分:同步状态(State)、CLH队列(等待队列)、原子操作(CAS),它实际上是一个同步器框架。CLH队列的头指针指向当前持有锁的线程节点,如果线程获取锁失败,会被封装为一个Node,插入到等待队列的末尾,随后线程被阻塞。
对于独占模式(比如ReentrantLock)的实现流程
- 尝试获取锁(acquire):先尝试CAS抢锁(线程调用 tryAcquire()去尝试原子性修改state),如果成功,那么当前这个线程是独占线程。如果失败,要么是重入(state+= 1),不是重入的话进入等待队列。如果线程阻塞,会检查是否是head.next,如果是,会尝试继续抢锁,防止“假唤醒”。抢锁失败,线程会被park直至唤醒。
- 释放锁(release): 首先去调用tryRelease(int arg),也就是 state -= arg。如果state减到0,表示锁完全释放。随后唤醒后继节点,被唤醒的后继节点也会进行抢锁,防止“假唤醒。
5)ThreadLocal 它内部是一个什么样的数据结构?
这边答了Map(也许直接答ThreadLocalMap更好一点?),然后提到了弱引用和内存泄漏的问题,需要在最后的finally中调用remove()方法
ps:实际上,ThreadLocal的底层实现是依靠Thread类内部维护的一个ThreadLocalMap
6)那么它的key是啥?value是啥?
这边答了key是线程的id,value是存储的变量(答的不太对)
ps:key是ThreadLocal对象本身,value是我们要存储的变量副本。对于弱引用问题:ThreadLocalMap中的key是弱引用,如果ThreadLocal外部没有强引用,GC时会被回收,导致Map中出现key为null但是value还在的情况,造成内存泄漏。所以,使用完之后务必调用remove()方法。
MySQL
1)事务隔离级别有哪些?MySQL默认是哪个?
答了 uncommitted read、committed read、repeatable read、serializable,其中MySQL默认是Innodb引擎,也就是可重复读。
2)介绍一下索引的数据结构
答使用B+树,然后开始吟唱B+树特性(范围查询、只有叶子结点存储数据信息、矮胖树)
3)像我们查询条件里的 like 语句,是执行索引的吗?
这边答了看情况,或者说是左模糊匹配,此时就会发生索引失效。如果在最右边的话,那么还是能够进行部分索引查询。
ps:答的应该没错,这边最好再提一下MySQL的B+树中的最左前缀原则
4)什么是联合索引,这个了解吗?
答联合索引是多个索引并行,首个字段会有序,之后的字段在前者索引限定的区间内各自有序(此处答的还是欠妥)
ps:联合索引就是多列索引,也就是在数据库表的多个字段上创建的一个索引。符合多字段排序和最左前缀原则。
5)SQL 题目:统计最高分的学生(两张表,一张包含student_id、course_id、score,还一张包括stundent_id、student_name,然后要求把总分最高的那个学生的student_name和总分返回)
这个题不是很难,面试官给题的时候也说很简单的一道题。写了个子查询搞掉了,但是还是出了点小错,一开始把SUM写成COUNT,还忘了写order by,后面补回来了。
反问
1)关于此次面试中我的改进之处,当然有一点很重要,是我刚刚那个算法手撕没写出来
面试官告诫我要继续多练练算法,就是有一些很难的算法倒还好,但是像反转链表这种肯定是要很快的把它写出来不出错。
2)后续技术栈学习和关于项目学习的建议,有没有必要再学一下ES之类的
面试官说这几个项目其实挺全的(前后写过的外卖、点评和现在正在学的12306火车购票项目),简历上的技术栈也基本覆盖了要求。如果可以的话,再加一个Kafka。建议是现在把那些现有的基础的再再打牢一点。因为到时候你像秋招或春招面试的时候,他不会太要求你会多少框架,但是那些技术底层实现他们是一定会考察的。你的项目我觉得现在面试,就是实习这个阶段而言已经够了;那如果面向明年的暑期的话,也是足够的。对,因为本身实习生就没有项目,因为大部分都是在一些视频课程上做的一些实践,对吧? 然后就主要针对是针对项目的话,主要就是看你们那个实践的过程中的一些思考,就是有一些问题是不是真正理解了。 然后至于那些工具和框架,其实不用看着那么多。当然你像常用的这个SpringBoot框架和MySQL、Redis,这个是越熟悉越好。像ES那种的话,它属于是扩展科目了。对,这不要求这个阶段一定要会的。
3)面试流程
日常实习的话是两轮技术面+一轮hr面
最后真的很感激这位面试官,给出的建议全面周到,而且相当客观真实。他在提问的时候很和蔼客气,很nice。之后自己一定要好好继续学下去
,明年暑期争取能去美团(先幻想一波)
本专栏用于记录我的日常实习、暑期实习和秋招中面经的记录、反思和总结
