项目介绍
大部分情况,这是一场面试的开门题,面试官问这个问题,主要是考察你的概述能力和全局视野。有的人经常抱怨自己每天在堆业务,但没有成长。事实上,很多情况下确实在堆业务,但并不是没有成长的。并非做中间件或者技术架构才是成长,例如我们的需求分析能力,沟通协作能力,产品思维能力,抽象建模能力等都是一个非常重要的硬实力。
Java基础
1、List 和 Set 的区别
1:List:可重复,有序,数组或者链表存储
2:Set:不可重复,无序,使用map存储
2、HashSet 是如何保证不重复的
(1)如果hash码值(hashcode())相同,且equles判断相等,说明元素已经存在,不存;
(2)如果hash码值(hashcode())相同,且equles判断不相等,说明元素不存在,存;
3、HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?
1. 对象不能安全发布,构造过程逃逸;
2. 内存的可见性,内容不能及时发布;
3. 操作不是原子的;
4. 读写不能同步;
5. 存在死锁的可能性;
4、HashMap 的扩容过程
HashMap的初始容量都是2的n次幂的形式存在的,而扩容也是2倍的原来的容量进行扩容
5、HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?
(1) 1.7的jdk采用Entry数组来存储数据,但是entry是链表结构。在数据量大的情况下,put/get操作时间会略长。
(2) 1.8的jdk采用node数组存储数据,node采用链表+红黑树结构,那么当数据存储在数组格子里,且这个格子key超过8个,会把链表转换为红黑树。那么根据红黑树的特性,在大数据量put/get会跟快。
6、HashMap实现原理 ?
HashMap是基于hashing的原理,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
hashmap原理:
操作put时,初始化好并查询链表长度是否需要扩容(扩容2倍),在根据key求hash值在计算它的下标做存储。 如果链表长度超过阈值,就会扩容成红黑树。
操作get时,使用键的hashcode求取bucket位置,在找到节点并返回。
ConcurrentHashMap(安全):
(1)JDK1.7里面,ConcurrentHashMap是用Segment和HashEntry实现的,每个Segment都是继承于Reentrantlock的,在对该segment进行操作时,获取锁,结束操作释放锁。
(2)JDK1.8里面,没有用segment,而是用Node+CAS+synchronized实现的。
HashMap(不安全,存在死锁可能性):默认16,扩容是2倍,一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。
(1) 1.7的jdk采用Entry数组来存储数据,但是entry是链表结构。在数据量大的情况下,put/get操作时间会略长。
(2) 1.8的jdk采用node数组存储数据,node采用链表+红黑树结构,那么当数据存储在数组格子里,且这个格子key超过8个,会把链表转换为红黑树。那么根据红黑树的特性,在大数据量put/get会跟快。
7、HashMap 的并发问题?
HashMap在多线程put后可能导致get无限循环 。
如果扩容前相邻的两个Entry在扩容后还是分配到相同的table位置上,就会出现死循环的BUG。
解决方案:
使用ConcurrentHashMap进行替代
8、Java反射
java 获取反射常使用的三种方式:
1.通过new对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通过路径-相对路径)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
Java 并发
1、synchronized 的实现原理以及锁优化?
synchronized 常见的三种用法如下:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
synchronized的对象锁,其指针指向的是一个monitor对象(由C++实现)的起始地址。每个对象实例都会有一个 monitor。其中monitor可以与对象一起创建、销毁;亦或者当线程试图获取对象锁时自动生成。需要注意的是monitor不是Java特有的概念.
实现原理:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
2、volatile 的实现原理?
1.保证共享变量可见性
通俗来说就是,某个线程对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
2.禁止指令重排序
volatile底层通过内存屏障实现禁止特定类型的处理器重排序
原理:操作发送一个lock前缀的命令并写到系统内存,当其他线程来获取,效验这个lock,不对就重新获取值。(类似版本锁)
3、Java 的信号灯?
Semaphore是Java1.5之后提供的一种同步工具,Semaphore可以维护访问自身线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而release() 释放一个许可。
4、synchronized 在静态方法和普通方法的区别?
synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;
synchronized修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。
5、怎么实现所有线程在等待某个事件的发生才会去执行?
1.使用读写锁,刚开始主线程先获取写锁,然后所有子线程获取读锁,然后等事件发生时主线程释放写锁;
2.使用CountDownLatch,初始值设为1,所有子线程调用await方法等待,等事件发生时调用countDown方法计数减为0;
6、CAS?CAS 有什么缺陷,如何解决?
Cas是另一个无锁解决方案,更准确的是采用乐观锁技术,实现线程安全的问题。cas有三个操作数----内存对象(V)、预期原值(A)、新值(B)。
CAS缺点这个方式也存在一定的问题:
1、自循环时间长,开销大
2、只能保证一个共享变量的原子操作
3、ABA问题
7、synchronized 和 lock 有什么区别?
1.Lock是一个接口,而synchronized是关键字。
2.synchronized会自动释放锁,而Lock必须手动释放锁。
3.Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
4.通过Lock可以知道线程有没有拿到锁,而synchronized不能。
5.synchronized能锁住类、方法和代码块,而Lock是块范围内的
8、final finally finalize
final 表示最终的、不可改变的。用于修饰类、方法和变量。
finally 异常处理的一部分,它只能用在try/catch语句中,表示希望finally语句块中的代码最后一定被执行(但是不一定会被执行)
finalize()是在java.lang.Object里定义的,Object的finalize方法什么都不做,对象被回收时finalized方法会被调用。
特殊情况下,可重写finalize方法,当对象被回收的时候释放一些资源。但注意,要调用super.finalize()。
9、强引用 、软引用、 弱引用、虚引用
强引用: 是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
软引用: 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;
弱引用: 与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期
虚引用: 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期
image.png
10、ConcurrenHashMap 介绍?1.8 中为什么要用红黑树?
ConcurrenHashMap 是当hash值相同的情况下(大于8),才会采用红黑树,利用红黑树的二分查找法进行定位。性能较高。
11、ThreadLocal原理,用的时候需要注意什么?
ThreadLocal是并发场景下用来解决变量共享问题的类,它能使原本线程间共享的对象进行线程隔离,即一个对象只对一个线程可见。
需要注意的是,threadlocal在多线程下并不是线程安全的。
12、如何检测死锁?怎么预防死锁?
通过jvisualvm查看产生的死锁线程,一步步排查。
预防死锁可以设置加锁顺序,设置加锁时限等方法。
13、Java 内存模型?
程序计数器
堆
栈
方法区
14、线程池的种类,区别和使用场景?
四种线程池:
1. newCachedThreadPool(缓存)
2. newFixedThreadPool(单线程)
2. newScheduledThreadPool(定长)
4.newSingleThreadExecutor(延迟)
Spring
1、BeanFactory 和 FactoryBean?
BeanFactory只是个接口,负责生产和管理bean,它为其他具体的IOC容器提供了最基本的规范。
FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。
2、Spring IOC 的理解,其初始化过程?
ioc即依赖注入,就是把某某托管给这个工厂去创建,并实现。
初始化流程:
1.Resource定位,读取xml获取对应加载类的路径获取主要注入的接口类。
2.加载到spring的容器里。
3.注册对应的接口类,并存到容器里。(map里)
3、BeanFactory 和 ApplicationContext?
BeanFactory只是个接口,负责生产和管理bean,它为其他具体的IOC容器提供了最基本的规范。
ApplicationContext是负责加载上下文或读取资源文件的类,或者通过他去实例化bean。
4、Spring Bean 的生命周期,如何被管理的?
1.创建bean
2.set方法注入
3.调用init方法bean初始化
4.执行初始化方法
5.销毁
5、如果要你实现Spring AOP,请问怎么实现?
6、如果要你实现Spring IOC,你会注意哪些问题?
7、Spring 是如何管理事务的,事务管理机制?
Spring事务管理模块主要包括3个接口:
PlatformTransactionManager:事务管理器,主要用于平台相关的事务管理。
TransactionDefinition:事务定义信息(隔离级别、传播、超时、只读)通过配置如何进行事务管理。
TransactionStatus:事务具体运行状态——事务管理过程中,每个时间点事务的状态信息。
主要通过这三个接口来实现aop切面,对事务做管理。
8、Spring 的不同事务传播行为有哪些,干什么用的?
这里写图片描述
9、Spring 中用到了那些设计模式?
工厂,单例,模型,原型,适配器,策略,装饰者。
Springboot
boot如何自动装配和启动流程是怎么样的?
首先启动会先找到run方法,run方法会找到SpringBootApplication注解,里面包含三个注解,然后简单说一下每个注解的作用.
1.SpringApplication.run 执行流程中有refreshContext(context),内部会解析我们的配置类上的标签,实现自动装配功能的注解@EnableAntoConfiguration
2.会解析@EnableAntoConfiguration这个注解里面的@Import引入的配置类@AntoConfigurationImportSelector
3.@AntoConfigurationImportSelector这个类中有方法SpringFactoriesLoder.loadFactoryNames(getStringFactoriesLoaderFactoryClass(),getBeanClasLoader());作用就是读取jarbao中的项目中的META-INF/spring.factories文件
4.spring.factories 配置了要自动装配的 Configuration 类。
什么是 Spring Boot Stater ?
starter负责配置好与spring整合相关的配置和相关依赖(jar和jar版本),使用者无需关心框架整合带来的问题。
Netty
1、BIO、NIO和AIO
2、Netty 的各大组件
3、Netty的线程模型
4、TCP 粘包/拆包的原因及解决方法
5、了解哪几种序列化协议?包括使用场景和如何去选择
6、Netty的零拷贝实现
7、Netty的高性能表现在哪些方面
分布式相关
1、Dubbo的底层实现原理和机制
Dubbo :是一个rpc框架,soa框架
作为RPC:支持各种传输协议,如dubbo,hession,json,fastjson,底层采用mina,netty长连接进行传输!典型的provider和cusomer模式!
作为SOA:具有服务治理功能,提供服务的注册和发现!用zookeeper实现注册中心!启动时候服务端会把所有接口注册到注册中心,并且订阅configurators,服务消费端订阅provide,configurators,routers,订阅变更时,zk会推送providers,configuators,routers,启动时注册长连接,进行通讯!proveider和provider启动后,后台启动定时器,发送统计数据到monitor!提供各种容错机制和负载均衡策略!!
Dubbo的实现:
Dubbo协议的Invoker转为Exporter发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接收客户端发来的各种请求,通讯细节由Dubbo自己实现。
dubbo原理:
I.初始化过程
2.解析服务
3.暴露服务
4.引用服务
dubbo的泛化调用:泛化接口调用方式主要用于客户端没有 API 接口(泛化调用是指不需要依赖服务的二方包)及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,API网关比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。泛化调用的方式可以有效减少平台型产品的二方包依赖,实现系统的轻量级运行。
2、描述一个服务从发布到被消费的详细过程
首先先获取zk的配置信息,然后获取需要生产者暴露的url,然后调用registry.register方法将url注册到zookeeper上去,然后去消费者配置对应配置,根据端口和接口到注册中心找到生产者接口,然后去调用。
3、分布式系统怎么做服务治理
服务治理解决方案:负载均衡,熔断降级限流,链路追踪,服务发现与配置中心,连接复用
服务治理的痛点:超时配置,线程池配置,故障定位
4、幂等性的概念
一个操作重复执行多次,其效果(不考虑操作时间)和只执行一次是一样的,那么这个操作就叫做是幂等
5、消息中间件如何解决消息丢失问题
以rabbitmq为例:
消息持久化:RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。
需要做到消息持久化,以下三个条件缺一不可。
Exchange设置持久化
Queue设置持久化
Message设置持久化
ack确认机制:就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
RabbitMQ消息丢失解决方案:
第一步:设置创建queue设置为持久化(只会持久化queue元数据,但不会持久化queue中的数据);
第二步:发送消息时,设置deliveryMode设置为2(此时queue中的数据也将被持久化);
第三步:配合confirm机制使用(推荐使用异步监听机制,不推荐事务机制),当消息被持久化后才通知生产者ack;
RabbitMQ消费者丢失数据场景:
消费者接收到消息,但是还没操作,消费者自己挂了,但是rabbitmq以为该条消息已经被消费.
消费者丢失数据解决方案:
将autoAck关闭,在处理完消息后再手动提交ack;
6、Dubbo的服务请求失败怎么处理
重连或者配置超时请求
7、重连机制会不会造成错误
如果在高性能请求的环境下,会导致服务雪崩。
8、对分布式事务的理解
分布式事务就是指事务的参与者、支持事务的服务器分布于不同服务器节点上,要保证这些小操作要么全部成功,要么全部失败
9、如何实现负载均衡,有哪些算法可以实现?
轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法
10、Zookeeper的用途,选举的原理是什么?
zk的用途:1.命名服务 2.配置管理 3.集群管理 4.分布式锁
选举原理:当leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的Server都恢复到一个正确的状态。
1.选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
2.选举线程首先向所有Server发起一次询问(包括自己);
3.举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
4. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
5.线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。
11、数据的垂直拆分水平拆分。
垂直拆分:把一个拥有很多列的表拆分为多个表,原则根据使用度的区分,根据业务区分,根据复杂类型区分
水平拆分:指按照业务对系统进行划分 。
12、zookeeper原理和适用场景
zookeeper基本原理:树形层次结构,树中的节点称为 Znode, 每个 Znode 存储的数据有小于 1m 的大小限制。zookeeper 对 Znode 提供了几种类型:临时 Znode、持久 Znode、顺序 node 等几种类型,用于不同的一致性需求。在 Znode 发生变化时,通过“观察”(watch)机制可以让客户端得到通知。可以针对 Zookeeper 服务的“操作”来设置观察,该服务的其他操作可以触发观察。Zookeeper 服务的“操作”包括一些对 Znode 添加修改获取操作。
zookeeper适用场景:可以用作集群管理,服务名统一管理,配置管理等。
13、zookeeper watch机制
Znode发生变化(Znode本身的增加,删除,修改,以及子Znode的变化)可以通过Watch机制通知到客户端
14、redis/zk节点宕机如何处理
redis根据哨兵机制处理。
zk根据心跳机制检测做轮询重新选举。
15、分布式集群下如何做到唯一序列号
数据库id,uuid,雪花算法等方式去生成唯一序列号
16、如何做一个分布式锁
可以使用redis来实现,当有一个商品下单,生成key签名,并把对应商品采用redis提供的锁做锁定,并设置时限,如果超出时间其他线程可下单。
17.b和b+树的区别
b树:
b+树:
区别:主要是存储的内容不一样,b树只存单一数据,b+可以存储hash集合等。检索查询的方式不一样,b是一层层遍历,b+顺序排列相连。
缓存
1、Redis有过哪些数据,以及Redis底层怎么实现
五种类型:(string)字符串对象,(list)列表对象,(hash)哈希对象,(set)集合对象,(zset)有序集合对象
底层实现:
1.String的底层实现:string原理
底层字符串特性?
1).二进制安全(体现在len这个字段上)
2).避免频繁的内存分配,进行内存得预分配(体现在上面扩容过程)
3).兼容c语言函数库(其实底层都会加\0占一个字节作为字符串结尾)
2.哈希底层的实现:数组 + 链表(采用头插法解决冲突,不会像java语言的map一样转化为红黑树),redis会把kay、value封装成一个dictEntry的结构体(dictEntry的key为string类型,value是一个指针,指向一个redisObject对象(不像java语言一样把hashmap的key、value的具体值封装在一起))
redis为什么要把value封装成一个redisObject对象?
因为redis支持value的值有多种不同的数据类型
3.list底层实现:底层是链表,有两个list,一个是ziplist,字面意是压缩列表,另一个是quicklist,字面意是快速列表,在redis中直接使用的是quicklist
4.set底层实现: Set是一个特殊的value为空的Hash。
5.zset底层实现:Zset底层是一种跳表SkipList数据结构(跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找,实质就是一种可以进行二分查找的
2、Redis缓存穿透,缓存雪崩
缓存穿透问题
现象:用户大量并发请求的数据(key)对应的数据在redis和数据库中都不存在,导致尽管数据不存在但还是每次都会进行查DB。
为什么key对应数据在缓存和db中不存在还会每次都进行DB查询呢?因为很多开发同学写的代码写的逻辑都是先从redis缓存中查一把,如果缓存中为空则从DB中查,如果DB中查到的数据不为空则设置到缓存并返回给接口。那么问题来了,如果从DB中查询的数据为空呢??
解决方案:
从DB中查询出来数据为空,也进行空数据的缓存,避免DB数据为空也每次都进行数据库查询;
使用布隆过滤器,但是会增加一定的复杂度及存在一定的误判率;
缓存雪崩问题
现象:大量key同一时间点失效,同时又有大量请求打进来,导致流量直接打在DB上,造成DB不可用。
解决方案:
设置key永不失效(热点数据);
设置key缓存失效时候尽可能错开;
使用多级缓存机制,比如同时使用redsi和memcache缓存,请求->redis->memcache->db;
3、如何使用Redis来实现分布式锁
4、Redis的并发竞争问题如何解决
5、Redis持久化的几种方式,优缺点是什么,怎么实现的
6、Redis的缓存失效策略
7、Redis集群,高可用,原理
8、Redis缓存分片
9、Redis的数据淘汰策略
JVM
1、详细jvm内存模型
2、讲讲什么情况下回出现内存溢出,内存泄漏?
3、说说Java线程栈
4、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
5、JVM 出现 fullGC 很频繁,怎么去线上排查问题?
6、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?
7、类的实例化顺序
8、JVM垃圾回收机制,何时触发MinorGC等操作
9、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的
10、各种回收器,各自优缺点,重点CMS、G1 11、各种回收算法
12、OOM错误,stackoverflow错误,permgen space错误
1)put的时候导致的多线程数据不一致。
这个问题比较好想象,比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
2)另外一个比较明显的线程不安全的问题是HashMap的get操作可能因为resize而引起死循环(cpu100%)
java
1.基础(集合,io流)-----》进阶:多线程包,JVM,设计模式
2.spring(ioc和aop)-----》进阶:事务,分布式事务
3.中间件(zookeeper,redis,activemq)集群-----》落地:缓存穿透,雪崩
4.RPC框架(dubbo,Cloud)----》问题:dubbo和cloud的区别
5.接口(http,webservice)
数据库
1.mysql,oracle(特性与区别)
2.acid原理
3.数据库4种隔离级别
4.数据库优化(执行计划解析,索引的场景,索引碎片的处理)
5.分区分表
流程图、甘特图、燃尽图
项目风险时间控制
懂点技术
然后让下面人 乐意 跟你干
前端
1.html
2.css
3.js
4.es6,webpack
5.vue,react
如果技术点的话,有:
1.前后加密双重效验实现机制
2.单点登录实现原理和几种方式
3.跨域怎么实现互通手段和几种方式
4.nio,bio的区别和机制
5.消息中间件的原理,举例你用的几项
6.sql优化和执行计划的技巧使用过么
7.java并发编程的有几个类,并举例几个常见的并发设计模式
参考:面试宝典
https://gitee.com/MrString/study