三、后端开发2021面试题总结JAVA_JVM_Redis_ZooKeeper

    以下问题是对本人在2021.01.27到2021.02.08这段时间面试过的一些互联网、传统型、初创型公司的技术问题整理。建议小伙伴们在面试前一定不要着急,先静下心来让把下面的面试题或者自己认为重要的一些知识点看完再去投自己想去的公司。要不然可能会浪费掉很多宝贵的机会。大公司的招聘流程相对来说比较长,可能初始、复试、三面、HR面试,整个流程下来需要大概15左右,如果遇到假期那么时间会更长,如果想进大厂的话,在面试周期上面要规划的久一些,同时需要考虑一下自己的社保缴纳问题(可通过在网上进行代缴)。规模较小的公司效率确很高,1到2天就能拿到offer。不多说了,开始进入正题吧!
1、JAVA基础与JVM相关
1.1 如何自定义线程池?创建线程池时会有哪些影响因素或者说涉及哪些参数?
1)可以通过 Executors(类) 来创建
//方法1:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,其可以执行的队列数为:Integer.MAX_VALUE
ExecutorService pool = Executors.newSingleThreadExecutor();
//方法2:创建一个定长的线程池,支持定时及周期性任务执行。
ExecutorService pool = Executors.newFixedThreadPool(2);
//方法3:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService pool = Executors.newCachedThreadPool();
上述的这三种方式本质上都是通过 new ThreadPoolExecutor 来进行创建的
//方法4:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行,ScheduledExecutorService 是一个接口
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
上述四种方法本质上都使用 ThreadPoolExecutor 类来实现的。

2)可以通过 ThreadPoolExecutor类 来自定义实现,这种方式是为了便于对线程池的管控(实际开发过程中多使用该方式)
private RejectedExecutionHandler handler = new CallerRunsPolicy();
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(initQueueSize);
ExecutorService boundedThreadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize , keepAliveTime, TimeUnit.SECONDS,workQueue, handler);
备注1:构造方法中的入参字段含义说明:
a.corePoolSize - 池中所保存的线程数,包括空闲线程。
b.maximumPoolSize - 池中允许的最大线程数
c.keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
d.TimeUnit.SECONDS - keepAliveTime 参数的时间单位
e.workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务
f.handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
API查阅请移步:https://tool.oschina.net/apidocs/apidoc?api=jdk-zh
备注2:相关入参的进一步理解:
a.当一个任务到来的时候如何它的核心线程数即corePoolSize没满的话,就会创建一个核心线程去执行该任务
b.如何核心线程数满了,但是阻塞队列没有满的话,就会将该线程先放入阻塞队列中
c.如果核心线程和阻塞队列都满了,但是最大线程数没有满的话,就会新建一个非核心线程去执行该任务
d.如果核心线程数、阻塞队列、最大线程数都满了的话,就会执行线程池的拒绝策略,一共有4种方式:
AbortPolicy:默认测策略,丢弃任务并抛出RejectedExecutionException运行时异常;
CallerRunsPolicy:由提交任务的线程执行该任务,并通过反馈机制,减慢提交新任务的速度;
DiscardPolicy:直接丢弃新提交的任务;
DiscardOldestPolicy:如果执行器没有关闭,队列头的任务将会被丢弃,然后执行器重新尝试执行任务,如果失败,则重复这一过程;
1.2 如何按顺序打印基数和偶数?
a.通过 synchronized 对象锁实现
b.通过 ReentrantLock 独占锁实现
c.通过 ReentrantReadWriteLock 读写锁实现

1.3 String 类可以被继承吗?为什么?
public final class String implements Serializable, Comparable<String>, CharSequence {}
无法被继承,被final修饰的类无法被继承,

1.4 CMS垃圾收集器进行垃圾收集的整个过程?优点和缺点是什么?
之前已经整理过,就不再重述,请移步!-->2.5 CMS收集器

1.5 算法:闭环数统计

1.6 hashMap的底层给介绍一下?
1)HashMap的数据结构为:数组+(链表或红黑树),因为数组的特点是查询效率高,但是插入、删除效率低而链表的特点是查询效率低,插入删除效率高。在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
2)HashMap存储元素的过程:map.put("key","value");
a.计算出键“key”的hashcode,该值用来定位要将这个元素存放到数组中的什么位置。在Object类中有一本地方法 public native int hashCode();调用该方法会生成一个int型的整数,被称作哈希码,哈希码和调用它的对象地址和内容有关。对于同一个对象(使用equals比较返回true)那么无论何时它的hashcode值都是相同的。对于两个对象如果他们的equals返回false,那么他们的hashcode值也有可能相等。
b.根据key的hashCode值对hashMap的容量取模,得到元素存储的数组的下标。
如果该下标位置为空,则直接放进去;
如果不为空,则需要通过equals 比较该位置的元素值是否和待插入的元素值是否相等,相等的话则直接覆盖掉,不相等则在原元素下面使用链表的结构存储该元素。每个元素节点都有一个next属性指向下一个节点,这里由数组结构变成了数组+链表结构。因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.
c.HashMap中有两个重要的参数:初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,也被称作扩容。在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。所以在定义hashMap的时候,如果知道其容量大小最好先定义好其长度,避免扩容引发的性能消耗。

1.7 谈谈对 ThreadLocal 的理解,及其使用场景有哪些?
ThreadLocal提供一个线程的局部变量,使的线程拥有自己局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
1)ThreadLocal 类中常用的4个方法如下:
void set(Object value)设置当前线程的线程局部变量的值;
public Object get()该方法返回当前线程所对应的线程局部变量;
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
总的来说ThreadLocal就是一种以 空间换时间 的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
2)ThreadLocal 是如何进行变量存储的?
ThreadLocalMap是ThreadLocal的一个静态的内部类。ThreadLocal最终的变量是放在了当前线程的 ThreadLocalMap 中,而不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
3)ThreadLocal的使用场景?
a.在进行对象跨层传递的时候,可以考虑ThreadLocal,避免方法多次传递,打破层次间的约束;
b.线程间数据隔离;
c.进行事务操作,用于储存线程事务信息;
备注:ThreadLocal 变量通常被 private static修饰,当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
4)自己实际使用过的业务场景?
a.使用 FreeMarker中的自定义函数:进行菜单权限校验时,将用户的custNo通过 拦截器写入到一个 ThreadLocal全局变量中,然后再根据custNo,进行相关权限的校验。
4)ThreadLocal可能带来什么问题?
如果线程的执行周期过程,可能导致内存泄漏的风险,虽然线程之后完后 ThreadLocal 也会随着销毁,但是最好加上remove()这样可以加快内存的释放,一般来说线程周期不长是不会引发内存泄漏的问题的。

1.8 拦截器(interceptor)同过滤器(filter)的相同和不同之处?
相同之处:两者都是AOP编程思想的体现,都能实现权限检查、日志记录等。
不同之处:
a.规范和应用范围不同:filter 是servlet 规范中规定的,是由Servlet容器支持的,只能用于web程序中;拦截器是Spring 容器中的,是Spring 框架支持的,既可以用于web程序中,也可以用于Application、Swing 程序中;
b.可使用的资源不同:同其他的代码块一样,拦截器也是一个spring 组件,归Spring管理,配置在Spring文件中,因此能使用Spring的任何资源、对象。例如:Service对象、数据源、事务管理等,通过IOC注入到拦截器即可,而Filter则不能;
c.使用的深度不同:Filter只能在Servlet前后器作用,而拦截器可以深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring架构的程序中,要优先使用拦截器;
总结:
a. 拦截器是基于java反射机制;不依赖与servlet容器;只对action请求起作用;可以访问action上下文;在action的生命周期中拦截器可以多次被调用;拦截器可以调用IOC容器中的各个bean;
b. servlet过滤器是基于函数回调,依赖于servlet容器,过滤器对所有的请求都起作用,不能访问action山下文,过滤器只能在容器初始化时被调用一次,不能调用IOC容器中的各个bean;

1.9 哪些场景下使用单例?
a.数据库连接池的设计,主要是节省打开或关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的;
b.多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制;
c.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源;
d.网站的计数器,一般也是采用单例模式实现,否则难以同步;
e.Windows的任务管理器,你无法同时打开2个任务管理器;

1.10 单例的线程安全如何实现?
在懒汉式单例中都可以使用 synchronized 保证线程按全;
饿汉式单列本就是线程安全;
使用静态类部类实现;
使用双检索机制实现;

1.11 单例是否可以通过反射进行破坏?

1.12 JVM内存调优时使用的相关命令?
1)jps命令:Java版的ps命令,查看java进程及其相关的信息
## 输出JVM参数
jps -v

2)jinfo命令:是用来查看JVM参数和动态修改部分JVM参数的命令
## 查看打印GC日志参数
jinfo -flag PrintGC pid值
jinfo -flag PrintGCDetails pid值
## 打开GC日志参数
jinfo -flag +PrintGC pid值
jinfo -flag +PrintGCDetails pid值
## 关闭GC日志参数
jinfo -flag -PrintGC pid值
jinfo -flag -PrintGCDetails pid值

3)jstat命令:主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等
## 内存状态信息打印 其中 11666为pid,每隔1000毫秒打印一次,打印3次
jstat -gcutil pid值 1000 3

4)jstack命令:查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合。使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等。jstack还可以查看程序崩溃时生成的core文件中的stack信息。
## 显示线程快照及锁信息
jstack -l pid值 | more

5)jmap命令:用来生成堆dump文件及查看堆相关的各类信息的命令
jmap -dump:live,format=b,file=dump.hprof pid值

6)jhat命令:用来分析jmap生成dump文件的命令,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上

7)其他的dump文件分析工具: jconsole, jvisualvm、memoryAnalyzer(常用)

2、Redis相关
2.1 Redis如何确保高可用?
1)采用 Redis Sentinel(哨兵)模式:推荐使用该方式,公司当前就使用的该方式
2)采用 Redis Cluster 模式
这两种方式的含义及各自的优缺点,之前已经做过整理,请移步!--> 3、Redis 的几种常见使用方式?

2.2 Redis 在哪些场景下使用?
答案之前已经做过整理,请移步!--> 4、redis 的使用场景

2.3 谈谈 RedisObject 的结构?
答案之前已经做过整理,请移步!--> 11、谈谈 RedisObject 的结构?

2.4 以key=hello,value=world 说明 redis 的数据是如何存储的?
dictEntry、RedisObject、SDS、内存分配器;
答案之前已经做过整理,请移步!--> 10、以key=hello,value=world 说明redis 的数据是如何存储的?内存分配器、SDS、RedisObject

2.3 redis 常用的五种对象类型及其内部编码?
答案之前已经做过整理,请移步!--> 13、redis 的五种对象类型及其内部编码?

2.4 热点问题产生的原因、危害、如何解决?
答案之前已经做过整理,请移步!--> 17、热点问题产生的原因、危害、如何解决?

2.5 redis 如果清理过期的Key?或者说Redis 设置了过期时间会立即过期吗?
redis出于性能上的考虑,无法做到对每一个过期的key进行即时的过期监听和删除。但是redis提供了其它的方法来清理过期的key。
1)被动清理:当用户主动访问一个过期的key时,redis会将其直接从内存中删除。
2)主动清理:在redis的持久化中,我们知道redis为了保持系统的稳定性,健壮性,会周期性的执行一个函数。在这个过程中,会进行之前已经提到过的自动的持久化操作,同时也会进行内存的主动清理。在内存主动清理的过程中,redis采用了一个随机算法来进行这个过程。简单来说,redis会随机的抽取N(默认100)个被设置了过期时间的key,检查这其中已经过期的key,将其清除。同时,如果其中已经过期的key超过了一定的百分比M(默认是25),则将继续执行一次主动清理,直至过期key的百分比在概率上降低到M一下。
3)内存不足时触发主动清理:在redis的内存不足时,也会触发主动清理。redis允许用户通过配置maxmemory-policy参数,指定redis在内存不足时的解决策略。对于只针对设置了过期时间的键进行删除的策略,在所有的可被删除的键(非永久的键)都被删除时内存依然不足,将会抛出错误。

2.6 缓存淘汰算法有哪些?及如何使用?
答案之前已经做过整理,请移步!--> 18、缓存淘汰算法有哪些?及如何使用?

2.7 Redis提供了哪几种持久化方式?
答案之前已经做过整理,请移步!--> 27、Redis提供了哪几种持久化方式?

2.8 谈谈对redis槽点(slot)的认识?
答案之前已经做过整理,请移步!--> 15、谈谈对redis槽点(slot)的认识:Redis 集群中内置了 16384 个哈希槽,这些哈希槽就被称为槽点。

上述 8个问题,是我在面试过程中被问到的问题,更多Redis相关问题的学习请移步!共计29道,与上面的8道会有重复!

3、MySql相关?
3.1 MySql的慢SQL如何进行监控和分析优化?
3.2 在有索引的情况下,B+Tree 数据是如何存储的?如何进行查询的?

3.3 B+Tree、B-tree 的区别?

4、Zookeeper相关
4.1 Zookeeper集群模式下,修改请求在是如何被处理的?
在接收到⼀个写请求操作后,追随者会将请求转发给群⾸,群⾸将探索性地执⾏该请求,并将执⾏结果以事务的⽅式对状态更新进⾏⼴播。具体如下:
1)群⾸将每⼀个请求转换为⼀个事务(事务包含了对应请求处理⽽改变ZooKeeper状态所需要执⾏的步骤)。群首就像⼀个定序器,建⽴了所有对ZooKeeper状态的更新的顺序,将这些事务发送给追随者,追随者接收群⾸所发出更新操作请求,确保集群按照群⾸确定的顺序接受并处理这些事务。而观察者不会参与决策哪些请求可被接受的过程,只是观察决策的结果,观察者的设计只是为了系统的可扩展性。
当群⾸产⽣了⼀个事务,就会为该事务分配⼀个标识符,我们称之为ZooKeeper会话ID(zxid),通过Zxid对事务进⾏标识,就可以按照群⾸所指定的顺序在各个服务器中按序执⾏。服务器之间在进⾏新的群⾸选举时也会交换zxid信息,这样就可以知道哪个⽆故障服务器接收了更多的事务,并可以同步他们之间的状态信息。
2)当事务提交时,服务器就会将这些变更反馈到数据树上,其中数据树为ZooKeeper⽤于保存状态信息的数据结构。服务器通过zab原⼦⼴播协议(全称:ZooKeeper Atomic Broadcast protocol)确认⼀个事务是否已经提交。具体步骤如下:
a.群⾸向所有追随者发送⼀个PROPOSAL消息p;
b.当⼀个追随者接收到消息p后,会响应群⾸⼀个ACK消息,通知群⾸其已接受该提案(proposal)。在应答提案消息之前,追随者还需要执⾏⼀些检查操作。追随者将会检查所发送的提案消息是否属于其所追随的群⾸,并确认群⾸所⼴播的提案消息和提交事务消失的顺序正确。
c.当收到仲裁数量的服务器发送的确认消息后(该仲裁数包括群⾸⾃⼰),群⾸就会发送消息通知追随者进⾏提交(COMMIT)操作;
注:Zab协议主要提供了以下两个保障:⼀个被选举的群首确保在提交完所有之前的时间戳内需要提交的事务,之后才开始⼴播新的事务;在任何时间点,都不会出现两个被仲裁支持的群首。

4.2 Zookeeper 集群选举的整个过程?
1)每个服务器启动后进⼊LOOKING状态,开始选举⼀个新的群⾸或查找已经存在的群⾸,如果群⾸已经存在,其他服务器就会通知这个新启动的服务器,告知哪个服务器是群⾸,与此同时,新的服务器会与群⾸建⽴连接,以确保⾃⼰的状态与群⾸⼀致。
2)如果集群中所有的服务器均处于LOOKING状态,这些服务器之间就会进⾏通信来选举⼀个群⾸,通过信息交换对群⾸选举达成共识的选择。在本次选举过程中胜出的服务器将进⼊LEADING状态,⽽集群中其他服务器将会进⼊FOLLOWING状态。
对于群⾸选举的消息,我们称之为群⾸选举通知消息(leader election notifications),或简单地称为通知(notifications)。具体说来就是当⼀个服务器进⼊LOOKING状态,就会发送向集群中每个服务器发送⼀个通知消息,该消息中包括该服务器的投票(vote)信息,投票中包含服务器标识符(sid)和最近执⾏的事务的zxid信息,⽐如,⼀个服务器所发送的投票信息为(1,5),表⽰该服务器的sid为1,最近执⾏的事务的zxid为 5(出于群⾸选举的⽬的,zxid只有⼀个数字,⽽在其他协议中,zxid则有时间戳epoch和计数器组成)。当⼀个服务器收到⼀个投票信息,该服务器将会根据以下规则修改⾃⼰的投票信息:
a.将接收的voteId和voteZxid作为⼀个标识符,并获取接收⽅当前的投票中的zxid,⽤myZxid和mySid表⽰接收⽅服务器⾃⼰的值;
b.如果(voteZxid>myZxid)或者(voteZxid=myZxid 且 voteId>mySid),保留当前的投票信息;
c.否则,修改⾃⼰的投票信息,将voteZxid 赋值给 myZxid,将voteId赋值给mySid;
⽽⾔之,只有最新的服务器将赢得选举,因为其拥有最近⼀次的zxid。我们稍后会看到,这样做将会简化群⾸崩溃后重新仲裁的流程。如果多个服务器拥有最新的zxid值,其中的sid值最⼤的将赢得选举。
⼀个服务器接收到仲裁数量的服务器发来的投票都⼀样时,就表⽰群⾸选举成功,如果被选举的群⾸为某个服务器⾃⼰,该服务器将会开始⾏使群⾸⾓⾊,否则就成为⼀个追随者并尝试连接被选举的群⾸服务器。
注:我们并未保证追随者必然会成功连接上被选举的群⾸服务器,⽐如,被选举的群⾸也许此时崩溃了。⼀旦连接成功,追随者和群⾸之间将会进⾏状态同步,在同步完成后,追随者才可以处理新的请求。





https://www.cnblogs.com/damsoft/p/6105122.html 单例模式的优缺点和使用场景
https://www.cnblogs.com/wxisme/p/9878494.html JVM监控和调优常用命令工具总结
https://www.jianshu.com/p/85b65e649038 redis分布式缓存之 redis过期时间
https://zhuanlan.zhihu.com/p/79507868 最通俗易懂搞定HashMap的底层原理
https://blog.csdn.net/qq_42742861/article/details/90649837 ThreadLocal常用方法浅析、使用场景及注意事项

书籍:Zookeeper分布式过程.pdf 有需要欢迎留言!
posted @ 2021-02-13 21:43  爱笑的berg  阅读(321)  评论(0编辑  收藏  举报