Java面试宝典产生自我准备面试的过程,当时网上找了些试题,但是没答案,然后一边梳理自己的知识,一般结合自己平时钻研的知识和工作经验,自己对试题进行了整理,解答。
也凭借着这次梳理,面试一路过关斩将,offer收割率100%。面了头条,和美团的3个事业部,均收获offer。
也预祝各位校招的朋友,斩获满意的offer
一:Java基础
1. String, Stringbuffer, StringBuilder 的区别。
答:String 是 final 类,⽆法继承,也⽆法被修改,每次修改都会创建新的 String 对象。
Stringbuffer 和 StringBuilder 则能正常被修改,两者的区别是 StringBuffer 每个方法都加了锁,是线程安全的。
2. JAVA8 的 ConcurrentHashMap 为什么放弃了了分段锁,有什么问题吗,如果你来设计,你如何设计。
答:在 1.8 之前, ConcurrentHashMap 通过一个大⼩为 16 的 Segment 数组,这 16 个Segement 继承自 ReenterLock,类似 16 把锁均匀的维护着所有的桶。每次写操作将会锁住 1 个 segment 下所有的桶,锁力度较大,会降低支持的并发数。所以 1.8 进行了优化,采用了更细力度的锁, hash 后,如果没有相关的桶,不加锁,直接通过 Unsafe 的类似 CAS 操作将值放入,如果有桶,则对这个桶加锁,其他桶的节点依然可以正常操作
3. 继承和聚合的区别在哪。
答:继承主要描述的是‘A is B’的关系,而聚合描述的是‘A has B’的关系,一般情况下,优先使用聚合,因为继承可能会继承父类中一些不必要的属性和方法。但是,如果需要向上转型,就需要用继承。
4. 反射的原理,反射创建类实例的三种方式是什么。
答:当⼀个类加载完成后,会生成一个 Class 对象, Class 对象可以获取类的所有属性,⽅法等数据。
⽅式一:对象调用 getClass()方法
⽅式二:类名.class
⽅式三:Class.forName(全路径名称)
创建 class 对象后,直接调用 class 对象的 newInstance()方法即可创建实例
二:Jvm
1. Jvm 包括那⼏大部分。
答:主要包含 4 个部分:类加载器,字节码执行引擎,内存模型,本地方法调⽤。
2. 什么情况下会发生栈内存溢出。
答:一般在递归调用的时候容易发生。
虚拟机栈描述的是 Java 方法执行的内存模型,每个⽅法的执行都会创建一个栈帧,用于保存局部变量表, 操作数栈,动态链接,方法出口等信息。
如果请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverFlowError,如果无法申请所需的内存,则会抛出 OutOfMemoryError。
3. JVM 内存为什么要分成新生代,⽼年代,持久代。新生代中为什么要分为 Eden 和Survivor。
答:分为新生代,⽼年年代和持久代是为了不同的区域根据特点采用不同的 GC 策略,例如新生代对象⼀般是朝⽣夕死,一般采⽤复制算法,区分进一步分为一个 Eden 和两个Survivor 区域。而老年代和持久带则一般采⽤标记清除算法。
4. 详细介绍下 CMS 垃圾回收器器。
答:CMS 是⽼年代垃圾回收器,⽬标是最短的停顿时间。它的⼯作主要包括 4 个阶段:
(1)初始化标记,主要是标记老年年代中被 GCroots 和新生代引用的对象,需要 STW
(2)并发标记:从第一步对象开始,并发的完成标记
(3)重新标记:因为第二步是并发完成的,过程中对象引用可能发生变化,这一步主要是保证实际清理前标记是正确的,需要 STW
(4)并发清除:对标记对象并发进行清除
第一步和第三部虽然依然需要 STW,但完成的工作较简单,较少,时间较短;最耗时的完成标记过程和清除过程都是并发进行的,所以 CMS 能控制较短的停顿时间。
三:开源框架知识
1. 简单介绍 Spring 加载流程。
答:资源的定位(Resource) -> bean 的解析(BeanDefinition) -> bean 的注册
(conncurrentHashMap)
2. Spring AOP 的实现原理?解释几个 AOP 相关的专业名词?
答:原理:通过 IOC 和动态***, aop 本质上是对被*** bean 的增强,在 getBean()获取 bean的时候,初始化 bean 完成后会有一些后处理流程, aop 就是在这里实现的,如果发现bean 有相关联的 advisior 或者 Interceptor,就会通过动态***对其进行增强。
专业术语:通知(Advice):切面的工作,切面是什么时使用, before,after,after-returning,afterthrowing,around;连接点(JoinPoint):应用中能插入切面的一个点;切点(PointCut):一个或多个连接点
3. 讲讲 Spring 事务的传播属性。
答:spring 事务传播属性主要作用是处理事务⽅法被另一个事务方法调用的时候, spring如何处理这些事务的行为。例如默认的传播属性 PROPAGATION_REQUIRED,就是如果当前有事务,则加⼊入当前事务,如果没有则新建一个事务。Spring 在 TransactionDefinition 中定义了事务的 7 种传播属性和 5 种隔离级别
4. Spring 为什么把 bean 设计成默认单例的?这样设计有什么好处和坏处
答:(1)避免频繁的创建实例,减少开销,提升性能;(2) 避免频繁创建对象导致 OOM 或者频繁的 GC; (3)可以充分利⽤缓存,加快获取速度
它的劣势也比较明显,因为⼤家共⽤一个 bean,所以多线程环境下可能出现线程安全问题
四:操作系统
1. Linux 下 IO 模型有几种,各⾃的含义是什么。
答:5 中 IO 模型:阻塞 IO 模型,⾮阻塞 IO 模型, IO 复用模型,信号驱动 IO,异步 IO
阻塞 IO:进程一直阻塞,直到数据拷贝完成。
⾮阻塞 IO:通过反复调用,内核如果数据没准备好,会立即返回失败,上游决定继续重试,直到准备好。数据拷贝过程依然是阻塞的。
IO 复用模型:使用 select, poll 或者 epoll,依然会阻塞,但是可以同时监听多个 IO 端口,哪一路路准备好了,就优先处理哪个。
信号驱动 IO:⽴即返回,当数据准备好了,向调用进程发送一个信号。
异步 IO:数据准备好后,内核完成后通过回调函数通知用户进程
2. 平时⽤用到哪些 Linux 命令。
答:cd, mkdir ,touch, cp, vi, cat, netstat, kil, top
浏览⽂文件:cat more less
看⽇志:tail -n100 -f cantina.log
wc -l(看文件行数) -c(看文件的字节数)
top 看负载, cpu
df du 看磁盘使用率。df -h du -d1 -h /user
iostat -d -k 看磁盘 IO
free -m 看内存使⽤用情况
3. 介绍下你理理解的操作系统中线程切换过程。
答:进程切换一般发⽣于中断,异常或者系统调用的时候。此时,
(1) 被中断的进程 A 保存当前的上下⽂信息,然后挂起,修改线程状态,进入
相关进程队列。
(2) 恢复 B 进程的上下文信息,分配 cpu 时间片进行处理
4. 进程和线程的区别。
答:进程操作系统资源分配的基本单位,线程是任务调度执行的最小单位。一般一个进程会包含多个线程,线程是轻量级的进程。
开销:进程有⾃己独立的代码和内存空间,切换开销较大,⽽多个线程共用进程的代码和数据空间,每个线程有⾃己独立的线程栈和程序计数器。
五:多线程
1. 多线程的几种实现方式,什么是线程安全。
答:通过继承 Thread, 或者实现 Runnable 接⼝
也可以通过线程池的⽅式,实现 Callable 接⼝
当多个线程访问某个类时,不需要采取额外的同步措施,这个类依然能表现出正确的行为,这个类就是线程安全的。
2. volatile 的原理,作用,能代替锁么。
答:volatile 修饰的变量在写数据的时候, JVM 会自动向处理器加一个 lock 指令,处理器收到 lock 指令后会将新修改的值立即回写到主内存,同时导致此变量在其他工作内存的值失效。
可以用作轻量级的同步方式,它能保证数据在各个线程的可⻅性,也能避免指令重排序。但是 volitaleb 并不能代替锁, volatile 虽然能保证可见性,如果对 volatile 变量的操作不依赖当前值,那就没问题,但如果依赖,那就是多步操作,例如 i++,依然需要加锁。
3. sleep 和 wait 的区别。
答:sleep 是 Thread 类中的静态方法,⽽ wait 是 Object 中定义的普通方法。两个方法都会释放 cpu 资源并使线程进入 waiting 状态,但是 sleep 不会释放锁, ⽽ wait会释放锁。
4. Lock 与 Synchronized 的区别 。
答:第一,实现⽅式不一样。synchronized 是通过字节码层⾯面进行⽀持的,⽽ lock 底层是通过
AbstractQueuedSynchronizer 实现。 lock 加锁的⽅方式,以 UnfairLock 为例例,就是尝试去 setState,如果成功就给 state 加 1,如果失败,就排队到队尾,等待锁持有者的唤醒。 释放锁就是给 state 减 1。
第二, Lock 相⽐比较于 synchronized 功能更加的丰富。例如 trylock 尝试去加锁,带过期时间的加锁,公平锁和非公平锁等。
六:网络知识
1. http1.0 和 http1.1 有什么区别。
答:(1)http1.1 ⽀持长链接,通过请求头的 keep-alive,1.0 则是短链接的,每次请求都会建⽴ tcp 连接。
(2)增加 host 字段,之前认为每台服务器都有一个唯一的 ip,但随着虚拟技术的发展,一个服务器上可以存在多个虚拟主机,它们共享一个 ip 地址。
(3)新增状态码 100,客户端先发一个不带内容的请求头,如果服务器接受就返回 100,然后客户端在继续其他请求,⽤于试探服务器端是否接收请求,还节省带宽。
(4) 引⼊了了 Chunked transfer-coding 来解决上面这个问题,发送方将消息分割成若干个任意⼤小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允许发送方只缓冲消息的一个⽚片段,避免缓冲整个消息带来的过载。
(5) 在 1.0 的基础上加⼊入了一些 *** 的新特性,当缓存对象的 Age 超过 Expire 时变为stale 对象, *** 不需要直接抛弃 stale 对象,⽽是与源服务器进行重新激活
2. TCP 三次握手和四次挥手的流程,为什么建立连接要3次,2次不行,而断开连接要 4 次
握手需要 3 次,是因为 tcp 是双向通信协议, 2 次握⼿手只允许一方建立连接,而另一方则承认它, 这意味着只有一方可以发送数据。所以需要 3 次握手达到双方互相确认可以发和接收数据。
建立连接的 3 次握手的第二次信号,同时发送了 ack 和 syn,所以相对于断开连接少 1 次发送流程。当客服端向服务端发送断开 FIN 请求时,表示客户端不会再向服务端发送数据了,但是客户端可能还有数据没接收完,服务端还需要继续向客户端发送剩余的数据,当服务端也没数据发送给客户端时,服务端在发送断开请求,客户端进行确认即可,所以是4 次握⼿手。
3. 说说你知道的几种 HTTP 响应码,比如 200, 302, 404。
答:2**一般指请求成功, 例如 200 成功。
3**指重定向, 301 永久移动, 302 临时移动。
4**指请求错误, 404 未找到, 403 禁止。
5**指服务器异常, 500 服务器内部错误, 503 服务不可⽤。
七:架构设计与分布式
1. 分布式集群下如何做到唯一序列号。
答:uuid -> segment 获取号段方案 -> segment+双 *** -> snowflake。详细的说明可以查看美团技术公众号对应文章
2. 如何使用 redis 和 zookeeper 实现分布式锁?有什么区别优缺点,会有什么问题,分别适用什么场景。
答:简单的 redis 分布式锁可以通过 setnx 命名,对同一个 key 去 setnx 1,因为只有一个会成功,所以就达到了加锁的目的,但是这会有个问题,就是因为 value 都是 1,所有就有可能锁会被其他客户端释放,所以一般采取的措施就是 value 是随机数或者时间戳。但这个仅是和单机的 redis,如果 redis 是集群,主的数据还没来得及同步到从,主挂了,那么锁就失效了,其他客户端就能再次获取锁了。
所以 redis 作者提出了 redlock,客户端向多个节点申请锁,每个节点设置远小于锁过期时间的等待时间,当成功的节点个数⼤于一半,则获取成功。但这和上面单节点一样会有问题,有节点发生崩溃或者有节点阻塞导致过期等都同样会有问题
3. REST 和 RPC 异同?
答:(1) ⾯向的对象不同:REST 是⾯向资源的,而 RPC 是⾯向服务,⾯向方法的
(2) 所属类别不不同:REST 主要是用在 http 中,而 RPC 主要是远程调⽤
4. Zk 怎么保证多客户端同时创建节点,只有一个创建成功。
答:通过查看 zk 的源代码可以发现, createNode 的实现会先根据节点的 path 获取上级路径的父节点,并用⽗节点对后续的创建节点操作进行加锁。当父节点 getchildren 包含当前节点时,直接失败。
八:数据库知识
1. Mysql MyIsam 和 InnoDB 引擎索引结构有什什么区别。
2. 数据库隔离级别有哪些,各自的含义是什么, MYSQL 默认的隔离级别是什么。
答:隔离级别有 4 种
未提交读:最低级别,只保证不读取物理损坏的数据
提交读:可以避免脏读
可重复读:默认级别,能够避免脏读和不可重复读
可序列化:最⾼级别,可以避免脏读,不可重复读和幻读。
3. 什么是幻读。
答:一个事务按照相同条件读取以前读取过的数据,发现其他事务插入了满足条件的数据。避免幻读一般采取的措施是采用间隙锁。 不可重复读关注的是数据被其他事务修改,而幻读关注的是其他事务插入了新的符合条件的数据。
4. Mysql 的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化。
答:索引使用 B+树对数据进行快速的检索。
B+树索引, hash 索引,前缀索引,全文索引等等。
创建合理理的索引:(1)首先你得清楚你业务中的使用场景,哪些字段会被经常用做检索条件,然后考虑字段的离散程度
(2) 根据需要适当的建立索引,不要过度,索引不是越多越好
(3) ⻓字段可以考虑前缀索引,多字段可以考虑建立联合索引
(4) InnoDB 还可以充分利用聚集索引
索引的优化我认为主要是优化有索引但没用上的情况:
or 的每个条件都必须有索引,不然不会⽤索引;
是否是复合索引的前列;
Like 模糊查询模糊匹配%放在最前面;
字符串没加引号导致的隐式类型转换
九:消息队列
1. 消息队列的使用场景。
答:消息队列一般用于系统间的解耦, 例如订单组发生订单相关的各类 mq 消息,关心订单操作的系统自行申请相关的 mq 即可。
异步处理, 相比较于 rpc 的同步调⽤,需要等待结果返回, mq 是异步处理的,例如库存的扣减主流程仅仅依赖 redis,而扣减 DB 可以通过 mq 实现最终一致性。
瞬时流量的平滑削峰处理,对于瞬时的大流量,可以将其放⼊ mq 中,消费端不断拉取任务进⾏处理,做到平滑削峰。
2. 消息的重发,补充策略。
答:消费端消费成功后都会给予消息中间件确认消息,中间件即可将相关的消息删除。但是由于网络的不可靠或者其他因素,消息中间件为了保证消息的一定送达,一般会采取各种重发措施,一般常见的措施有超时重传,消息确认机制等。
3. MQ 系统的数据如何保证不丢失。
答:各大消息中间件采取的措施其实⼤同小异,互相借鉴,以 Kafka 为例。
发送端一般是通过 ACK 应答机制,当 kafka 接收到消息后,就会发生应答消息,可以配置是不需要应答,还是 leader 应答, 还是所有的 follower 都完成再应答。
消息队列一般是通过持久化到文件系统,节点的主从机制,已经主节点的***等机制。消费端则一般是通过应答机制,以及超时重传来保证消息的可靠性的。 Kafka 通过位点来控制数据的不丢失
十:缓存
1. 如何防止缓存击穿和雪崩。
答:(1)缓存穿透:指的是当缓存查不到的时候,去查数据库。当有人⽤大量量的不存在的key 去恶意查你的缓存时候,就会有⼤量的请求打到数据库,对数据库造成压力。一般可以采取的措施是,查询缓存前在增加一层过滤,例如通过 bitmap。
(2)缓存击穿:指个别被高频访问的热点 sku,当这些热点 sku 过期了,大量的请求就会打到数据库。可以考虑采取加锁,或者设置更⻓的过期时间,甚⾄不过期。
(3)缓存雪崩:指大⾯积的 key 同时过期,请求全部请求到了 DB。采取的办法可以 key 的过期时间增加一个随机数。
其他还可以采取的措施,可以单机通过 Guava 做防刷。其实前面问题的关键都是对数据库造成压力,所以可以在数据库端做好限流或者分布式锁。
2. Redis 的数据结构都有哪些,各自都适合什么样的场景。
答:String, list, hash, set, zset
String 应用最广泛,适合各种 key-value 数据的存储,List 底层是双向链表,适合用作列表,队列等
Hash 适合对象的存储
Set 适合去充场景下集合的存储, set 还提供交并差集⽀支持
Zset 是有序的 set,可以⽤来实现 PriorityQueue
3. Redis 的使用要注意什么
答:(1)冷热数据分开存储,不同业务数据分开存储
(2)规范 key 的命名,根据不同业务设置合适的命名空间
(3)注意垃圾回收, key 设置合适的过期时间
(4)⼤文本数据可以先进行压缩
(5)hash, set 的 key 的 field 不应太多,可以根据业务多用几个 hash 和 set
(6)设计 sharding 机制
4. redis 和 mem***d 的区别。
答:redis 和 mem***d 都能很好的适用缓存,它们使用内存进⾏行数据的存储。主要有如下⼏个区别:
(1)⽀持的数据类型:mem***d 仅支持简单的 key-value,而 redis ⽀持更丰富
(2)线程模型:redis 是单线程的, mem ⽀持多线程,数据量大时, mem 具有一定的优势
(3)持久化:mem 纯内存的, 断电,啥都没了了。redis ⽀支持 rdb 或者 aof 两种⽅式的持久化
(4)内存管理理:mem 将内存划分成一块块固定⼤小的 chunk 内存块,尺⼨寸相同的块组成slab class,当存储数据时,找一个最合适的 chunk 进行存储,可能会产⽣生碎⽚片,内存利⽤率不高。 ⽽ redis 通过对 C 语⾔言的 malloc/free 进行包装,将申请的内存块的⼤小放置在内存块的头部区域,做到精准的内存申请和释放。同时,当内存不不够的时候, mem 采⽤用LRU 进⾏删除,而 redis 除了 LRU 还可以使⽤虚拟内存将部分 value 转移到磁盘中, key依然保存在内存中。
(5)分布式:mem 本身不支持分布式,得⾃己在客户端实现。而 3.0 版本开始, redis 原⽣⽀持集群
如果本文对你有帮助,别忘记给我个3连 ,点赞,转发,评论,