原华为岗跳槽美团,整个面试流畅度高,表达清楚,面试官还是不满意,大厂太难了
视频记录了一位拥有京东和华为背景的Java工程师面试美团的全过程,涵盖自我介绍、项目经验及技术细节,并分析了大厂面试的评价标准。
UP主: IT枫斗者-Java面试突击 · 时长: 53:46 · 🔗 B站原视频
标签: 面试实录 · Java面试 · 美团面试 · 职场跳槽 · 程序员求职
自我介绍与工作背景
面试官:我是美团这边一面的面试官,您方便先自我介绍一下吗?
候选人:面试官您好,很荣幸今天参加您的面试。我先介绍一下我自己,我今年32岁,18年毕业于西安交通大学研究生,专业是软件工程。这些年一直在做软件开发方面的工作。工作中接触的技术栈比较多,包括Java、安卓、前端、人工智能Python那块都有接触,但是大部分的时间还是在搞Java。包括Java经常用的一些技术栈,比如Redis、MySQL、Dubbo、Zookeeper这些,包括常用的Java基础、高并发还有分布式系统这些。
在工作经历上,我17年的时候拿到了京东的校招offer,当年就去了京东,在京东待了3年多,包括实习和正式工作有两年半。20年的时候考虑要成家了,当时就回了西安来成家。现在想法就是家里已经安顿好了,趁年轻还想出去再找一份工作,在一线城市再历练历练。
我个人的性格比较外向,除了工作之外,跟同事关系也很好,比较喜欢玩。但是我绝对是有责任心的,工作上也会尽力去把工作做好。我近两次的工作绩效,包括去年和上半年都是A。大概就是这么个情况。
华为在职情况与求职意向
面试官:那您现在是还在西安华为吗?
候选人:对,我现在是这样,因为西安没有太多的互联网公司,当时我就想回来。我不知道您对华为这边了解不,华为现在招聘的话,中端和低端都是以OD的形式进来,后期会转。我现在已经在走华为转正式的流程,四轮面试(两轮技术面、一轮主管面、一轮HR面)都面试完了,现在就是走转正式合同的流程,转完之后就是一个华为的正式合同了。因为当时没有太多的选择,觉得薪水什么还可以,当时就过来了。
面试官:那您现在在西安,然后我们这个岗位是在北京,你知道吧?
候选人:对,我现在就是想找一线城市的工作机会。
面试官:你现在还在华为在职是吧?
候选人:对,在职。
项目经验:To B 与 To C 业务对比
面试官:我刚看您简历,您在京东的时候主要用的Java,在华为的时候是不是主要做算法呀?
候选人:华为是这样,有一年人工智能这块的算法经验。我在华为也两年半了,有一年是做人工智能这块,其他的大部分时间还是在做Java。
面试官:做企业内部系统是吧?
候选人:对,这个系统相对来说并发量没有京东的并发量高,因为这个应用是To B的,并发量不会很高。但是它的业务特别复杂,因为涉及到很多通讯的材料,就是我们装光纤线缆之类的这些材料的业务会很复杂。所以这块的挑战就是业务特别复杂。当时有一个特别复杂的业务,我们团队的领导讨论了很久解决不了,领导让我去解决,我就很快把那个问题给解决掉了。
面试官:是一个算法问题?
候选人:对,是一个算法问题。我可以给你细说一下这个问题。我们在每个城市铺设网络走向,有基站,有光纤网络线缆。这个线缆有不同的规格,有粗的、有细的。一线工人会把勘探数据发给我们,说从哪个基站到每一家用户线怎么走。那么就提出一个问题,比如我们挖管道,粗的管道如果挖了就不用挖细的管道了。大概是这么一个问题,就是说整个城市设计了很多的线路,如何把这些粗的线路、中的线路和细的线路进行合并。这个数据量很大,一个城市可能有几百万。从代码的角度来想,如果有重叠的就进行合并,如果不重叠就不合并。当时就有一个这么个问题需要解决,您还需要我继续讲怎么实现的吗?
面试官:不用,我大概听懂了。
候选人:还是比较复杂的一个算法,当时我写了大概两天多,从算法角度把这个问题解决掉。
面试官:你用的什么算法吗?
候选人:大概思路我讲一下。说起来很简单,但实际写起来还是挺麻烦的。我大概思路是把整个城市,因为我要进行两两的线路比对,看这两个线路是否有重叠。如果重叠的话,我只算粗的,细的就不算挖管道的长度了。如果是两两去比对,计算量会很大,几百万条线缆两两比对,N的平方可能就几十亿上百亿了。如果每个城市都这么算就很困难。当时我就想了一个分组的想法,把这些线缆全部分组去比对。怎么分组呢?不能按区域分,因为按区域分组解决不了问题。我当时把所有的线缆按照斜率去分组,相同或者相近的斜率,如果两条线缆斜率是一样的,不管它在城市的东北角还是西南角,我们根据斜率把它进行分类。这样就可以把一个很大数据量的算法分成很多小分组,根据斜率分组之后去两两比对,这样就把问题解决了。
面试官:在华为后面也用了Java是吧?你个人刚刚说To C和To B,你自己感觉是To C的并发量更高,看量更大是吗?
候选人:对,我个人感觉来讲,To C这块并发量会很大,我们需要设计一个比较好的系统,包括数据的准确性、系统的可靠性,我们要实现一个高可用的系统、高并发的系统。对于To B这块,因为并发量和系统的复杂度没那么高,但是业务又很复杂。所以在做To C业务的时候,对技术要求会比较高;那么To B这块,对业务的理解要求会比较高一点。
高可用系统设计
面试官:你怎么去实现一个高可用的To C系统呢?这块有什么经验可以分享吗?
候选人:如果是一个高可用的话,首先我们要有检查机制。现在比较流行的高可用或者高并发系统,一般都是分布式系统。在分布式系统里边,我们每一步操作、数据流转、数据透传,要保证每一步的可靠性和数据的一致性。可以通过一些检查机制、版本机制去保证每一步数据的正确。然后再通过,比如有高并发,为了避免机器宕机,我们在京东的时候会杜绝用户直接查数据库,因为数据库承受的并发可能没有那么高。可以给一些缓存或者内存的方式,用内存直接返回,或者去查缓存等方式去保证高并发。就是从数据的一致性、高并发去满足系统的高可用。
Java基础:CAS与锁机制
面试官:Java基础怎么样啊?
候选人:您可以考察一下,我觉得Java这块应该还是看了不少的,学过也用过不少。
面试官:你大概给我讲讲Java里面CAS机制吧。
候选人:是这样,在做多线程的情况下,我们知道锁可以分为很多类型,比如共享锁、排他锁、乐观锁、悲观锁,从不同维度根据不同的业务把锁进行分类。刚才您说的CAS就属于乐观锁的一种实现。悲观锁的话,就是我们对一个资源去操作的时候,如果被其他线程占用,另一个线程完全不能操作。在乐观锁的情况下,我们会对比一下,CAS就是Compare And Swap的一个标准动作。这个动作的流程就是我们先检查一下数据,有可能是版本号,有可能是数据的值内容,先检查一下,如果满足我们的要求再去操作,如果不满足要求就不操作。这就是乐观锁的机制。我们常用的CAS工具,比如AtomicInteger和AtomicLong,这两个工具我们就可以直接去使用,实现CAS操作。
面试官:CAS它能保证线程的可见性吗?
候选人:可见性应该是不可以,它主要是去保证原子性。我个人理解是这样。原子性就是说我们在操作里边,多个操作要么都发生,要么都不发生。可见性的话,是多个线程操作一个变量的时候,另一个线程可以立即感知到这个变量的改变。可见性我们会通常用volatile去修饰一个变量,这样可以保证多线程对这个变量的实时观察。原子性主要是保证操作的原子性,要么都操作,要么都不操作。
Java基础:Integer常量池
面试官:看看这个啊。我定义了两个对象A和B,判断A等于B,这个X是任意整数,他俩是否相等的结果是什么?
候选人:是这样,如果这个X是-128到正的127之间,那么这个等号是成立的。为什么呢?因为在包装类型,特别是Integer这块,JVM为了节省每次建对象的开销,在-128到127之间会有一个常量池。我们每次取的时候,如果是在这个区间,它就会取同一个对象。如果超过这个-128到127,它就会创建一个新的对象。具体为什么会这么做呢?就是Java的研发人员觉得-128到127这个区间可能会比较常用一点。超过这个范围它就是不等的。
Java内存模型与逃逸分析
面试官:能大概说说Java的内存模型吗?
候选人:Java的内存模型,就是我们常说的运行时数据区。除了运行时数据区,其实还有一个类加载子系统和一个执行引擎。执行引擎主要是负责编译和垃圾回收。运行时数据区分为经常会用的五个部分:堆、虚拟机栈、本地方法栈、程序计数器和方法区。
面试官:堆里面放啥啊?
候选人:堆一般是放我们常用的对象。堆也会分为老年代、新生代这些。
面试官:栈里面放什么?
候选人:栈是我们一个方法栈,它有很多栈帧,主要是方法运行的时候,这些栈帧会存一些我们的数据,包括一些基本类型也会放在栈里边。其实我们知道有逃逸技术,所有的对象都在堆里边吗?也不一定。如果这个对象只是在方法内部用,在外部不会用到的话,符合逃逸分析,那么这个对象其实也会在栈里边,然后也会被及时销毁。垃圾回收不是会识别每个对象是否可以被回收的吗?怎么去判定的?先找GC Root。有一部分就是从栈里边看一下,这个对象是不是在栈里边,如果是在栈里边,它就是一个GC Root。当然还有一些其他的GC Root需要去识别。
面试官:看我现在定义了一个A,假如说在一个方法里写了这么一行代码,大概跟我说说这里面它的这些信息是怎么存储在你刚刚说的内存模型里面?
候选人:您说是栈帧这块是吧?
面试官:这是我写了这么一句之后,它的整体的一个存储是怎么样的?存储结构。
候选人:栈帧的存储结构有这么几个东西:局部变量表、操作数栈,然后有一个动态链接。其实我们的参数包括内部的声明都是在局部变量表里边的。操作数栈就是执行我们的计算,还有一些中间结果用于储存计算相关的值。主要就是有一个局部变量表去存储我们的那些局部变量。
线程池与并发编程
面试官:用过线程池吗?
候选人:线程池也有用过。
面试官:线程池里面,比如说核心线程数和最大线程数,一般怎么设置呢?肯定有一个设置依据,不可能随便设。
候选人:首先线程池的大小设置跟我们的业务是有关系的。我们根据业务可以大致分为IO密集型和计算密集型。IO密集型的线程,我们一般会把线程池设得大一点,比如说是两倍的CPU核心数。如果是计算密集型的话,核心线程池的数量会直接设置成CPU核心数或者核心数减一。
面试官:有用过CompletableFuture吗?它跟我们1.5里面的Future最大的使用上的区别是什么?
候选人:Future应该是Java 5引入的,我们在线程执行时会返回一个结果,就通过这个Future去返回。CompletableFuture应该是Java 8引入的,它相对来说会比较灵活一点,允许一些更复杂的异步操作。
面试官:平时会关注一些Java方面的最新动态吗?比如Java 21现在新出来,有一些最新特性还是比较有革命性的。那你大概说说虚拟线程呗?
候选人:学习渠道的话有时候会看一些博客,还有就是买一些Java方面的书,比如早期的《Java编程思想》,阿里巴巴出的《Java开发手册》。最近因为在准备面试,看的也不少。
面试官:虚拟线程还要讲吗?
候选人:不用了。
JVM排查:Full GC与OOM处理
面试官:假设现在你负责的服务频繁报Full GC,你会怎么去排查处理呢?
候选人:如果是频繁报Full GC的话,首先我们要去看一下是哪一段代码。以前在公司的时候,如果有这种频繁报Full GC的,我们是有平台可以直接去线上看到底是哪些方法、哪些对象比较多,直接去平台上看看会比较方便。如果没有这些工具,我们要自己去看的话,可以通过一些命令。
面试官:发生Full GC,你第一反应是会去看什么?你可以把你处理的流程完全说一下。假设现在是一个线上的应用,开始承担线上请求,突然你收到Full GC的报警。
候选人:首先我觉得Full GC应该不牵扯到错误问题,它应该就是堆里边某个对象在短时间内聚集了太多。如果是线上问题的话,也没办法立马把代码上线换掉,因为调用量很大的服务肯定有个正规流程,要改代码、审批补丁之类的。我们去查Full GC,首先要有一个思路,找出来导致Full GC的对象是哪一个,找到对象就能找到大概是哪一个方法。我们可以看一下内存快照,看一下当时产生很多对象的那个对象是哪个,找到相应的代码,再看看怎么把代码优化一下。
其实我前段时间刚经历过一个内存溢出的问题。当时我们去生成一个文件,文件里边有很多内容,每个内容对应一个Java对象。有时候文本会很大,好几百兆。在生成数据的时候,如果原数据量很大,我们要把产生大量对象的这个操作进行分批处理。比如我一次要生产100万个对象的对应文件,我每次产生10万个,然后每次去写入,这样就不会产生内存太大的问题。
消息队列与Kafka架构
面试官:你们会用消息队列吗?
候选人:原来在京东的时候用的消息队列,其实它是基于Kafka包装的一个消息队列。在华为这边倒没有用到消息队列。
面试官:你对消息队列有了解吗?
候选人:消息队列有三个大的作用:一个是异步,这样代码响应会好一点;然后是解耦,整体系统的可扩展性会比较好一点;再一个是削峰,在大数据量比如秒杀,或者同一时间有高并发请求的时候,我们通过MQ进行削峰。拿Kafka来举例的话,它有这么几个组件:消息的生产者、消息的消费者、消息的主题Topic。再一个就是集群,在Kafka里边我们叫Broker,Broker里边每个消息有分区。
面试官:Kafka为什么吞吐量那么高呢?
候选人:Kafka的读写速度会很快。为什么会很快呢?它在磁盘那块是顺序写入的,非随机写入。还有一个零拷贝机制,在磁盘写入和读出的时候有零拷贝机制,保证消息读取和消费很快。主要是磁盘顺序写入和零拷贝。
面试官:它所有的消息都是顺序写入的吗?
候选人:它是在每个消息分区里边是顺序的。如果是在不同的节点上,它也保证不了顺序。如果我们把消息指定到一个分区里边,那么它整个消息都是顺序的。
面试官:现在假设我是一个交易场景,肯定是先下单后支付。假设这两个都通过消息去进行处理,我怎么保证这个顺序呢?我怎么保证支付肯定是在处理下单之后?
候选人:我们在下单的时候可以写库,写库成功之后再发消息,这样就能保证顺序。
面试官:我现在有两个消息,一个是下单消息,一个是支付消息。我怎么保证消费者处理的时候,肯定是先处理下单消息,再去处理支付消息?
候选人:可以有很多种实现方法。比如在支付的时候,去缓存里边查一下这个订单有没有下单成功,然后再去进行下一步。或者把这两个消息搞成顺序的,下单之后再发支付消息。
面试官:你的意思是要把下单和支付发在同一个Partition下面去吗?
候选人:放在一个分区里边是可以保证他们的消息发送顺序,但是不能保证业务可行。我觉得不用这种方式,就是用我刚才说的那种,在支付的时候检查一下下单成功没有。
MySQL索引分析
面试官:你们用的是MySQL是吗?
候选人:对,用的是InnoDB。
面试官:现在假设有一张表,里面有很多字段,其中有几个核心字段,第一个是ID,第二个是age,第三个是name。ID肯定是主键索引,然后我建立了一个name和age的联合索引。我现在有一个查询语句,能大概分析一下这个语句是怎么去索引数据的吗?
候选人:我觉得还是会走name和age的联合索引。这个name应该是走索引了,然后age是没有走索引的。
面试官:你大概就分析一下这个语句它是怎么个索引过程吧。
候选人:InnoDB在查的时候,先去找name相关的,通过name索引走完之后,再去查age的时候就不会再走索引。我理解是这样。
开发框架与算法题测试
面试官:你平时用Spring Boot是吧?
候选人:Spring Boot倒没怎么用过。在分布式服务这块我们用的是Dubbo,Spring会在编码做AOP的时候用。构建的话直接用Maven去构建。
面试官:那我们做个算法题吧。假设有N个房间,每个房间里面东西价值是V[i],不能偷相邻的房间,要不然会被报警。想问一下小偷偷这N个房间,偷出来价值总和最大的是多少?
候选人:这个数组代表是每个房间里面的价值行。大概10分钟吧。
面试官:在这写呗。
(候选人编写代码)
候选人:写完了。应该还有一种特殊情况,应该差不多。其实我平时做题还是比较多的,刷题比较多,这个题我好像见过。
面试总结与反馈
面试官:行,我今天的面试大概就到这。你看你有什么问题想问我这边吗?
候选人:因为我也很久没有面试过了,现在也是在积攒经验。很荣幸今天能给您面试,也感谢您辛苦。我想问问看我有哪些方面还需要改进的,我可以多多努力。今天确实比较紧张,很久没有面试了。您直说,我都能承受。
面试官:可能我们这边更希望候选人对互联网常用的技术栈相对更熟悉一点。
候选人:您觉得我熟悉的还不够深入是吧?
面试官:可能还需要加强一下。
候选人:好的,今天辛苦您了,谢谢,再见。
面试官:再见。