.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}
这几天在整理之前的面试资料,又偶在在群里和各位同学一起探讨些奇妙的问题,心有所感,便准备将这些题目整理起来,一来是分析给大家,二来也以备日后之需。
一面二面主要是基础面,问的都是一些基础知识,难度不是很高,这里我就简单的分享一下我所遇到的题目。
一、面试者你好,我们开门见山,能跟我介绍一下spring的核心特征吗?包括Spring bean的生命周期也可以讲一下。
对于java工程师来说,几乎没有多少不了解Spring了,Spring Boot 、Spring Cloud等等也是信手拈来。但是很多和我一样的新人对于Spring的源码还是了解的不深,但是看过几遍之后,觉得还是有意义的。
众所周知,Spring的核心特性就是IOC和AOP
IOC(控制反转)是依赖倒置原则的一种代码设计思路。就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现。IOC容器主要通过注解和XML的形式来配置类之间的依赖关系,降低了类之间的耦合关系。IOC
AOP(面向切面编程)则是Spring的另一个核心特征,它把一些与业务无关,却为业务模块所共同调用的逻辑封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。它主要由JDK动态代理和CGLIB代理实现。
至于Spring bean的声命周期,我们先看下图,然后再慢慢讲。
因为是面试,我们不用讲的非常细,心中有图然后翻译出来就行了,具体的内容,大家可以自己打开编译工具,然后找到这个类ClassPathXmlApplicationContext,找到这个方法往下看就行
Bean的生命周期:
createBeanInstance() -> 创建实例
populateBean() -> 属性赋值
initializeBean() -> 初始化
1、在doCreateBean方法中调用createBeanInstance进行实例化
2、还是在同一个地方,赋值并初始化我们的bean
但是Bean生命周期内的拓展点非常的多,远远不是上文这一点。 我们可以这样总结一下:
代码语言:javascript复制1、实例化Bean对象
2、设置Bean属性
3、如果通过各种Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖。
Aware接口集体包括BeanNameAware、BeanFactoryAware和ApplicationContextAware
分别注入Bean ID、Bean Factory 和ApplicationContext
4、如果实现了BeanPostProcesser,调用BeanPostProcesser的前置初始化方法postProcessBeforeInitialization
5、如果实现了InitializingBean接口,则会调用afterPropertiesSet方法
6、调用Bean自身定义的init方法
7、调用BeanPostProcesser的后置方法postProcessAfterInitialization
创建完毕
如果我们要销毁的销毁话
8、容器关闭前调用DisposableBean的destroy方法和自身的destroy方法
二、你讲的很精彩,那么能不能顺便讲一讲AOP的实现方式?
AOP的实现主要是通过JDK动态代理和CGLIB代理来实现,这两种方法随着java的发展,在效率上已经没有多少区别,主要的不同点在于一个是JRE提供给我的,一个是需要依赖第三方jar包实现。
不,这不是最重要的区别。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,被JDK动态代理的类必须实现接口。
而CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。因为才用的方法是继承,所以该类或者方法最好不要声明成final的。
三、可以讲一讲HashMap的底层原理吗?
这道题真的可以说是必考。。。或许每个面试官都回答过这个问题?
首先,我们知道HashMap是一个集合,键值对的集合,源码中每个节点用Node表示。
其次,底层通过数组 链表/红黑树来实现。
HashMap的初始容量是16,负载因子是0 75,扩容方式为newsize = oldsize*2,size大小为2的n次幂 扩容后一定会变成2的n次幂 每次扩容时,原数组中元素依次重新计算存放位置,并重新插入。
HashMap在JDK1.7版本的头插法实现元素插入,到了JDK1.8版本升级成尾插法,为什么这样?因为采用头插法可能出现死链。同时,1.8还是先插入后扩容。
讲到了红黑树,我们再来讲讲为什么用红黑树?
红黑树性质 性质1:每个节点要么是黑色,要么是红色。 性质2:根节点是黑色。 性质3:每个叶子节点(NIL)是黑色。 性质4:每个红色结点的两个子结点一定都是黑色。 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
因为比平衡树快:
代码语言:javascript复制(1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。
(2)红黑树更适合于插入修改密集型任务。
(3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。
四、不错,不知道你有没有听过G1垃圾回收器?
G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。
G1和CMS一样,采用的是分代回收机制。
**Young GC ** 当Eden区的空间占满之后,会触发Young GC,G1将Eden和Survivor中存活的对象拷贝到Survivor,或者直接晋升到Old Region中。Young GC的执行是多线程并发的,期间会停顿所有的用户线程(stop-the-world)。
Old GC / 并发标记周期
- 1、初始标记:这个阶段会stop-the-world,它伴随着一次Young GC,然后对Survivor区的对象进行标记
- 2、扫描根引用区:从上一阶段标记的根区域中,标记所有拥有老年代对象引用的存活对象,这是一个并发的过程,而且必须在进行下一次Young GC之前完成
- 3、并发标记:寻找整个堆的存活对象。这个阶段是并发执行的,中间可以发生多次 Young GC,Young GC 会中断标记过程
- 4、重新标记:这个阶段会stop-the-world,同时完成最后的存活对象标记。它使用了比 CMS 收集器更加高效的 snapshot-at-the-beginning (SATB) 算法。这个阶段会回收完全空闲的区块
- 5、复制/清除:G1统计存活对象和完全空闲的区域,完全空闲区域将被重置回收。
五、你对消息中间件了解多少
在工作中,比较常见的是rabbitmq、kafka、Rocketmq这几种消息中间件,我所在的公司现在用的是rabbitmq。 面试官问我们这个问题,其实是希望我们讲讲工作中的业务场景,这个业务场景中为什么要使用消息中间件。
消息中间件看起来很复杂,其实核心说起来就是:解耦、异步、削峰。
解耦:RPC的调用是强依赖的,系统之间的调用会存在耦合,解耦就是把本来耦合在一起的几个系统给分开,通过消息中间件可以将业务上弱依赖、系统调用上的强依赖关系进行解耦。
异步:其实还是上一步解耦的延生,原先业务上是同步代码采用中间件进行异步操作以后,系统的响应耗时大大降低。
削峰:MQ会提供两种模式消息获取方式:PUSH推模式,PULL拉模式,消息中间件根据自己的处理能力,每隔一定时间,或者每次拉取若干条消息,实施流控,达到保护自身的效果。
但是,引入了消息中间将势必会增加系统的复杂性,需要考虑更多的问题:如何保证消息没有重复消费?如何处理消息丢失的情况?如何保证消息传递的顺序性。
六、哦?那你就讲讲如何保证消息没有重复消费?如何处理消息丢失的情况?如何保证消息传递的顺序性?
诱引面试官问你擅长的问题,但是很多时候面试官都会一笑而过,假设今天没有一笑而过
如何保证消息没有重复消费?
有时候重启系统、重启应用等突发事件发生时可能会无可避免的产生MQ的消息重复推送。
但是重复消费并不可怕,只要我们考虑到消息的幂等性就行了。
如何保证幂等性? 比如我们可以在写数据库的时候,根据主键查询一下,如果有,那么就说明已经消费过了。 或者可以用redis来写操作,redis的set有天然的幂等性。 比较常用的就是生成一个全局唯一的订单ID,到了消费者这里,可以根据这个ID进行判断。
如何处理消息丢失的情况?
消息丢失主要有三种情况,第一是生产者丢失了数据,第二是MQ丢失了数据,第三是消费者弄丢了数据,这次我就以rabbitmq来举例子讲讲如何处理消息丢失的情况。
一般来说。如果我们要确保生产者消息不丢失,可以开启confirm模式,在生产者那里设置开启confirm模式之后,每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给我们回传一个ack消息,告诉我们这个消息ok。如果rabbitmq没能处理这个消息,会回调我们一个nack接口,告诉我们这个消息接收失败,我们可以重试。而且我们可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么我们就可以重发。
面对rabbitmq自己弄丢数据这个问题,我们必须开启rabbitmq的持久化,就是消息写入之后会持久化到磁盘,哪怕是rabbitmq自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq还没持久化,自己就挂了,可能导致少量数据会丢失,但是这个概率较小。
如果消费者丢失了消息,这个时候可以用rabbitmq提供的ack机制,简单来说,就是我们关闭rabbitmq自动ack,可以通过一个api来调用就行,然后每次我们自己代码里面确保处理完的时候,再程序里ack一次。这样的话,如果我们还没处理完就没有ack了。这个时候rabbitmq就会认为我们还没处理完,这个时候rabbitmq就会把这个消息分配给别的消费者去处理,消息是不会丢失的。
如何保证消息传递的顺序性
如果我们有一个queue,多 consumer。生产者向 RabbitMQ 里发送了三条数据,顺序依次是 ABC,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ中消费这三条数据中的一条,结果消费者2先执行完操作,把B存入数据库,这样就造成了消费顺序混乱。
我们可以拆分多个 queue,每个queue对应一个 consumer。或者可以一个queue只对应一个 consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理。
时间也不早了,今天的面试就到这里吧。
关于面试题这个专题,我会慢慢夹杂在java专题内更新,一次不会更新很多题,但是都是一些我或者其他小伙伴遇到的实际真题,面试题的内容也不会写的很底层,很专业,只是尽量在短时间内说清楚问题就好了。如果大家有什么遇的高频题目,也可以给我留言,我会一起写到这个专题里面。
我的微信: