Java⾯试题2021【美团】真题:
⽬录在右边
1、Spring AOP 底层原理
aop 底层是采⽤动态代理机制实现的:接⼝+实现类
如果要代理的对象,实现了某个接⼝,那么 Spring AOP 会使⽤ JDK Proxy,去创建代
理对象。
没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候 Spring AOP 会
使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理。
就是由代理创建出⼀个和 impl 实现类平级的⼀个对象,但是这个对象不是⼀个真正的对象, 只是⼀个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这 样就不需要修改源代码。
2、HashMap 的底层数据结构是怎样的 ?
JDK1.8 之前
JDK1.8 之前 HashMap 底层是 数组和链表 结合在⼀起使⽤也就是 链表散列。
HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -
hash 判断当前元素存放的位置(这⾥的 n 指的是数组的长度),如果当前位置存在 元素的话,就判断该元素与要存⼊的元素的 hash 值以及 key 是否相同,如果相同的话, 直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash ⽅法。使⽤ hash ⽅法也就是扰动函数是为了 防⽌⼀些实现⽐较差的 hashCode() ⽅法 换句话说使⽤扰动函数之后可以减少碰撞。
JDK1.8 之后
当链表长度⼤于阈值(默认为 8)时,会⾸先调⽤ treeifyBin()⽅法。这个⽅法会根据
HashMap 数组来决定是否转换为红⿊树。只有当数组长度⼤于或者等于 64 的情况下,才会 执⾏转换红⿊树操作,以减少搜索时间。否则,就是只是执⾏ resize() ⽅法对数组扩容。
3、HashMap 的扩容机制是怎样的?
⼀般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。 HashMap 的容量是有上限的,必须⼩于
sql短整型怎么表示1<<30,即 1073741824。如果容量超出了这个
数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。
JDK7 中的扩容机制
空参数的构造函数:以默认容量、默认负载因⼦、默认阈值初始化数组。内部数组是空数组。
有参构造函数:根据参数确定容量、负载因⼦、阈值等。
mysql面试题acid第⼀次 put 时会初始化数组,其容量变为不⼩于指定容量的 2 的幂数,然后根据负载因 ⼦确定阈值。
如果不是第⼀次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量 x 负载因⼦ 。 JDK8 的扩容机制
空参数的构造函数:实例化的 HashMap 默认内部数组是 null,即没有实例化。第⼀次 调⽤ put
⽅法时,则会开始第⼀次初始化扩容,长度为 16。
有参构造函数:⽤于指定容量。会根据指定的正整数到不⼩于指定容量的 2 的幂数,
将这个数设置赋值给阈值(threshold)。第⼀次调⽤ put ⽅法时,会将阈值赋值给容 量,然后让 阈值 = 容量 x 负载因⼦。
如果不是第⼀次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。(容量和阈值 都变为原来的 2 倍时,负载因⼦还是不变)。
此外还有⼏个细节需要注意:
⾸次 put 时,先会触发扩容(算是初始化),然后存⼊数据,然后判断是否需要扩容; - 不是⾸次
put,则不再初始化,直接存⼊数据,然后判断是否需要扩容;
4、ConcurrentHashMap 的存储结构是怎样的?
Java7 中 使⽤的分段锁,也就是每⼀个 上同时只有⼀ 个线程可以操作,每⼀个 Segment 都是⼀个类似 HashMap 数组的结构,它可以扩容, 它的冲突会转化为链表。但是 Segment 的个数⼀但初始化就不能改变,默认 Segment 的个数是 16 个。
Java8 中的 ConcurrnetHashMap 使⽤的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红 ⿊树,Node 是类似于⼀个 H
ashEntry 的结构。它的冲突再达到⼀定⼤⼩时会转化成红⿊树,在冲突⼩于⼀定数量时⼜退回链表。tcp ip 区别指什么
5、线程池⼤⼩如何设置?
CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N (CPU 核⼼数)+1,⽐ CPU 核⼼数多出来的⼀个线程是为了防⽌线程偶发的缺页中 断,或者其它原因导致的任务暂停⽽带来的影响。⼀旦任务暂停,CPU 就会处于空闲状 态,⽽在这种情况下多出来的⼀个线程就可以充分利⽤ CPU 的空闲时间。
I/O 密集型任务(2N): 这种任务应⽤起来,系统会⽤⼤部分的时间来处理 I/O 交互,⽽线 程在处理 I/O 的时间段内不会占⽤ CPU 来处理,这时就可以将 CPU 交出给其它线程使 ⽤。因此在 I/O 密集型任务的应⽤中,我们可以多配置⼀些线程,具体的计算⽅法是 2N。
如何判断是 CPU 密集任务还是 IO 密集任务?
CPU 密集型简单理解就是利⽤ CPU 计算能⼒的任务⽐如你在内存中对⼤量数据进⾏排序。 单凡涉及到⽹络读取,⽂件读取这类都是IO 密集型,这类任务的特点是 CPU 计算耗费时间 相⽐于等待 IO 操作完成的时间来说很少,⼤部分时间都花在了等待 IO 操作完成上。
6、IO 密集=Ncpu*2 是怎么计算出来?
I/O 密集型任务任务应⽤起来,系统会⽤⼤部分的时间来处理 I/O 交互,⽽线程在处理 I/O 的时间段内不会占⽤ CPU 来处理,这时就可以将 CPU 交出给其它线程使⽤。因此在 I/O 密集型任务的应⽤中,我们可以多配置⼀些线程。例如:数据库交互,⽂件上传下 载,⽹络传输等。IO 密集型,即该任务需要⼤量的 IO,即⼤量的阻塞,故需要多配置线 程数。
7、G1 收集器有哪些特点?
G1 的全称是 Garbage-First,意为垃圾优先,哪⼀块的垃圾最多就优先清理它。
G1 GC 最主要的设计⽬标是:将 STW 停顿的时间和分布,变成可预期且可配置的。 被视为 JDK1.7 中 HotSpot 虚拟机的⼀个重要进化特征。它具备⼀下特点:
ConcurrnetHashMap
Segment
并⾏与并发:G1 能充分利⽤ CPU、多核环境下的硬件优势,使⽤多个 CPU(CPU 或者 CPU 核⼼)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线 程执⾏的 GC 动作,G1 收集器仍然可以通过并发的⽅式让 java 程序继续执⾏。
分代收集:虽然 G1 可以不需要其他收集器配合就能独⽴管理整个 GC 堆,但是还是保留 了分代的概念。
css文字两端对齐实现方式空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算 法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另⼀个⼤优势,降低停顿时间是 G1 和 CMS 共 同的关注点,但 G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型,能让使⽤者明 确指定在⼀个长度为 M 毫秒的时间⽚段内。
G1 收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的 Region(这也就是它的名字 Garbage-First 的由来)
8、你有哪些⼿段来排查 OOM 的问题?
增加两个参数 -XX:+HeapDumpOnOutOfMemoryError - XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发⽣时⾃动dump 堆内存信息 到指定⽬录。
同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题⼤概出在什么区域。
使⽤ MAT ⼯具载⼊到 dump ⽂件,分析⼤对象的占⽤情况,⽐如 HashMap 做缓存未
清理,时间长了就会内存溢出,可以把改为弱引⽤。
9、请你谈谈 MySQL 事务隔离级别,MySQL 的默认隔离级别是什么?
为了达到事务的四⼤特性,数据库定义了 4 种不同的事务隔离级别:
READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许脏读,也就是可能读取
到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 只能读取到已经提交的数据。Oracle 等多数数 据库默认都是该级别 (不重复读),可以阻⽌脏读,但是幻读或不可重复读仍有可能发
⽣。
REPEATABLE-READ(可重复读):对同⼀字段的多次读取结果都是⼀致的,除⾮数据
是被本⾝事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
SERIALIZABLE(可串⾏化):最⾼的隔离级别,完全服从 ACID 的隔离级别。所有的
事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏
读、不可重复读以及幻读。
MySQL 默认采⽤的 REPEATABLE_READ 隔离级别。
10、可重复读解决了哪些问题?
可重复读的核⼼就是⼀致性读(consistent read);保证多次读取同⼀个数据时,其值都和事 务开始时候的内容是⼀致,禁⽌读取到别的事务未提交的数据,会造成幻读。
⽽事务更新数据的时候,只能⽤当前读。如果当前的记录的⾏锁被其他事务占⽤的话,就 需要进⼊锁等待。
查询只承认在事务启动前就已经提交完成的数据。
可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的
时候会有幻读。
11、对 SQL 慢查询会考虑哪些优化 ?
分析语句,是否加载了不必要的字段/数据。
分析 SQL 执⾏计划(explain extended),思考可能的优化点,是否命中索引等。 - 查看 SQL 涉及的表结构和索引信息。
如果 SQL 很复杂,优化 SQL 结构。
电脑编程好学吗17u按照可能的优化点执⾏表结构变更、增加索引、SQL 改写等操作。
查看优化后的执⾏时间和执⾏计划。
如果表数据量太⼤,考虑分表。
利⽤缓存,减少查询次数。
12、谈⼀谈缓存穿透、缓存击穿和缓存雪崩,以及解决办法?
缓存穿透
问题:⼤量并发查询不存在的 KEY,在缓存和数据库中都不存在,同时给缓存和数据库 带来压⼒。
原因:⼀般⽽⾔,缓存穿透有 2 种可能性:业务数据被误删,导致缓存和数据库中都没 有数据。恶意进⾏ ddos 攻击。
分析:为什么会多次透传呢?不存在 ⼀直为空,需要注意让缓存能够区分 KEY 不存在和 查询到⼀个空值。
解决办法:缓存空值的 KEY,这样第⼀次不存在也会被加载会记录,下次拿到有这个 KEY。Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在,如果布隆过滤器中没有查到 这个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果检测到是恶意攻 击,则拒绝进⾏服务。完全以缓存为准,使⽤延迟异步加载的策略(异步线程负责维护缓 存的数据,定期或根据条件触发更新),这样就不会触发更新。
缓存击穿
问题:某个 KEY 失效的时候,正好有⼤量并发请求访问这个 KEY。
分析:跟穿透其实很像,属于⽐较偶然的。
解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使⽤延迟异步加载的策
略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。 缓存雪崩
问题:当某⼀时刻发⽣⼤规模的缓存失效的情况,导致⼤量的请求⽆法获取数据,从⽽将 流量压⼒传导到数据库上,导致数据库压⼒过⼤甚⾄宕机。
原因:⼀般⽽⾔,缓存雪崩有 2 种可能性:⼤量的数据同⼀个时间失效:⽐如业务关系 强相关的数据要求同时失效 Redis 宕机
分析:⼀般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存 数据同⼀个时间点⼤规模不可⽤,或者都更新。所以,需要我们的更新策略要在时间上合 适,数据要均匀分享,缓存服务器要多台⾼可⽤。
解决办法:更新策略在时间上做到⽐较平均。如果数据需要同⼀时间失效,可以给这批数 据加上⼀些随机值,使得这批数据不要在同⼀个时间过期,降低数据库的压⼒。使⽤的热 数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现⾼可⽤。做好主从的部署,当主节点挂掉后,能快速的使⽤从结点顶上。实现熔断限流机制,对系统进⾏负 载能⼒控制。对于⾮核⼼功能的业务,拒绝其请求,只允许核⼼功能业务访问数据库获取 数据。服务降价:提供默认返回值,或简单的提⽰信息。
13、LRU 是什么?如何实现?
最近最少使⽤策略 LRU(Least Recently Used)是⼀种缓存淘汰算法,是⼀种缓存淘汰机 制。
使⽤双向链表实现的队列,队列的最⼤容量为缓存的⼤⼩。在使⽤过程中,把最近使⽤的
页⾯移动到队列头,最近没有使⽤的页⾯将被放在队列尾的位置
使⽤⼀个哈希表,把页号作为键,把缓存在队列中的节点的地址作为值,只需要把这个页
对应的节点移动到队列的前⾯,如果需要的页⾯在内存中,此时需要把这个页⾯加载到内
存中,简单的说,就是将⼀个新节点添加到队列前⾯,并在哈希表中跟新相应的节点地
址,如果队列是满的,那么就从队尾移除⼀个节点,并将新节点添加到队列的前⾯。
14、什么是堆内存?参数如何设置?
堆内存是指由程序代码⾃由分配的内存,与栈内存作区分。
在 Java 中,堆内存主要⽤于分配对象的存储空间,只要拿到对象引⽤,所有线程都可 以访问堆内存。
-Xmx, 指定最⼤堆内存。 如 -Xmx4g. 这只是限制了 Heap 部分的最⼤值为 4g。这个内
存不包括栈内存,也不包括堆外使⽤的内存。
-Xms, 指定堆内存空间的初始⼤⼩。 如 -Xms4g。 ⽽且指定的内存⼤⼩,并不是操作系
统实际分配的初始值,⽽是 GC 先规划好,⽤到才分配。 专⽤服务器上需要保持 –Xms 和 –Xmx ⼀致,否则应⽤刚启动可能就有好⼏个 FullGC。当两者配置不⼀致时,堆内存 扩容可能会导致性能抖动。
-Xmn, 等价于 -XX:NewSize,使⽤ G1 垃圾收集器 不应该 设置该选项,在其他的某些 业务场景下可以设置。官⽅建议设置为 -Xmx 的 1/2 ~ 1/4.
-XX:MaxPermSize=size, 这是 JDK1.7 之前使⽤的。Java8 默认允许的 Meta 空间⽆ 限⼤,此参数⽆效。
-XX:MaxMetaspaceSize=size, Java8 默认不限制 Meta 空间, ⼀般不允许设置该选 项。
-XX:MaxDirectMemorySize=size,系统可以使⽤的最⼤堆外内存,这个参数跟 - Dsun.nio.MaxDirectMemorySize 效果相同。
-Xss, 设置每个线程栈的字节数。 例如 -Xss1m 指定线程栈为 1MB,与- XX:ThreadStackSize=1m 等价
15、栈和队列,举个使⽤场景例⼦?
栈(后进先出)可以⽤于字符匹配,数据反转等场景
队列(先进先出)可以⽤于任务队列,共享打印机等场景
16、MySQL 为什么 InnoDB 是默认引擎?
聚集索引是指数据库表⾏中数据的物理顺序与键值的逻辑(索引)顺序相同。⼀个表只能有⼀ 个聚簇索引,因为⼀个表的物理顺序只有⼀种情况,所以,对应的聚簇索引只能有⼀个。聚簇 索引的叶⼦节点就是数据节点,既存储索引值,⼜在叶⼦节点存储⾏数据。
Innodb 创建表后⽣成的⽂件有:
firefightingfrm:创建表的语句 idb:表⾥⾯的数据+索引⽂件
17、MySQL 索引底层结构为什么使⽤ B+树?
哈希虽然能够提供 O(1) 的单数据⾏操作性能,但是对于范围查询和排序却⽆法很好地⽀ 持,最终导致全表扫描;B 树能够在⾮叶节⼦点中存储数据,但是这也导致在查询连续数 据时可能会带来更多的随机 I/O,⽽ B+树的所有叶节点可以通过指针相互连接,能够减 少顺序遍历时产⽣的额外随机 I/O;
第⼀,B 树⼀个节点⾥存的是数据,⽽ B+树存储的是索引(地址),所以 B 树⾥⼀个节 点存不了很多个数据,但是 B+树⼀个节点能存很多索引,B+树叶⼦节点存所有的数据。
第⼆,B+树的叶⼦节点是数据阶段⽤了⼀个链表串联起来,便于范围查。 18、B+ 树的叶⼦节点链表是单向还是双向?
双向链表
19、MVCC 是什么?它的底层原理是什么?
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从⽽提⾼并 发性能的⼀种机制。
事务版本号
表的隐藏列
undo log - read view
20、undo log 具体怎么回滚事务 ?
举个例⼦:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论