2020Java面试题整理
1.SpringAOP
spring项目中的事务,安全,日志等功能都可以通过AOP技术实现
AOP proxy底层基于JDK的动态代理或者cglib字节码操作技术,运行时,动态生成被调用类型的子类,并实例化代理对象,实际的方法会被代理给响应的代理对
象
核心概念:
1)Aspect切面,横跨多个类,Spring框架创建Advisor来指代它,内部包括切入实际(Pointercut)和切入动作(Advice)
2)Joint Point , 在Spring中只有方法可以作为切入点
3)Advice 定义切面中采取的动作,指明要做什么 , 通过Before/After/Around来指明什么时候做
4)Pointcut 具体定义Aspect中使用那些Join Point
2.Mybatis缓存
一级缓存 : 同一个sqlSession对象,在参数和SQL完全一样的情况下,只执行一次SQL语句,默认开启 , 不能关闭
二级缓存 : 在SqlSessionFactory生命周期中 , 需要手动开启
二级缓存全局开启
<setting name="cacheEnabled"value="true"/>
二级缓存关闭默认
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
3.servlet生命周期
1)web容器加载servlet类并实例化
2)运行init方法并初始化
3)用户请求该servlet , 请求到达服务器时 , 运行servlet方法
4)service方法运行与请求对应的doGet/doPost
5)调用destroy销毁实例
4.springMVC的理解
核心组件:
DispatcherServlet : 作为前端控制器 , 整个流程的控制中心 , 控制其他组件的执行 , 统一调度 , 降低组件之间的耦合性 ,提高每个组件的拓展性.
Handler : (Controller)后端控制器 , 负责处理请求的控制逻辑 .
HandlerMapping : 映射器对象 , 用于管理url 和 对应Controller 的映射关系 .
HandlerAdapter : 适配器 , 主要处理方法参数 , 相关注解 , 数据绑定 , 消息转换 , 返回值 , 调用视图解析器 等等.
ViewResolver : 视图解析器 , 解析对应的视图关系 .
执行流程 :
1) 一个请求匹配前端的控制器 DispatcherServlet 的请求映射路径
2)DispatcherServlet接收到请求后 , 根据请求信息交给映射器处理(HandlerMapping)
3)HandlerMapping 根据用户url 查找匹配的handler , 并返回一个执行链 HandlerExecutionChain
4)DispatcherServlet 再请求适配器 (HandlerAdapter) ,调用相应的Handler进行处理并返回 ModelAndView给DispatcherServlet
5)DispatcherServlet 将ModelAndView 交给viewResolver解析 , 获取具体view
6)DispatcherServlet 对view进行渲染视图
7)DispatcherServlet将页面响应给用户
5.SpringBean的生命周期
1)实例化bean对象
2)设置bean属性
3)如果通过各种Aware接口声明了依赖关系 , 则会注入bean 对容器基础设施层的依赖. Aware接口包括BeanNameAware , BeanFactoryAware , ApplicationContext 分别注入bean Id , beanFactory 和 applicationcontext
4) 如果实现了BeanPostPorcesser , 调用BeanPostPorcesser 的前置初始化方法postProcessorBeforeInitialization
5)如果实现了InitializingBean接口 , 则会调用afterPropertiesSet方法
6)调用bean自身定义的init方法
7)调用beanPostPorcesser的后置方法postProcessorAfterInitialization
8)创建完毕
9)销毁 , 调用DisPosableBean 的destroy方法和bean自身的destroy销毁方法
6.Java实现线程同步的方法
主要有以下三种:
1.使用同步代码块 ;
2.使用同步方法;
3.使用互斥锁ReetrantLock(更灵活的代码控制);
import java.util.concurrent.locks.ReentrantLock;
public class SyncThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable {
private int tickts = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (tickts > 0) {
// 方法一:使用同步代码块
synchronized (this) {
System.out.println("第" + (tickts--) + "张票售出");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 方法二:使用同步方法达到线程安全
saleTickts();
// 方法三:使用ReentrantLock,更加灵活,可以加入判断,在特定的情况下释放锁
saleTickts2();
}// for
}
// 同步方法:同步的是当前对象
private synchronized void saleTickts() {
if (tickts > 0) {
System.out.println("第" + (tickts--) + "张票售出");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}// saleTickts
ReentrantLock lock = new ReentrantLock();
// 方法三:使用ReentrantLock,更加灵活,可以加入判断,在特定的情况下释放锁
private void saleTickts2() {
lock.lock();
try {
if (tickts > 0) {
System.out.println("第" + (tickts--) + "张票售出");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 保证锁一定会被释放,不会出现死锁情况
} finally {
lock.unlock();// 释放锁
}
}// saleTickts2
}
7.1Spring中的设计模式
1)BeanFactory和ApplicationContext应用了工厂模式;
2)在Bean的创建中,Spring提供了单例和原型等模式;
3)AOP领域使用的是代理模式 , 装饰器模式 , 适配器模式 ;
4)事件监听使用的是观察者模式 ;
5) 类似JdbcTemplate等模板对象使用的是模板模式;
7.2设计模式的应用
Builder 建造者模式 , 特点是不通过New的方式创建对象 , 而是通过其他的类来创建对象 . 例如MyBatis 的SqlSessionFactoryBuilder 创建SqlSessionFactory ;
8.Springboot 自动配置的原理
从@SpringBootApplication源码可查看到三个关键注解;
@SpringBootConfiguration 代表一个配置类 , 相当于bean.xml , 在自定义配置类中的@Bean 作用于方法或者注解类上 , 表示给容器注入一个bean , 类型为方法的返回值 , 默认id是方法名 , 可以指定BeanId 例如:@Bean(“beanId”)
@ComponentScan 自动扫描并加载符合条件的组件或者Bean定义 , 最终将这些Bean定义加载到容器中
@EnableAutoConfiguration 开启自动装配 .从classpath中搜寻所有的META-INF/spring.factories配置文件,有大概130行的配置,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
9.数据库优化
1)选择字段合适的属性 , 尽可能减少定义字段的宽度 , 尽量把字段设置成NOTNULL , 能用数字类型 ,不用字符串类型 ,创建合适的索引 , 索引列尽可能使NOTNULL;
2)使用连接查询代替子查询 ;
3)使用exists , not exists 代替 in not in ;
4)避免在索引使用计算 ;
5)在where 和 order by 常用列上创建索引 ;
6)避免在where 子句对字段进行 null 值判断 , 避免进行字段表达式的操作 , 会导致搜索引擎放弃使用索引进行全表扫描 ;
7) 建表的时候使用正确的存储引擎;
8)max 函数字段可以创建索引;
9)count(字段) 不包含null , count(*)包含null;
10.如何让堆栈内存溢出
不断创建对象导致堆内存溢出;
递归调用导致栈内存溢出;
11.Mybatis工作原理
Mybatis 核心组件:
- Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
- SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
- Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
- ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
- ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
- TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
- MappedStatement MappedStatement维护一条
- SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql 表示动态生成的SQL语句以及相应的参数信息
工作流程:
1.在 MyBatis 运行开始时需要先通过 Resources 加载全局配置文件.
2.下面 需要实例化 SqlSessionFactoryBuilder 构建器.帮助 SqlSessionFactory 接 口实现类 DefaultSqlSessionFactory.
3.在实例化 DefaultSqlSessionFactory 之前需要先创建 XmlConfigBuilder 解析全局配置文件流,并把解析结果存放在 Configuration 中.之后把 Configuratin 传递给 DefaultSqlSessionFactory.到此 SqlSessionFactory 工 厂创建成功.
4.由 SqlSessionFactory 工厂创建 SqlSession. 每次创建 SqlSession 时,都需要由 TransactionFactory 创建 Transaction 对象,同时还需要创建 SqlSession 的执行器 Excutor,最后实例化 DefaultSqlSession,传递给 SqlSession 接口.
5.根据项目需求使用 SqlSession 接口中的 API 完成具体的事务操作. 如果事务执行失败,需要进行 rollback 回滚事务. 如果事务执行成功提交给数据库.关闭 SqlSession 到此就是 MyBatis 的运行原理
12.Java动态代理
分两种 : JDK 代理 和 cglib
JDK代理:
1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、利用JDKProxy方式必须有接口的存在。
4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。
cgLib代理:
1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
2、 用CGlib生成代理类是目标类的子类。
3、 用CGlib生成 代理类不需要接口
4、 用CGLib生成的代理类重写了父类的各个方法。
5、 拦截器中的intercept方法内容正好就是代理类中的方法体
区别 :
JDK动态代理智能对实现了接口的类生成代理对象 ;
cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理,如果目标对象被final修饰,那么该类无法被cglib代理。
spring两种代理方式
- 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口 - 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。
13.JVM相关
Jvm包括堆 , 栈 , 本地方法栈 , 程序计数器 , 方法区 (1.8后叫元空间 , 加入堆中,又要永久代 , 持久代)
jvm 栈 , 本地方法栈是线程不共享的 , 每个方法 , 每个线程 创建一个栈帧 , 栈帧中有 局部变量表 , 操作数栈 , 方法出口.
程序计数器 是线程记录线程的停止 , 开始 位置
堆包括新生代 , 老年代 , 元空间 ,优化主要是优化堆
新生代 默认占堆内存的1/3 包括 eden , fromservivor , toservivor , 新创建的对象默认放入Eden ,如果太大 , 直接放入老年代
GC 分younggc (minor GC) 和fullgc (major GC)
GC常用标记算法 可达性分析法 , GCroots 对象 , 当对象没有引用相连GCRoots ,将对象标记为不可用
GCroot是根节点对象 : 虚拟机栈的局部变量表 , 方法区静态属性引用的对象 , 方法区中常量引用的对象 , 本地方法引用的对象
垃圾回收算法 : 复制算法 , 标记-清除算法 , 标记-整理算法 , 分带收集法
标记清除算法 缺点 : 效率问题 , 标记和清除 两个过程的效率不高 ; 空间问题 , 标记清除后产生大量不连续碎片
新生代采用复制算法 将未回收的对象从fromservivor 到 toservivor , 每迁移一次年龄+1 , 当年龄15的时候 , 将对象从新生代移动到老年代 , 老年代采用标记整理算法 ;
垃圾回收器 , JDK1.8默认指定的垃圾收集器Paramllel Scavenge
还有G1 和CMS (多用于老年代)垃圾收集器 , G1收集量比CMS大 , 官方推荐G1收集器 , zgc 是目前最优秀的垃圾收集器 , 官方推荐G1收集器
JDK性能调优监控工具
命令 jps 查看所有Java 程序
jstart -gc pid 内存使用情况
jmap 可以在内存溢出的时候到处dump 文件
java visualVM 查看java 进程的可视化工具
jstack 用于生成线程快照
JVM调优思路
1.打印GC日志
-XX:+PrintGCDetails -XX:+PrintGCTimestamps -XX:+PrintGCDateStamps +Xloggc: ./gc.log
2.分析日志得到的关键性指标
3.分析GC原因 , 调优JVM参数
调优主要是调整一下两个指标 :
1.停顿时间 : 垃圾回收器做垃圾回收中段应用执行的时间 -XX:MaxGCPauseMills
2.吞吐量 : 垃圾收集的时间和总时间的占比 -XX:GCTimeRatio
调优方法 :
- 设置元空间Metaspace 大小 , 增大元空间 -XX:Metaspace.size=128M -XX:MaxMetaspace.size=128M
- 增大老年代动态扩容量, 一般是20%
- 配置垃圾回收器 -XX:+UseConcMarkSweepGC/-XX:+UseG1GC
14.线程池创建参数
线程池创建拒绝用Excutors 而使用方法:
ExecutorService pool1 = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
ExecutorService pool2 = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
ExecutorService pool3 = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
ExecutorService pool4 = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
其参数入下:
corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
unit:keepAliveTime的单位
workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
threadFactory:线程工厂,用于创建线程,一般用默认即可;
handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
拒绝策略 :
1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
15.面向对象的六原则一法则
(1)单一职责原则:一个类只做它该做的事情。其核心就是我们常说的”高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”,一个对象如果承担太多的职责,那么注定它什么都做不好。
(2)开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:
①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;
②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。
(3)依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
(4)里氏替换原则:任何时候都可以用子类型替换掉父类型。简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。
需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。
(5)接口隔离原则:接口要小而专,绝不能大而全。
(6)聚合复用原则:优先使用聚合关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码。例如Properties类继承了Hashtable类,API中原话:因为 Properties继承于 Hashtable,所以可对Properties 对象应用put 和putAll 方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是String 的项。相反,应该使用setProperty 方法。这个继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据。
(7)迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。
16.一次Http请求的全过程
1、TCP建立连接
HTTP协议是基于TCP协议来实现的,因此首先就是要通过TCP三次握手与服务器端建立连接,一般HTTP默认的端口号为80;
2、浏览器发送请求命令
在与服务器建立连接后,Web浏览器会想服务器发送请求命令
3、浏览器发送请求头消息
在浏览器发送请求命令后,还会发送一些其它信息,最后以一行空白内容告知服务器已经完成头信息的发送;
4、服务器应答
在收到浏览器发送的请求后,服务器会对其进行回应,应答的第一部分是协议的版本号和应答状态码;
5、服务器回应头信息
与浏览器端同理,服务器端也会将自身的信息发送一份至浏览器
6、服务器发送数据
在完成所有应答后,会以Content-Type应答头信息所描述的格式发送用户所需求的数据信息
7、断开TCP连接
在完成此次数据通信后,服务器会通过TCP四次挥手主动断开连接。但若此次连接为长连接,那么浏览器或服务器的头信息会加入keep-alive的信息,会保持此连接状态,在有其它数据发送时,可以节省建立连接的时间;
17. js 闭包
闭包是指有权访问另外一个函数作用域中的变量的函数。可以理解为(能够读取另一个函数作用域的变量的函数)
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
使用场景
1、读取函数内部的变量
2、让这些变量的值始终保持在内存中。不会再f1调用后被自动清除。
3、方便调用上下文的局部变量。利于代码封装。
原因:outer是inner的父函数,inner被赋给了一个全局变量,inner始终存在内存中,inner的存在依赖outer,因此outer也始终存在内存中,不会在调用结束
18.Runnable()和Callable()的区别
1.区别点:
Callable规定的方法是call(),Runnable规定的方法是run();
Callable的任务执行后可返回值,而Runnable的任务是不能返回值;
call方法可以抛出异常,run方法不可以;
2.相同点:
两者都是接口;
两者都需要调用Thread.start()启动线程;
19.concurrentHashMap数据结构
20.Java CAS
cas是compareandswap的简称,从字面上理解就是比较并更新,简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止
21.nginx负载均衡策略
a. 轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
配置方式:
b. weight
weight 代表权重, 默认为 1,权重越高被分配的客户端越多
upstream myserver {
server 208.208.128.122:8081 weight=10; # 在这儿
server 208.208.128.122:8082 weight=10;
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
c. ip_hash
ip_hash 每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器
upstream myserver {
ip_hash; // 在这儿
server 208.208.128.122:8081 ;
server 208.208.128.122:8082 ;
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
d. fair(第三方)
fair(第三方),按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream myserver {
server 208.208.128.122:8081 ;
server 208.208.128.122:8082 ;
fair; # 在这儿
}
server {
listen 80;
server_name 208.208.128.122;
location / {
root html;
proxy_pass http://myserver;
index index.html index.htm;
}
Redis 内存满了
Java 用过哪些锁
ArrayList扩容机制
eureka缺点