1.请描述JVM的内存模型
https://blog.csdn.net/xinzhou201/article/details/81981282
什么是线程独有的?
本地方法栈(native method):为虚拟机所使用的native方法服务,native方法是java通过JNI直接调用c/c++库 ,给链接连到的是c语言
程序计数器(program counter register),程序计数器是一块很小的内存空间,它是线程私有的,记录程序运行的行数
虚拟机栈(jvm stacks):每一个方法执行都会生成一个栈帧,栈帧里有局部变量区、操作数栈、动态链接、方法返回地址。
—栈帧:局部变量区(参数集合来源)、操作数栈(if)、动态链接(动态对象、参数集合)、方法返回地址
—栈帧大小确定时间: 编译期确定,不受运行期数据影响。
Ps:
大家公用的?
方法区/非堆:包含了元数据区和常量池,(元数据区在1.8以前叫永久代)用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存数据等
堆:存放对象的实例。同时它也是GC所管理的主要区域,因此常被称为GC堆,又由于现在收集器常使用分代算法。
—只要没地方扔放着,new放着,堆里面的可以被收回
2.HashMap的加载因子是多少?每次扩容多少,能说说为什么?
加载因子0.75,扩容容量翻倍,k扩容的地点是l*0.75;为了减少ReHash的次数,从而提高运行效率
3.线程状态变迁图
第一种:线程的五大状态
新建状态New,线程还没有运行,处于新生状态
就绪状态Runnable, start—>队列中,start(),run(),处于就绪状态
运行状态Running,获得cpu后它才进行运行状态,执行run()
阻塞状态 Blocked,sleep(),让出cpu时间不一定让出内存时间
结束状态Dead,run正常退出而自然死亡;一个为捕获的异常终止了run方法使线程猝死。
https://blog.csdn.net/peter_teng/article/details/10197785
第二种:线程的六大状态(waiting(没时间)\timed waiting(有时间))
Java的六种状态分析
https://www.jianshu.com/p/ec94ed32895f
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
Thread.sleep; object.wait;
4.TCP状态迁移图
三次握手?
服务端先开启一个监听端口,变为LISTEN状态。
客户端发一个SYN请求,变为SYN_SENT状态。
服务端接收SYN请求,并返回一个ACK和SYN,此时变为SYN_RECV状态。
客户端接收SYN和ACK,并返回一个ACK,此时变为ESTABLISHED状态。
服务端接收ACK,变为ESTABLISHED状态。
四次挥手?
先发FIN,发完FIN变成FIN_WAIT_1的状态(主动关闭)
(1)收到FIN后发出一个ACK才算完,变成CLOSING等待,收到对方发过来的ACK(发送无)后直接进入TIME_WAIT,2MSL超时直接关闭就行。
(2)收到ACK,不确定对面是不是发完了,所以进入FIN_WAIT_2状态,等待你发完给我回FIN,我就收完了,给你回个ACK;然后i进入TIME_WAIT 2MSL超时自动关闭。
(3)收到FIN + ACK,说明对面也收完法完了,直接回个ACK进入TIME_WAIT 2MSL关闭。
被动关闭
如果收到FIN,我就发一个ACK变成CLOSE_WAIT状态,等我数据发完我再发一个FIN代表我发送结束,到LAST_ACK的状态,到我收到对方发来的ACK关闭。
为什么三次握手四次挥手?
断开的发起者可以是双方,但是链接的发起者只能是客户端;第二,链接之前是没有状态的,没有数据的,但是断开的时候能确认自己发完,不能确认对方发完了没,自己收完了没,存在几种状态。
5.请解释一下BIO,NIO,AIO之间的区别
原文链接:https://blog.csdn.net/weixin_43905219/article/details/105272912
(1)BIO是一种同步阻塞的IO流,一个线程负责一个IO
ps:用户线程会等待到它的IO执行完毕才能继续执行,当IO完成,用户线程继续执行,开始处理下一个IO。
烧水的开始、结束顺序都是A->B->C。
(2)NIO是一种同步非阻塞的IO流,有专门的几个线程来负责IO,一个线程负责多个IO。
Ps:用户线程会等待到它的IO执行完毕才能继续执行,多个IO中任意一个IO完成,这个IO对应的用户线程就可以解除等待继续进行。也就是说,多个用户线程进行IO时,不需要按顺序进行,而是同时进行,谁的IO先完成,谁就可以先解除阻塞继续执行,用户线程B的IO完成后,用户线程B就可以继续进行。
烧水的开始顺序是:A->B->C
烧水的结束顺序是:B->C->A
selector(多路复用器)选择要用的客户端,selector选择器(Channel通信管道,Buffer解决等待问题;channel和selector解决了一个线程对一个io的问题),IO多路复用,所有的字符流自带buffer
(2)AIO:AIO是一种异步非阻塞的IO流,也是有专门的几个线程来负责IO,一个线程负责多个IO。核心:FutureTask
Ps:关键在于用户线程不会阻塞,进行IO时不需要等待IO执行完毕,而是直接返回,用户线程继续向下执行,当IO执行完毕时,使用回调机制,进行后序的操作。
烧水的开始顺序是:A->B->C
烧水的结束顺序是:B->C->A
6.NIO如何做到非阻塞的?
答:NIO使用三个核心组件:Selector、Channel、Buffer去相互配合实现的非阻塞过程,将Channel注册到Selector中,然后Selector去轮询所有的channel,当监听到需要进行IO操作时,就获取这个想要进行IO操作的channel去处理IO请求,然后将数据放入Buffer中。
7.Callable和Runnable之间有什么区别
(1)相同点:
----两者都是接口
----两者都需要调用Thread.start启动线程
(2)区别:
----callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值
----call方法可以抛出异常,但是run方法不行
----因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常
----callable和runnable都可以应用于executors。而thread类只支持runnable
8.请解释偏向锁,轻量级锁,自旋锁的区别
—下面四个锁一直在升级,越低级的锁效率越高 –
**偏向锁:**它会偏向于第一个访问锁的线程。
轻量锁:轻量锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
自旋锁:由于还存在竞争,当前线程不停的在循环体内执行实现,执行称为自旋锁。
重量锁:自旋锁循环一定的次数后升级称为重量级锁。
答:偏向锁的优点是加解锁不需要额外消耗,和执行非同步方法比仅存在纳秒级差距,缺点是如果存在锁竞争会带来额外锁撤销的消耗,适用只有一个线程访问同步代码块的场景。
轻量级锁的优点是竞争线程不阻塞,程序响应速度快,缺点是如果线程始终得不到锁会自旋消耗 CPU,适用追求响应时间、同步代码块执行快的场景。
自旋锁的优缺点:如果锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起操作的消耗!
但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cup的线程又不能获取到cpu,就会造成cpu的浪费。
重量级锁的优点是线程竞争不使用自旋不消耗CPU,缺点是线程会阻塞,响应时间慢,适应追求吞吐量、同步代码块执行慢的场景。
9.什么情况下会出现死锁?如何解除死锁?
-----产生死锁的四个必要条件:
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
只要破坏死锁成立的条件之一,就解除了死锁。
10.事物的隔离级别有哪些?
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是 如果事务A已经开始写数据,则事务B不允许同时进行写操作,但允许其读此行数据,当这个事务未提交时,非事务读取的是原库的数据。
读已提交(read-committed) 否 是 是 如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。
可重复读(repeatable-read) 否 否 是 指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读
同步(serializable) 否 否 否 同时只能一个事务进行修改,操作使用同步锁,事务只能一个一个执行
11.Spring事物的传播特性有哪些?(有七种,但通常讨论5种)
required:表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务
support:表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
required new:示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
not support:表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
never:表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
mandatory必须当前有事务否则异常、nested嵌套
12.Spring的核心内容有哪些?
AOP(面向切面编程):利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
IOC(控制反转):现将组件间的关系从程序内部提到外部容器(spring的xml)来管理。
首先外部容器(spring.xml)中会动态的注册业务所需的对象(接口/类)
DI(依赖注入):组件之间的依赖关系由容器在应用系统运行期来决定, 也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中
13.请描述SpringMVC的工作原理
SpringMVC工作流程概述:
1、客户端向web服务器(如tomcat)发送一个http请求,web服务器对http请求进行解析,解析后的URL地址如果匹配了DispatcherServlet的映射路径(通过web.xml中的servlet-mapping配置),web容器就将请求交给DispatcherServlet处理。
2、DispatcherServlet接收到这个请求后,再对URL进行解析,得到请求资源标识符(URI)。然后调用相应方法得到的HandlerMapping对象,再根据URI,调用这个对象的相应方法获得Handler对象以及它对应的拦截器。(在这里只是获得了Handler对象,并不会操作它,在SpringMVC中,是通过HandlerAdapter对Handler进行调用、控制的)
3、DispatcherServlet根据得到的Handler对象,选择一个合适的HandlerAdapter,创建其实例对象,执行拦截器中的preHandler()方法。
4、在拦截器方法中,提取请求中的数据模型,填充Handler入参,所以所有准备工作都已做好,开始执行Handler(我们写的controller代码并不是能被直接执行,需要有刚才那些操作,才能转变为Handler被执行)。
5、Handler执行完毕后返回一个ModelAndView对象给DispatcherServlet。
6、这个ModleAndView只是一个逻辑视图,并不是真正的视图,DispatcherServlet通过ViewResolver视图解析器将逻辑视图转化为真正的视图(通俗理解为将视图名称补全,如加上路径前缀,加上.jsp后缀,能指向实际的视图)。
7、DispatcherServlet通过Model将ModelAndView中得到的处数据解析后用于渲染视图。将得到的最终视图通过http响应返回客户端。
14.你了解的缓存算法,如何通过LinkedHashMap实现LRU过程
------缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每次访问一个元素后把这个元素放在 List一端,这样一来最远使用的元素自然就被放到List的另一端。缓存满了t的时候就把那最远使用的元素remove掉。但更实用的是HashMap。因为List太慢,要删掉的数据总是位于List底层数组的第一个位置,删掉之后,后面的数据要向前补位。。所以复杂度是O(n),那就用链表结构的LinkedHashMap呗~,LinkedHashMap默认的元素顺序是put的顺序,但是如果使用带参数的构造函数,那么LinkedHashMap会根据访问顺序来调整内部 顺序。 LinkedHashMap的get()方法除了返回元素之外还可以把被访问的元素放到链表的底端,这样一来每次顶端的元素就是remove的元素。
FIFO算法:先进先出,离目前数据最远的数据优先淘汰,
LRU算法:最近最少未使用,最近一段时间内,最少使用的优先被淘汰
LFU算法:最近不经常使用,最近被使用的时间,离目前最远的数据优先被淘汰。
LRU和LFU都是内存管理的页面置换算法。
LRU,即:最近最少使用淘汰算法(Least Recently Used)。LRU是淘汰最长时间没有被使用的页面。
LFU,即:最不经常使用淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的页面。
ps:也就是说: LRU算法适合:较大的文件比如游戏客户端(最近加载的地图文件) LFU算法适合:较小的文件和教零碎的文件比如系统文件、应用程序文件 其中:LRU消耗CPU资源较少,LFU消耗CPU资源较多。
import java.util.*;
//扩展一下LinkedHashMap这个类,让他实现LRU算法
class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
//定义缓存的容量
private int capacity;
private static final long serialVersionUID = 1L;
//带参数的构造器
LRULinkedHashMap(int capacity){
//调用LinkedHashMap的构造器,传入以下参数
super(16,0.75f,true);
//传入指定的缓存最大容量
this.capacity=capacity;
}
//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
System.out.println(eldest.getKey() + “=” + eldest.getValue());
return size()>capacity;
}
}
//测试类
class Test{
public static void main(String[] args) throws Exception{
//指定缓存最大容量为4
Map<Integer,Integer> map=new LRULinkedHashMap<>(4);
map.put(9,3);
map.put(7,4);
map.put(5,9);
map.put(3,4);
map.put(6,6);
//总共put了5个元素,超过了指定的缓存最大容量
//遍历结果
for(Iterator<Map.Entry<Integer,Integer>> it=map.entrySet().iterator();it.hasNext();){
System.out.println(it.next().getKey());
}
}
}
输出结果如下
9=3
9=3
9=3
9=3
9=3
7
5
3
6
分析一下:使用带参数构造器,且启用LRU模式的LinkedHashMap会在每次有新元素加入的时候,判断当前储存元素是否超过了缓存上限,也就是执行
一次removeEldestEntry方法,看最后的遍历结果,发现果然把9删除了,LRU发挥作用了~
https://blog.csdn.net/Apeopl/article/details/90137398?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase
15.为什么LRU使用LinkedHashMap实现更加方便?
答:因为LInkedHashMap和HashMap的整体结构和实现原理很像,且LinkedHashMap整体是一个双向链表,基于这种实现特性,LinkedHashMap 在迭代遍历时,取得键值对的顺序的依据是其插入次序或者是最近最少使用(LRU)的次序。对于我们想要删除的数据可以很方便的删除,LinkedHashMap可以保证稳定性。当某个位置被命中,它就会通过调整链表的指向,将该位置调整到头位置,新加入的内容直接放在链表头,如此一来,最近被命中的内容就向链表头移动,需要替换时,链表最后的位置就是最近最少使用的位置。
删除数据方便,且是一个HashMap,因为缓存必定是一个kv对存放,
16.缓存能干什么?
----缓存就是数据交换的缓冲区(称作:Cache)--------
答:缓存可以将读取频率高的数据放入到缓存中,再次读取时从缓存中读取,以降低从数据库读取的次数来提高运行的效率
当硬盘处于运行时,缓存可以将要写入的数据先存放在缓存里,当硬盘空闲时,再从缓存中读取出来写入到硬盘中。
17.什么是指令重排序?
答:指令重排序就是因为cpu速度高于高速缓存速度,cpu为了提高执行的效率,JVM虚拟机按照自己内部的规则对指令进行了重排序。
18.如何避免指令重排序?
答:使用内存屏障可以避免指令重排序,
java中使用volatile或synchronized实现内存屏障
19.JVM的GC算法有多少种?
答:现代虚拟机算法分为标记清理算法,标记复制算法,标记整理(标记压缩)算法。
20.JVM的GC分类有多少中?
答:年轻代垃圾回收器:
Serial收集器,ParNew收集器,Parallel Scavenge收集器
老年代垃圾回收器:
Serial Old收集器,Parellel Old收集器,CMS收集器
G1收集器:面向堆内任何部分进行回收
21.Jsp 9大内置对象都是什么?
全称Java server pages,就是Java服务器页面。
JSP中九大内置对象为:
pageContext 页面上下文对象 类型 javax.servlet.jsp.PageContext 作用域 Page
request 请求对象 类型 javax.servlet.ServletRequest 作用域 Request
response 响应对象 类型 javax.servlet.SrvletResponse 作用域 Page
session 会话对象 类型 javax.servlet.http.HttpSession 作用域 Session
application 应用程序对象 类型 javax.servlet.ServletContext 作用域 Application
out 输出对象 类型 javax.servlet.jsp.JspWriter 作用域 Page
config 配置对象 类型 javax.servlet.ServletConfig 作用域 Page
exception 例外对象 类型 javax.lang.Throwable 作用域 page
page 页面对象 类型javax.lang.Object 作用域 Page
四个域对象:pageContext – page域
request – request域
session – session域
application – context域
22.EL表达式查找数据的顺序是什么?
答:EL表达式查找顺序是按照从小到大的顺序来获取的
page—>request—>session—>application
${application.username}
23.请解释一下拦截器,你都在哪些框架里面见过拦截器?
拦截器定义: 拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
Mybatis的拦截器
SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的。
SpringBoot中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的
24.请解释一下AOP的原理,什么是动态代理,Spring通过哪些过程可以实现AOP过程?
答: AOP分为静态的AOP和动态AOP,静态AOP是指AspectJ实现的AOP,它将切面代码直接编译到java类文件中,动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP实现原理是动态代理;
所谓的动态代理就是程序在运行时创建代理类和代理对象,而不是像静态代理一样提前定义好一个代理类,动态代理分为JDK动态代理和CGLB动态代理;
AOP 是基于动态代理模式实现的,其中 JDK 动态代理只能代理实现了接口的对象,而 Cglib 动态代理则无此限制。,JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现InocationHandler接口;CGLB是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意CGLB是通过继承的方式做的动态代理,所以如果某个类时被标记为final,那么这个类就无法使用CGLB做动态代理; 实现接口的前提下,默认使用的是JDK动态代理,如果没有实现接口,则必须采用的是CGLB的动态代理,两种方式都不能使用private和final去修饰。