1.java基础
1.1 说一说java有哪些集合
答:分层次记忆:第一层:Collection;第二层:List、Set、Queue;第三层:Vector、ArrayList、Deque、PriorityQueue、HashSet、SortedSet、EnumSet;第四层:Stack、LinkedList、ArrayQueue、LinkedHashSet、TreeSet;
集合主要还是指collection下面的接口,如果问的是容器的话还需要回答map相关内容;
同样用分层记忆:第一层:Map;第二层:IdentityHashMap、SortedMap、WeakHashMap、HashTable、EnumMap;第三层:LinkedHashMap、Properties、TreeMap;
1.2 说一说ArrayList跟LinkedList的区别;
答:ArrayList底层结构是数组,随机查询效率较高,增删元素慢;
LinkedList底层结构是链表,随机查询效率比较慢,但是增删元素快;
1.3 说一下HashMap实现原理
答:1.jdk1.8之前是数组加上链表,1.8以后是数组加上链表加上红黑树;
2.默认初始化容量为16,且每次扩容为2的n次幂,负载因子为0.75,链表阈值为8;
3.每次扩容后这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为 <原下标+原容量> 的位置;
4.加入红黑树的原因是因为hash碰撞过多产生的链表过长会导致查询效率降低,转变红黑树以后查询效率更改,查询时间复杂度在O(logN);
5.hashCode计算方法为,i.高 16bit 不变,低 16bit 和高 16bit 做了一个异或;
6.hashMap是线程不安全的容器,情况1:在put的时候,当hashcode一样时,会覆盖值;情况2:并发扩容的时候也会存在同时扩容,最终只留下了最后一次扩容的数据,导致数据丢失;
7.当容量大于容量*0.75时,put后进行扩容;
1.4 说一说volatile关键字的原理
答:1.volatile用来标明属性内存可见性,即程序每一个获取到的volatile的标量都是最新的;
2.volatile还可以防止指令重排序;
3.voltile修饰后会在操作系统层面,对变量的存取加内存屏障,使其每次获取值都是从主存拿;
1.5 说一下ConcurrentHashMap
答:1.这是一个线程安全的hashMap容器,采用分段锁实现;
2.jdk1.8之前用的是segment加锁来实现,1.8之后使用volatile+CAS来实现,降低了复杂度以及提供了性能;
3.get操作时,没有加任何锁,因为Node的成员val是用volatile修饰的;
4.put时,使用锁和cas实现线程安全;
5.内部node上加volatile修饰是为了保证扩容时对其他线程可见;
1.6 说一说java I/O
答:IO 总的来说分为两个阶段:第一阶段是等待数据到达内核缓冲区,第二阶段是将数据从内核缓冲区复制到用户缓冲区。
1.javaI/O氛围BIO、NIO、AIO;
2.传统形式的InputStream等类为BIO;
3.NIO主要涉及Channel,Buffers,Selectors ;代表框架为netty;
4.AIO在两个阶段都完成以后才发送信号,数据是直接可用的;
1.7 java锁机制
答:这里主要说两个类,Synchronized和ReentrantLock;
1.Synchronized是一个关键字,只要给加上这个关键字,jvm底层会帮我们做同步
如果加在静态方法上,那么锁对象为该类;
如果加在普通方法上,那么锁对象为该实例;
如果是静态同步块,那么锁对象为括号里的对象;
加上该关键字后,jvm字节码会多出两个指令,一个monitorEnter和monitorExit;理解为获取锁和释放锁;
2.ReentrantLock是一个类,我们可以通过new 来获取锁对象,用这个类来控制锁的获取和释放更加灵活;
每次在同步块之前通过tryLock()方法尝试获取锁,但是必须在退出同步方法中自己手动释放锁,否则下一个线程永远都获取不到锁,导致程序崩;
3.jvm底层有无锁,偏向锁,轻量级锁,重量级锁的概念,且锁只能升级不能降级;锁标志是放在java对象的头里Mark word,获取到锁后,会在mark word里面记录当前线程的id;
1.8 Thread.sleep()跟object.wait()的区别;
答:sleep方法只是阻塞了线程,不释放锁;而wait方法调用后不仅阻塞线程,还释放锁并且需要等到notify唤醒;调用后线程都是进入阻塞状态;
1.9 公平锁与非公平锁底层实现原理
答:公平锁是指按照线程等待顺序获取锁,这种方式会造成性能低下,大量的时间花费在线程调度上;
非公平锁是指不管等待顺序,随机获取;
都是基于AQS实现,内部维护一个双向链表,表结点Node的值就是每一个请求当前锁的线程,公平锁每次从队首取,非公平锁不去判断队列直接抢占,如果获取不到就放入队列,后续就一个个来获取锁了;
具体底层使用volatile 修饰的state字段和CAS操作来实现;
1.10 红黑树特点
答:1.每个节点不是红色就是黑色
2.根节点必须为黑色
3.红色节点的叶节点只能是黑色
4.从任意节点到它的每个叶子结点的所有路径都包含相同的黑色结点
1.11 java8的新特性
答:1.函数式编程,新增lambda表达式
2.Opntional类
3.时间相关类,例如LocalDate等
4.jvm变更,取消了PermGen,新增了元数据区,这个区域跟本地内存共享
5.接口允许添加默认方法
1.12 一个类的字节码由哪些部分组成
答:1.魔术,表示文件类型,防止篡改
2.jdk版本号
3.常量池相关信息,如容量,类型,字面量符号引用,类型,结构等
4.访问标志
5.索引、父类索引、接口索引
6.字段表,方发表,属性表
1.13 对用引用类型
答:1.强引用:直接new ,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
2.软引用:SoftReference ,只有在内存不足的时候JVM才会回收该对象,适合用来做缓存
3.弱引用:WeakReference,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
4.虚引用:RefrenceQueue,如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
2.spring相关
2.1 什么是IOC
答:ioc就是控制反转的意思,将bean的管理权交给spring管理,就不需要自己new,也叫依赖注入,将每个bean所以依赖的对象交由spring框架来自动注入;
2.2 什么是AOP
答:1.aop就是面向切面编程,通过代理的方式实现,主要用来做统一处理,例如登录拦截,日志记录,日志埋点等操作;事务实现其实也是通过aop来的;
2.aop有多种通知,分别为前置,后置,环绕,异常等;切点可以用相关表达式去匹配,也可以自己去定义注解实现;
3.动态代理又分jdk动态代理和cglib动态代理,当有实现接口时默认使用jdk,没有实现接口时使用cglib;
2.3 spring bean的初始化过程
答:创建上下文环境(beanFactory)-->准备容器--->加载xml内容---
->初始化bean到BeanDefinition-->实例化bean--->设置属性---->对于实现了相关Aware接口的调用方法-->调用BeanPostProcess的postProcessBeforeInitialization方法---->调用InitializingBean的afterPropertiesSet方法--->调用指定的初始化方法----->调用BeanPostProcess的postProcessAfterInitialization方法--->bean准备就绪------->调用Dispostblebean的destory方法----->调用指定的销毁方法
整个图片就是:
这里可以扩展下spring循环依赖的实现,由于先实例化bean然后再设置属性,由此解决了循环依赖的问题,spring会将实例化后的bean放入一个标记容器里面,等到设置属性的时候就会到相关容器里面去取;由此引出spring使用三级缓存来解决该问题;
三级缓存分别为:singletonObjects(一级)指单例对象的cache,singletonFactories(三级)指单例对象工厂的cache,earlySingletonObjects(二级)指提前曝光的单例对象的cache;
spring在实例化的时候会将还未初始化完全的对象放入三级缓存中;且这里用ObjectFactory来生产对象;
例如:我们在编写代码时,两个service类有时候会互相引用,但是程序运行时却没有报错;
但是使用构造方法实例化的和多态的bean还是存在循环依赖问题的;
2.4 BeanFactory跟applicationContext的区别
答:1.beanFactory面向spring,而applicationContext面向spring开发者;
2.applicationContext扩展了beanFactory,实现了更多的功能,例如:支持aop,web等插件,国际化,事件传递等;
2.5 SpringMVC的流程图
答:2.6 Spring boot的优点
答:1.简化配置,不需要xml文件
2.自动配置,许多的插件只需要引入相关依赖包,并配置上相关参数就可以使用
3.应用监控强大,提供一系列端点可以监控服务及应用,做健康检测。
4.内置了tomcat容器,部署起来方便
2.6 Spring boot初始化流程
答:1.SpringApplication的main方法调用run时,主要做三件事:
根据classpath下是否存在(ConfigurableWebApplicationContext)判断是否要启动一个web applicationContext。
SpringFactoriesInstances加载classpath下所有可用的ApplicationContextInitializer
SpringFactoriesInstances加载classpath下所有可用的ApplicationListener
2.遍历初始化过程中加载的SpringApplicationRunListeners,然后调用starting(),开始监听springApplication的启动
3.加载SpringBoot配置环境(ConfigurableEnvironment)
4.banner配置
5.ConfigurableApplicationContext(应用配置上下文)创建,根据webEnvironment是否是web环境创建默认的contextClass,通过BeanUtils实例化上下文对象,并返回
6.prepareContext()方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联。
7.refreshContext(context),bean的实例化完成IoC容器可用的最后一道工序
3. mybatis相关
3.1 mybatis有几级缓存,分别是
答:mybatis分二级缓存,分别sqlsession和namespace,默认开启一级缓存
3.2 mybatis“$“和“#”的区别
答:“$“不做预编译,“#”会进行预编译,可以有效防止sql注入问题;
4.数据库相关
4.1 mysql事务的隔离级别
答:读未提交,读已提交,不可重复读,序列化,mysql默认级别为不可重复读;
4.2 什么是脏读,什么是幻读,什么是不可重复读;
答:1.脏读:脏读即为事务1第二次读取时,读到了事务2未提交的数据。若事务2回滚,则事务1第二次读取时,读到了脏数据
2.幻读:事务1第二次查询时,读到了事务2提交的数据。
2.不可重复读:不可重复读与脏读逻辑类似。主要在于事务2在事务1第二次读取时,提交了数据。导致事务1前后两次读取的数据不一致。
4.3 事务的特性
答:acid分别为原子性,一致性,隔离性,持久性;
4.4 事务的传播行为
答:PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务
PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
PROPAGATION_MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式运行,假设当前存在事务,则抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
4.5 如何优化sql
答:1.首先查看是否是联表查询,如果是就进行语句拆分;
2.拆分以后如果还是慢,就进行sql语句分析利用explain语句;
3.如果发现没有使用索引,那么就修改语句使走索引,如果没有,那就考虑是否需要建立;
这里需要注意:联合索引的最左匹配原则;
4.6 哪些情况下不会走索引
答:1.or语句,如果or前后有一个不走索引就不会走索引;
2.like匹配,只有前缀匹配才走索引;
3.等号前面进行运算
4.用联合索引时,没有使用第一个索引字段
5.如果列类型是字符串则必须用引号,否则不走索引
6.如果存在隐式转换也是不走索引的
4.7 说一下数据库的数据结构
答:这里拿innodb来说明:
1.B+tree的数据结构,为什么不用Btree是因为B+tree的每一层能放下更多地址,只索引到下一个节点,不保存数据,这样的话就降低了树的高度,减少了查询时的磁盘io;
B+tree的特点:
非叶子节点只存储键值信息。
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中。
这里也可以引申说明下:当我们使用其余索引查询时,如果只需要查询索引数据最好就只写该字段值,可以减少回表操作,提高性能;
4.8 说一下数据库的mvcc
答:即多版本并发控制,在并发访问的时候,数据存在版本的概念,可以有效地提升数据库并发能力,读写不冲突,利用快照实现读;通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。
innodb实现:在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
4.9 mysql explain type类型
答:1.all:全表扫描
2.index:另一种形式的全表扫描,只是扫描顺序根据索引顺序
3.range:有范围的索引扫描,优于index
4.ref:使用了索引,但该索引列的值并不唯一,有重复
5.ref_eq:唯一查询,只有一个
5. 线程池相关
5.1 如何new线程池
答:一般使用ThreadPoolExecute来创建线程池,参数详解:
corePoolSize:核心可运行线程数
maximumPoolSize:最大可运行运行程数
workQueue:阻塞队列
keepAliveTime:当线程大于核心线程数时,且阻塞队列没有元素,最大等待时间
threadFactory:生成线程的工厂类
handler:超出线程池最大承受能力之后的失败策略方法对象
5.2 为什么不用静态方法创建
答:1.new CachedThreadPool,new ScheduledThreadPool线程池无限大,当任务过大时会导致oom;
2.new FixedThreadPool,new SingleThreadpool,当请求队列过大时,会导致oom;
5.3 线程池submit和execute方法的区别
答:submit有返回值,execute没有返回值,当我们需要方法运行后返回值来做处理时应该使用submit;
5.4 线程的生命周期
答:1.新建(new Thread())
2.就绪(Runnable)调用 start启动
3.运行(Running)
4.堵塞(blocked)sleep、wait、join、suspend 都会阻塞;
5.死亡(dead)
6.jvm相关
6.1 说一下jvm内存模型;
答:jvm分为堆、栈、程序计数器、本地方法栈、方法区、元数据区(这里需要说下jdk1.8以前和以后的区别),堆又分年轻代,幸存区,和老年代;
可以扩展说明下:年轻代使用复制算法,老年代是用标记-整理;
6.2 说下jvm是如何标记存存活对象
答:1.可达性分析法:通过将一些称为”GC Roots”的对象作为起始点,从这些节点开始搜索,搜索和该节点发生直接或者间接引用关系的对象,将这些对象以链的形式组合起来,形成一张“关系网”,又叫做引用链
2.引用计数法:就是给对象添加一个引用计数器,每当有一个地方引用它时就加1,引用失效时就减1,当计数器为0的时候就标记为可回收
6.3例举jvm的垃圾收集器
答:1.Serial 复制算法
2.Serial old 标记整理
3.PerNew 复制算法
4.parallel nScavenge 复制算法
5.parallel old 标记整理
再主要说明下cms和G1
1.G1 基本不用配置,低停顿,用于大容量的堆。但是他牺牲了应用程序的吞吐量和部分堆空间。
2.CMS 配置比较复杂,合理的低停顿,用于中等或更小的堆。
3.所以当你觉得配置 CMS 太难了,或你的堆在 2 G 以上,或你想要显式的指定停顿时间那么你可以使用 G1。否则使用 CMS
关于jvm调用还是要具体看你的目标是吞吐量还是程序停顿时间;
6.4 常用jvm的相关工具
答:Jstatus,JStack,Jmap等
6.5 什么是双亲委派模型
答:当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。(自己不去加载交由父类去加载,一直往上,如果顶层加载不到然后依次往下)
6.6 哪些对象能作为GC root
答:1.由系统类加载器加载的对象;
2.Thread,活着的线程;
3.java方法的local变量;
4.JNI方法的local变量;
5.JNI全局引用;
6.用于JVM特殊目的由GC保留的对象;
7.用于同步的监控对象;
6.7 虚拟机栈使用是否会内存溢出
答:内存溢出分为多种情况:
1.堆栈溢出
2.PermGen的溢出
3.使用ByteBuffer中的allocateDirect方法的,没有做clear,jvm垃圾回收不会回收这部分的内存;
4.堆设置过大,导致机器内存分配线程不够;
5.地址空间不够;
所以虚拟机栈使用会内存溢出;
7.redis相关
7.1 redis的两种持久化机制
答:1.aof,和rdb
2.rdb适合大规模数据恢复,会存在数据丢失情况, 如果对数据一致性要求不高可以选择这种
3.aof数据完整性更高,但是数据文件比较大,运行效率上比较慢
7.2 redis单线程为什么快
答:1.完全基于内存
2.数据结构简单
3.采用单线程,避免了不必要的上下文切换
4.使用多路I/O复用模型
5.使用底层模型不同,Redis直接自己构建了VM 机制
7.3 什么是缓存穿透,什么是缓存击穿,什么是缓存雪崩
答:1.缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
- 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
3.缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
8. dubbo相关
8.1说一下dubbo跟java的spi机制
答:java:java spi机制的优点是解耦,但是缺点也有,比如说,不能指定获取哪个实现类,写在配置里面的都会被加载进来。 ServiceLoader
dubbo:支持指定实现类,支持切面,支持依赖注入 ExtensionLoader、@Adaptive标记适配器类、@SPI("值")使用默认的实现类
9.mq相关
9.1 说一下mq使用场景
答:解耦、异步、削峰
9.2 mq对比
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | - | - | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 |
时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | - | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
9.3 kafka offset相关
答:kafka offset分为两种,一种是Current offset,保存在消费者端,决定调用poll方法时,拉取消息的顺序,
保证收到的消息不重复;
第二种是Commited offset,保存在broker,通过commitSync和commitAsync方法来操作,是用于Consumer Rebalance过程的,能够保证新的Consumer能够从正确的位置开始消费一个partition,从而避免重复消费。
10.其他
10.1 如何保证接口幂等性
答:使用唯一索引和token机制
10.2 分布式优缺点
答:优点:1.便于开发和扩展;
2.每个服务足够内聚,降低耦合度;
3.提高代码复用性;
4.增加功能只需要增加子项目;
缺点:1.提高了系统复杂度
2.部署多个服务比较复杂;
3.因为服务多导致运维变得复杂;
4.架构复杂导致学习慢;
5.测试和查错复杂度上升;
10.2 什么是线程安全;
答:多个线程执行同一段代码跟单线程执行同一段代码结果一样;主要是通过锁机制来解决;
10.3 分布式事务的原则
答:柔性事务:遵循CAP理论或者其变种BASE理论的事务。分布式事务基本上都是柔性事务。具有弱一致性,也就是最终一致性
10.4 CopyOnWriteArrayList实现原理
答:读写分离的思想,copy出原来的数据然后做操作,最终把引用指向新的容器,最终一致性;
10.5 RocketMQ是如何实现分布式事务的
答:通过事务消息来实现,发到broker的消息并不能马上消息,称为半消息,需要producer进行二次确认,如果二次确认途中丢失,有回查机制来解决;
10.6 读写锁实现原理
答:与传统锁的区别是读读不互斥,读写互斥,写写互斥,核心类为Sync,基于AQS的实现,可以用2个int变量来实现;AQS中的stata字段高16位是读锁个数,低16位是写锁个数;
10.7 如何实现分布式锁
答:分布式锁实现主要有三种方式
分布式锁实现方式 | 数据库实现 | redis实现 | zookeeper实现 |
---|---|---|---|
实现原理 | 1.通过给数据库记录加乐观锁或者悲观锁;2.通过数据库记录,获取锁insert一条记录,释放锁删除记录 | 1.通过setnx方法实现,然后设置过期时间;2.与1一样,但是key的值为当前时间+上过期的时间 | 利用zookeeper的临时有序节点,所有线程都去创建临时节点,但是序列最小的获取到锁,释放只要删除节点就可以 |
优点 | 1.悲观锁是一种比较安全的实现;2.乐观锁性能高于悲观锁,不容易出现死锁;3.增加记录实现方式简单 | 1.性能高,实现比较方便 | 1.可靠性高 |
缺点 | 1.悲观锁性能低,容易出现死锁;2.乐观锁只能对一张表的数据加记录,需要操作多张表是办不到的;3.不具备可重入性,没有锁失效机制,不具备阻塞锁特性 | 1.锁超时机制不是很可靠,如果处理时间过长就会导致锁实现;2.主从环境下可能存在key同步问题 | 1.性能比不上redis缓存锁,因为需要频繁的创建临时节点 |
10.8 什么是无锁编程
答:无锁编程就是指利用CAS操作和volatile关键字实现线程安全;
10.9 什么是模块化
答:就是讲复杂系统拆分出一个个的模块,模块开发就是封装细节,提供接口;重点关注内聚度以及耦合度
优点:1.可维护性高
2.灵活架构,焦点分离
3.方便模块间组合
4.多人协作,互不干扰
5.方便单个模块的调试和测试
缺点:
1.系统分层,调用链长
2.模块间发送消息,损耗性能
10.10 秒杀需要考虑的点
答:1.独立部署,防止影响其他业务
2.静态化相关商品描述信息;
3.增大秒杀带宽;
4.动态随机成下单url,可以说获取token,然后下单
5.限流,控制少量请求进入
3.23 新增
11 面试题
11.1 HTTP、TCP、UDP的区别
答:1.osi 七层中的层级不同,http就属于应用层,tcp与udp是属于传输层;
2.http基于tcp
3.http长连接、短连接;就是tcp长连接、短连接
4.tcp面向连接,udp不是,如三次握手,四次挥手;
11.2 websocket和tcp的关系
答:1.跟http一样,都是基于tcp;
11.3 jdk动态代理与cglib的区别,为什么要基于接口
答:1.jdk动态代理只能对实现了接口的类做代理,而cglib没有这个限制;
基于接口是因为:
1.生成的代理类继承了Proxy,由于java是单继承,所以只能实现接口,通过接口实现
2.从代理模式的设计来说,充分利用了java的多态特性,也符合基于接口编码的规范
11.4 分布式事务
答:1.指事务的每个操作步骤都位于不同的节点,需要保证事务的acid特性;
产生原因:是分布式服务中,分库分表;
应用场景:下单:减少库存以及更新订单状态;支付:买家和商家同时扣款:前提:两个账户都不在一个库或者一个表;
解决方案:
1.两阶段提交协议
使用XA来实现,XA分为事务管理器和本地资源管理器,本地资源管理一般由数据库实现
2.使用消息中间件
11.5 ThreadLocalMap原理
答:ThreadLocalMap其实是线程自身的一个成员属性threadLocals的类型,内部实现跟普通的map不一样,内部用了一个ertry数组,该数组继承自WeakReference,WeakReference(弱引用)的特性:大家都知道WeakReference(弱引用)的特性,只要从根集出发的引用中没有有效引用指向该对象,则该对象就可以被回收,但是这里的有效引用并不包含WeakReference,所以弱引用不影响对象被GC。WeakReference被ThreadLocal自身强引用,也就是说ThreadLocal自身的回收不受ThreadLocalMap的这个弱引用的影响;
3.24 面试题新增
12 面试题
12.1 TCP连接为什么可靠
答:1.超时重传:发送后启动一个定时器,等待目的确认收到,如果不及时确认,就会重发
2.给数据包编号,接收方对数据包排序,有序传给应用层;
3.校验:保持首部和数据的校验和,如果校验有错误就丢弃;
4.TCP接收端会丢弃重复数据;
5.拥塞控制,当网络拥塞时,减少数据的发送;
12.2 CGLIB是如何实现动态代理的
答:通过asm字节码处理框架来实现的一个code生产类库;
12.3 http 和https的区别,以及https是如何保证安全的;
答: 1.http是明文传输,https是加密传输
2.https加了一层ssl/tls协议
3.https需要使用ca证书,且收费;
https加密过程:1.服务器将公钥给浏览器 -->2.浏览器拿到公钥之后,生成一个“会话密钥"--->3.用公钥加密这个“会话密钥”发送给服务器--->4.数据传输的过程中,就用这个会话密钥来加密数据
12.4 进程和线程的区别
答:根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:进程切换上下文开销大;线程可以看做轻量级的线程,开销小;
所处环境:在操作系统中运行多个进程,在一个进程中运行多个线程,
内存分配:系统会给进程分配内存;对于线程只会分配cpu;
2020.03.25面试题新增
13 面试题
13.1 如何判断连接是活的
答:1.应用程序自我探测;2.应用程序的探测 ;3.TCP 协议层的保活探测
13.2 数据库死锁
答:1.以固定的顺序访问表和行。即按顺序申请锁,这样就不会造成互相等待的场面。
2.大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小
3.在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
4.降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
5.为表添加合理的索引。如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
6.通过添加锁,集群就分布式锁。
13.3 java内存模型
答:简称JMM,分为主内存和线程内存,共享变量存放在主内存;
13.4 什么是zero copy
答:减少了内核空间和用户空间的copy,用sendfile
13.5 如何判断连接存活
答:使用netstat 命令;
14 2020.04.15更新
14.1 rabbitmq消息持久化的三个必要条件
答:1.消息队列持久化。(设置durable)
2.exchange持久化。(设置durable)
3.消息持久化。(deliveryMode为2)
14.2 Spring 父子容器访问关系
答:springMVC可以访问Spring容器的bean,但是Spring容器不能访问SpringMVC容器的bean。
14.3 Servlet什么时候初始化
答:看web.xml里面的配置,loadestartup值为正数即容器启动时候初始化,为负数则第一次请求时初始化;
14.4 cloud如何自己实现负责均衡算法
答:1.集成AbstractLoadBalanceRule类;
2.注意:这个自定义的类不能放在@ComponentScan所扫描的当前包以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是我们达不到特殊化指定的目的了。
3.使用方式:@RibbonClient(name = "MICROSERVICECLOUD-DEPT", configuration = MySelfRule.class)
14.5 aio、nio与bio中的阻塞点
答:首先一次io分2个阶段:
1.等待数据准备
是否阻塞指的就是这一个阶段。
2.将数据从内核拷贝到进程中
是否同步指的就是这一个阶段。
bio 两个阶段都阻塞
nio第一阶段不阻塞,第二阶段阻塞,通过轮询数据是否准备好
aio两个阶段都不阻塞,由内核完成,完成后通过信号告知
属性\模型 | 阻塞BIO | 非阻塞NIO | 异步AIO |
---|---|---|---|
blocking | 阻塞并同步 | 非阻塞但同步 | 非阻塞并异步 |
线程数(server:client) | 1:1 | 1:N | 0:N |
复杂度 | 简单 | 较复杂 | 复杂 |
吞吐量 | 低 | 高 | 高 |
场景 | 适用于连接数目比较小且固定的架构 | 连接数目多且连接比较短(轻操作)的架构,比如聊天服务器 | 连接数目多且连接比较长(重操作)的架构,比如相册服务器 |
14.6 linux如何查看端口占用情况
答:lsof,netstat命令;
15. 2020.04.21 面试提更新
15.1 jvm GC问题
答:GC 分为young gc、major jc、full gc;
1.young gc(minor gc) 主要针对新生代;
2.majorgc 主要针对老生代;
3.full gc是针对整个堆;
以上三种gc都会出现stw,且任何垃圾回收器都无法避免stw,jvm调优就是为了是stw的时间缩短;
出发full gc的条件:
1.代码中显示调用system.gc(),调用后不一定马上触发,需要jvm自行决定;
- young gc 之前判断老年代可用的连续空间是否大于新生代的所有对象总空间;
①.如果大于,直接进行young gc;
②.如果小于,判断是否开启HandlerPromotionFailure,没有开启就直接full gc;
③.如果小于,且开启该参数,判断是否大于历次晋升的平均值,如果小于直接full gc,大于就young gc;
3.如果创建大对象时,老年代不足以存放;
4.当持久代(方法区)空间不足,即元数据区;
15.2 官方rocketmq 跟开源 rocketmq的区别
答:
功能 | 阿里rocketmq | 开源rocketmq |
---|---|---|
安全防护 | 支持 | 不支持 |
主子账号 | 支持 | 不支持 |
可靠性 | 高,超三分数据副本 | 不高 |
可用性 | 非常好 | 好 |
横向扩展能力 | 支持平滑扩展,百万级qps | 支持 |
low latency | 支持 | 不支持 |
定时消息 | 支持(精确到秒) | 支持(只支持18个level) |
事务消息 | 支持 | 不支持 |
全链路消息轨迹 | 支持 | 不支持 |
消息堆积能力 | 百亿级别 不影响性能 | 百亿级别 影响性能 |
性能(万级topic场景) | 非常好,百万级qps | 非常,十万级qps |
15.3 自定义实现lru算法
答:直接编写类继承LinkedHashMap类,构造方法时候传入true,表示根据最近访问时间排列,即设置accessOrder这个字段,重写removeEldestEntry方法,也就是制定删除策略;
如下就是,容量超过最大值时,会删除最少访问的数据;
@Override
protected boolean removeEldestEntry(Map.Entry eldest)
{
return size() > MAX_ELEMENTS;
}
15.4 后台刷新缓存,前台短时间内大量请求穿透到db;
答:通过熔断器以及限流技术,防止大量请求穿透到db,以至于db崩掉;
15.5 为什么String是不可变得
答:
1..不可变对象可以提高String Pool的效率和安全性,直接复制地址,不需要复制数据
2.不可变对象对于多线程是安全的,修改不可变对象会报错;
3.字符串常量池的需要,常量池中存在时就不会创建新对象;
4.允许String对象缓存HashCode,不可变就不需要重新计算hashcode;
5.安全性,用户名,密码无法修改;
15.6 Spring aop中用到的技术
答:1.动态代理技术
2.asm字节码框架
3.代理设计模式
15.7 事务的作用范围
答:作用于service层,第一次访问数据库时开启,service层执行完毕后,事务关闭/提交;
15.8 java中的不可变对象
答:String ,原生类型的包装器如Integer、Double、Long,BigInteger,LocalDate等
2020.04.23 面试题新增
16.1 dubbo spi用到的设计模式
答:在加载类的时候用到了工厂的设计模式,在方法调用时用到了代理模式;通过Adaptive,在调用阶段动态地根据参数决定调用哪个实现类。
16.2 dubbo的注册原理
答:利用Javassist技术生成invoker,然后调用DubboProtocol的export方法,最终会调用ZookeeperRegistryFactory的createRegistry进行注册;
16.3 redis分布式锁与zk分布式锁的对比
答:1.redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;
2.zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小;
3.如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁,而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁。
16.4 double check问题
答:写单例类时,有时用到double check,这个时候还需要把单利对象用volatile修饰,这个有两个作用
1.内存可见性;2.防止指令重排序;
因为new一个对象有四个步骤,1.堆内存分配;2.初始化值;3.在栈中创建引用变量;4.然后将堆内对象的地址赋值给引用变量;
如果不用volatile关键字,那么上面四部就会被打乱;
2020.11.24 新增
17.如何保证kafka有序消费
答:1.单线程,单分区,单消费者;
2.多线程生产,多quenue,多消费者就通过业务key去路由到指定的分区,使统一业务的key路由到同一个分区,同样消费这也消费同一个分区;
18.线程池参数详解,以及工作原理
答:①corePoolSize:线程池的核心线程数,说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
②maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
③keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出。
⑤unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
⑥workQueue:一个阻塞队列,提交的任务将会被放到这个队列里。
⑦threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
⑧handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。
1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
19.jvm为什么1.8要将永久代变为元数据区?
答:1.字符串存在永久代中,容易出现性能问题和内存溢出;
2.类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比,较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出;
3.永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
4.将 HotSpot 与 JRockit 合二为一;
20.如何防止缓存击穿
答:1.做无效key的空缓存;
2.利用bitmap实现过滤;
3.查库之前先获取分布式锁;
21.当同一组kafka消费者数量大于kafka分区数的时候会出现什么?
答:有一部分消费者永远都无法消费到数据;
22.seata支持高并发么?
答:不支持,因为整个业务期间需要独占资源;
23.es的索引结构
答:倒排索引和正排索引;
24.mysql聚簇索引和非聚簇索引
答:1.聚簇索引叶子结点存放具体数据,且逻辑有序;非聚簇索引叶子节点存放聚簇索引的指针;
25.spring和spring boot的区别
答:spring boot是对spring做的扩展;
优点是:
1:创建独立的spring应用。
2:嵌入Tomcat, Jetty Undertow 而且不需要部署他们。
3:提供的“starters” poms来简化Maven配置
4:尽可能自动配置spring应用。
5:提供生产指标,健壮检查和外部化配置
6:绝对没有代码生成和XML配置要求
26.eureka失效剔除的时间以及次数,客户端的同步时间间隔等
答:1.心跳间隔时间:30s
2.收到最后一次心跳过多久剔除:默认90s
3.进入自我保护的默认时间:15分钟内统计的心跳比例地低于85%时会进入
27.熔断器的默认配置,什么时候产生熔断
答:1.10s内出先20次失败;
2.5s后重试;
3.错误率达到50%时也会短路;
28.mysql单表的数据量应控制在多少?
答:应该控制在500W左右,这与 MySQL 的配置以及机器的硬件有关;InnoDB buffer size 足够的情况下,其能完成全加载进内存,查询不会有问题。但是,当单表数据库到达某个量级的上限时,导致内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降;
29.分表路由策略
1.对分表参数进行哈希并取余路由。
优点:请求可均衡分布。
缺点:当表库变更,扩容时,会导致数据位置变更,处理繁琐。因此需要提前规划好容量,一次分好。
场景:在线高性能服务
2.将分表参数划分为不同的范围。不同范围内的参数映射到不同的库表。例如用时间,根据时间段划分。
优点:当表库变更,扩容时处理较方便。
缺点:可能导致请求不均匀,造成某个库表被频繁访问,其它库表访问频率较低。
场景:非在线高性能服务。日志服务。运营服务
30.联合索引,最左匹配原则
答:当创建(col1,col2,col3)联合索引时,相当于创建了(col)单列索引,(clo1,clo2)联合索引以及(col1,col2,col3)联合索引想要索引生效,只能使用col1和col1,col2和col1,col2,col3三种组合;当然,col1,col3组合也可以,但实际上只用到了col1的索引,col3并没有用到!
31.线程池中的核心线程数是如何保活的?
答:死循环从quenue中获取任务,当不为空时,返回任务,否则一直阻塞;
32.threadLocal原理
答:每个thread内部都维护了一个map,而这个map又是threadlocal维护的,由threadlocal对其进行get,set操作;这样对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,这样就形成了副本隔离,互不干扰;
33.threadlocalmap是如何解决地址冲突的以及它扩容过程;
答:他是采用开放地址发解决地址冲突的,扩容时先清除key为null的value,然后在判断当前容量是否大于负载因子的3/4,如果大于就进行扩容,扩容方式和hashmap差不多;
34.redis自己实现的数据结构有哪些?
答:
1.string -----> simple synamic string - 支持自动动态扩容的字节数组
2..list------>ziplist 压缩列表,节省内存,存储不同类型的数据
3.hash----->当保存的键值大小都小于64时且个数小于512时,使用ziplist,否则使用散列表
4.set------->当存储数据都为整数且个数小于512个使用有序数组存储,否则使用散列表
5.sortSet----->当所有数据的大小都要小于64字节且元素个数要小于128个使用压缩表,否则使用跳表;