最近一直忙着面试(前面的几次面试给大佬留下了很深的印象所以后面就是我做了),面对各种java经验的人,或多或少的有一些不同的感触。
这不,最近一个三年经验的小哥哥给我留下了很深的影响,他面试的是我们的高级开发工程师,依照惯例我们先看看他的简历,简历写的还是很优秀的,各种技术都有或多或少的涉猎。
看完简历看看我都问了什么?先考虑下如果是你,能不能答得上来?
问题总汇
-
简述一下Spring的AOP和IOC
-
Spring通知(Advice)有哪些类型?
-
Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
-
那么解释Java堆空间及GC?
-
内存模型以及分区,需要详细到每个区放什么。
-
堆里面的分区:Eden,survival(from+to),老年代,各自的特点。
-
GC的两种判定方法
-
消息队列有什么优缺点?
-
RabbitMQ的高可用性如何保证?
面试部分
开场依旧是简单的,介绍在介绍中他提到了一些擅长的技术Spring和JVM以及MQ,所以我的问题也是偏向他擅长的部分。(PS:所以大家在面试的时候自我介绍介绍点自己擅长的部分会给自己后面的面试带来很大的优势哦)
我:刚才在你的自我介绍的时候你说你对Spring部分有一定的理解,那么能简述一下Spring的AOP和IOC么?
答:
首先IOC部分:
IOC就是控制反转,指创建对象的控制权转移给Spring框架进行管理,并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IoC容器来动态注入对象需要的外部依赖。
最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由Spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
Spring的IOC有三种注入方式:构造器注入、setter方法注入、根据注解注入。
然后是AOP部分:
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以SpringAOP为代表。
我:既然提到了AspectJ那么稍微说下吧。
答:
AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
我:Spring通知(Advice)有哪些类型?
答:
一般有5种
-
前置通知(BeforeAdvice):在连接点(Joinpoint)之前执行的通知。
-
后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
-
环绕通知(AroundAdvice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
-
返回后通知(AfterReturningAdvice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
-
抛出异常后通知(AfterThrowingadvice):在方法抛出异常退出时执行的通知
我:Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
答:
Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。
(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
-
有状态Bean(StatefulBean):就是有实例变量的对象,可以保存数据,是非线程安全的。
-
无状态Bean(StatelessBean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
(从这些可以看出他有一定的Spring功底于是我就转移了个话题。)
我:Spring的我大概了解了你的水平,我看你有写JVM的程度是了解,那么:解释一下Java堆空间及GC?
答:
当通过Java命令启动Java进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC是JVM内部的一个进程,回收无效对象的内存用于将来的分配。
我:讲下内存模型以及分区,需要详细到每个区放什么。
答:
JVM分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,class类信息常量池(static常量和static变量)等放在方法区。
-
方法区:主要是存储类信息,常量池(static常量和static变量),编译后的代码(字节码)等数据
-
堆:初始化的对象,成员变量(那种非static的变量),所有的对象实例和数组都要在堆上分配
-
栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操作数栈,方法出口等信息,局部变量表存放的是8大基础类型加上一个应用类型,所以还是一个指向地址的指针
-
本地方法栈:主要为Native方法服务
-
程序计数器:记录当前线程执行的行号
我:说下堆里面的分区:Eden,survival(from+to),老年代,各自的特点。
答:
堆里面分为新生代和老生代(java8取消了永久代,采用了Metaspace),新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to,当经过一次或者多次GC之后,存活下来的对象会被移动到老年区,当JVM内存不够用的时候,会触发FullGC,清理JVM老年区
当新生区满了之后会触发YGC,先把存活的对象放到其中一个Survice区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎片,因此一般会把Eden进行完全的清理,然后整理内存。那么下次GC的时候,就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下,就会使用老年代的担保,直接放到老年代里面。因为JVM认为,一般大对象的存活时间一般比较久远。
我:那GC的两种判定方法说一下。
答:
-
引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为0就会回收但是JVM没有用这种方式,因为无法判定相互循环引用(A引用B,B引用A)的情况
-
引用链法:通过一种GCROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,如果有一条链能够到达GCROOT就说明,不能到达GCROOT就说明可以回收
我:挺好的,那么我问下其他的点吧,对消息队列的了解怎么样?
答:还可以,在工作中有用到rebbitMQ。
我:消息队列有什么优缺点?
答:
-
优点:解耦、异步、削峰
-
缺点:系统可用性降低,系统复杂度提高,一致性问题,
我:那么你既然提到了系统可用性低,RabbitMQ的高可用性如何保证?
答:
abbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。
-
单机模式不存在高可用。
-
普通集群模式也不存在高可用性,意思就是在多台机器上启动多个RabbitMQ实例,每个机器启动一个。但是你创建的queue,只会放在一个RabbitMQ实例上,但是每个实例都同步queue的元数据。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个queue所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈。而且如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取,如果你开启了消息持久化,让RabbitMQ落地存储消息的话,消息不一定会丢,得等这个实例恢复了,然后才可以继续从这个queue拉取数据。
-
镜像集群模式的策略是高可用策略,指定的时候可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
后面我就问了一些个人相关的,例如:为啥换工作,以前的工作的评价等相关的。
他给我印象深刻是因为在回答的时候很有条例,几乎每一个技术点都能很好准确的答在我想要的点上甚至还能有所扩展,所以我给他的面试评价很高。
其实这些问题在1-5年的经验中也是经常问的,稍微想一想几乎大家都能或多或少答上来,那么大家看完之后是不是又有了新的收获,在面试中千万不要慌开场介绍很重要,要注意引导面试官来问你,而不是让他主动去发问,细节的准备要充分一些。
除了上面这份面经,我这里还整理了一整套包含市面上十来家大厂的的面试资料,还包含了Java基础知识,JVM,Mysql,并发,Spring,Mybatis,Redis,RocketMQ,Kafka,Zookeeper,Netty,Dubbo,ElasticSearch,Flink,Spring Boot,Spring Cloud,高并发项目,大数据系列,数据结构与算法,设计模式,网络与操作系统等20个技术栈的大厂面试题及详解文档。可以扫码领取