美团面经(完结)
| 项目 | 2023 年 | 2022 年 | 2021 年 | 单位 |
| 员工人数 | 114860 | 92046 | 100033 | 人 |
| 员工总薪酬 | 430.94 | 416.2 | 347.68 | 亿元 |
| 人均薪酬 | 37.52 | 45.22 | 34.76 | 万元 |
| 扣除股权激励 | 30.22 | 35.72 | 29.56 | 万元 |
| 扣除股权激励+社保+公积金+高管费用 | 23.20 | 27.14 | 23.25 | 万元 |
同学 1(附答案)
1.自我介绍
2.java的基本数据类型,对应的字节数;
3.基本数据类型和包装数据类型的区别,int和Integer有什么区别(默认值不一样,泛型只能是包装数据);
答案
- 包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
- 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有的对象实例(逃逸分析)都存在于堆中。
- 相比于对象类型, 基本数据类型占用的空间非常小。
4.浮点数据类型使用的时候要注意什么,精度丢失的原因?
答案
浮点数据类型在进行精确计算时,会出现精度丢失的问题。因为浮点数很多小数在二进制环境下很多都无法完整的表示,只能截取部分数据来近似的表示。
1)由于计算机内部以二进制保存,所以十进制的有限位的小数,在计算机内部会是一个无限位的小数。
2)计算机保存浮点数的精度有限,例如float可以保留十进制最多7位(二进制23位)有效数字,double 以保留十进制15~16位(二进制52位)有效数字。那有效数字以后的就被忽略了。
3)根据浮点数的存储标准(IEEE制定),float类型指数的起始数为127(二进制0111 1111),double类型指数的起始数为1023(二进制011 1111 1111),在此基础上加指数,得到的就是内存中指数的表示形式。尾数则直接填入,如果空间多余则以0补齐,如果空间不够则0舍1入。
5.java面向对象的特征,具体表现是什么,接口和抽象类的区别和共同点(都不能直接实例化),继承的优缺点?
答案
共同点 :
- 都不能被实例化。
- 都可以包含抽象方法。
- 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。
区别 :
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
- 一个类只能继承一个类,但是可以实现多个接口。
- 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
继承的优缺点
1、优点
- 提高了代码的再利用性。
- 提高了代码的维护性。
- 让类与类有关,是多态的前提。
2、缺点
- 提高类的耦合性。这样一个类的变化会影响其他与此类相关的类别。
6.==和equals区别;
7.hashcode;
8.java里面的异常,之间的区别,说几个常见异常;
9.反射是什么?优缺点,反射的应用场景
答案
动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制,在运行状态中,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意一个方法和属性,这就是Java的反射机制
优缺点
- 优点:可以实现动态创建对象和编译,体现出很大的灵活性。让用户在不需要修改代码的情况下获得新增的类的对象。
- 缺点:性能会影响。
应用场景
- JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
- 很多开发工具如Eclispe、IDEA等利用反射动态解析对象的类型与结构,动态提示对象的属性和方法
- Web服务器中利用反射调用了Sevlet的service方法
- Spring AOP的特性也是依赖反射实现的
10.讲下注解(因为前面提到了我用到了自定义注解,所以我解释了自定义注解我的实现);
答案
提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。是一种说明、配置、描述性的信息,与具体业务无关,也不会影响正常的业务逻辑。但我们可以用反射机制来进行校验、赋值等操作。
注解的常见用途:
- 生成文档的注解,如@author,@param。
- 跟踪代码依赖性,实现替代配置文件功能,如spring mvc的注解。
- 编译时进行格式检查,如@override。
- 编译时进行代码生成补全,如lombok插件的@Data。
11.arratLsit和LinkedList讲一讲,是线程安全的嘛?arratLsit初始化大小,怎么从0变到10的?(扩容算法实现的);
答案
ArrayList的初始化容量在JDK1.7之前一直是10,从JDK1.7开始初始化容量为0,等到第一次add的时候再初始化为10。做这样的改动,就是延迟初始化ArrayList的实际容量,应该是考虑到空间的问题,如果一开始就初始化为10,这个大小为10的数组中就全部是存的null,如果数量多了,这个也是很大的空间。应该是这样的原因吧。ArrayList的初始化容量已经变了,不再是以前的10了,而是初始化为0,等到第一次add的时候再初始化为10。做这样的改动,就是延迟初始化ArrayList的实际容量,应该是考虑到空间的问题,如果一开始就初始化为10,这个大小为10的数组中就全部是存的null,如果数量多了,这个也是很大的空间。应该是这样的原因吧。
12.hashmap底层数据结构和原理;为什么转化成红黑树?是线程安全嘛?想用安全的hashmap,该怎么做?
13.ConcurrentHashmap怎么实现的线程安全?
14.jvm的数据区域,1.8的元空间了解吗?
15.堆的结构,垃圾回收的区域都有哪些?怎么判断死亡?引用计数法的缺点?
答案
堆的结构主要分为新生代和老年代,永久代,在JDK1.8之后永久代移除改为元空间。新生代细分为Eden区,Survivor0区,Survivor1区。
垃圾回收的区域GC 其实准确分类只有两大种:
1)部分收集 (Partial GC):
- 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
- 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
- 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
2)整堆收集 (Full GC):收集整个 Java 堆和方法区。
死亡对象判断方法
- 引用计数法
给对象中添加一个引用计数器:
- 每当有一个地方引用它,计数器就加 1;
- 当引用失效,计数器就减 1;
- 任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
- 可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
16.垃圾回收算法和常见垃圾回收器,讲了cms和g1,年轻代有哪些垃圾收集器?说下g1和摒弃分代的优点。
答案
1)垃圾回收算法
标记-清除算法,该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
- 效率问题
- 空间问题(标记清除后会产生大量不连续的碎片)
标记-复制算法,为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
标记-整理算法,根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法,当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
2)垃圾收集器
2-1)Serial 收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
2-2)ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
2-2-1)并行和并发概念补充:
- 并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。
2-3)Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用标记-复制算法,老年代采用标记-整理算法。
2-4)CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
主要优点:并发收集、低停顿**。但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
2-5)G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内
G1 收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
17.类的生命周期(类加载),讲下双亲委派机制,最顶层的类加载器是什么;
答案
每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。
18.了解系统调用嘛,哪些用户态会转换到系统态?
19.进程和线程的区别,线程的状态,进程间的通信方式,同步方式?
20.volatile了解吗,有原子性嘛,多线程怎么实现原子性,用过synchronize嘛,讲一讲?synchronize可以作用到什么上面,和ReentrantLock的区别
21.threadlocal讲一讲;
22.线程池的运行流程;
23.网络的七层模型,tcp和udp在哪一层,区别是什么?
答案
应用层、表示层、会话层、传输层,网络层、数据链路层、物理层; TCP和UDP在传输层
24.http属于那一层,常见的状态码?说下400,302;
答案
HTTP属于应用层
HTTP CODE 2xx
- 状态码:200 ok 含义:客户端请求成功
- 状态码:204 No Content 含义:请求处理成功,但没有资源科返回。204不允许返回任何实体的主体
- 状态码:206 Partial Content 含义:客户发送了一个带有Range头的GET请求,服务器完成了它。使用video去播放视频,返回206,说明视频范围
HTTP CODE 3xx
- 状态码:301 Moved Permanently 含义:永久重定向。该状态吗表示请求的资源已被分配了新的URI,以后应按 Location 首部字段提示的 URI 重新保存。
- 状态码:302 Found 含义:和 301 Moved Permanently 状态码相似,但 302 状态码代表的资源不是被永久移动,只是临时性质的。
- 状态码:303 See Other 含义:303 状态码和 302 Found 状态码有着相同的功能,但 303 状态码明确表示客户端应当采用 GET 方法获取...
未能定位到原文的划线
企业级Agent工作流编排项目PaiFlow
Vibe Coding版本的PaiAgent
派聪明RAG AI知识库Java版本+Go版本
微服务 PmHub、技术派、MYDB
求职派JobClaw(OpenClaw/Hermes架构
PaiCLI(类似Claude Code的Agent
派简历(代码已完成)
等实战项目。
1. 微信扫右侧的优惠券加入知识星球
2. 解锁星球的实战项目教程和源码: 项目源码+教程获取
真诚点赞 诚不我欺
回复