一、 Redis缓存的删除策略
-
算法
FIFO First in First out 先近先出。删除进入redis最早的key。
LRU Least Recently Used 最近最早被使用。删除最近一段时间内,被使用过的使用时间离现在最远的key。
LFU Least Frequently Used 最近最不经常使用。删除最近一段时间内,使用频率最少的key。 -
具体策略,当有新key要写入却空间不足时,redis执行的操作
noeviction 不删除任何key,返回错误信息。
allkeys-lru 从所有key中选择最近最早被使用的key删除。
volatile-lru 从设置过有效期的key中选择最近最早被使用的key删除。
allkeys-lfu 从所有key中选择最近最不常使用的key删除。
volatile-lfu 从设置过有效期的key中选择最不常使用的key删除。
volatile-ttl 从设置过有效期的key中选择最快要到期的key删除。
二、springboot启动流程
- 创建SpringApplication实例
1.1 判断是否为web环境
1.2 设置初始化器:从META-INF/spring.factory处读取key为org.springframework.context.ApplicationContextInitializer的value,并进行实例化
1.3 设置监听器
1.4 推断应用入口 - 执行SpringApplication.run()
2.1 获取SpringApplicationRunListener,启动监听
2.2 根据SpringApplicationRunListener以及参数来准备环境
2.3 创建ApplicationContext(spring上下文)
2.4 创建FailureAnalyzer,用于触发spring.factory加载的failureAnalyzer和FailureAnalysisReporter实例
2.5 spring上下文前置配置
2.6 spring上下文刷新
2.7 spring上下文后置处理
三、Spring的Bean是否为单例?线程是否安全?
- spring的bean有单例,但并不只是单例,有几种作用域:
1.1 singleton:单例。是默认作用域。
1.2 prototype:原型。在使用的时候每次都会创建一个新对象。
1.3 request:请求。每次http请求是创建一个新对象。适用于WebApplicationContext环境下。
1.4 session:会话。同一个会话使用同一个实例(不同会话使用不同实例)。
1.5 global-session:全局会话。所有会话共享一个实例。 - 具体说明
原型Bean,在每次使用的时候都需要新建一个对象,不存在多线程问题,所以是线程安全的。
单例Bean,是被所有线程共享的。如果是无状态的单例Bean(比如spring中的Dao,Service,Controller等,用@AutoWired,@Resource等注解注入的)就是线程安全的。只注重方法。如果是有状态的单例Bean(比如spring官方提供的Bean)则是有Spring官方提供了ThreadLocal去解决线程安全问题。
四、Java中是否存在内存泄露?举例
- Java中是存在内存泄露的`
pubilc void test(){
Person p1 = new Person();
Person p2 = new Person();
p1 = p2;
}
这么写的话,p1和p2,除了互相依赖,并没有实质性的操作,按理说应该被GC回收,
但当GC执行的时候,会发现p1和p2两个对象是一直占用的,并不能被回收。
其实就已经是内存泄露了。
五、JVM内存模型
- 堆内存(Heap)
虚拟机启动时创建,是虚拟机中占用内存最大的一块,被该进程中所有线程所共享。此内存的唯一作用就是存放对象实例,几乎所有的对象都是在堆中完成内存分配。
堆可以分为老年代和新生代。
老年代和新生代的比例为2:1,可通过 –XX:NewRatio 参数来指定。
新生代又分为Eden区,from和to区。Eden:from:to = 8:1:1,可通过–XX:SurvivorRatio参数来指定。 - 方法区(Method Area)
也称为永久代。用于存放虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。
JDK8之后,取消了永久代,可以将永久代称为元数据区(MetaSpace)。
以下为方法区配置:
-XX:PermSize=64MB 最小尺寸,初始分配
-XX:MaxPermSize=256MB 最大允许分配尺寸,按需分配
XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收
默认大小
-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m
-
虚拟机栈(JVM Stack)
是java方法执行时的内存模型。每个方法执行时都会创建一个栈帧,用于存储局部变量表(包括参数),操作栈,方法出口等信息。每个方法从被调用到执行完,就是一个栈帧在虚拟机中从入栈到出栈的过程。
所有参数,操作,返回值等在栈中的操作过程可以叫做压栈。先进入栈帧的会在栈最底部,后进入的会在最顶部。出栈的时候,会从顶部开始出,也就是所谓的先进后出,后进先出。 -
本地方法栈(Native Method Stack)
这块区域与虚拟机栈类似,只是虚拟机栈是为虚拟机中执行的Java方法服务的,而本地方法栈是为虚拟机中用到的Native方法服务的。 -
程序计数器(PC Register)
用于表示当前线程执行的字节码文件的行号指示器。
不被线程共享,多线程操作时,每个线程都有自己独立的程序计数器(如当一个方法未执行完,又有另一个线程插入执行另一个方法,当新方法执行完时,可以通过程序计数器标识的位置继续执行之前的方法)。
执行Java方法时,计数器中保存的是字节码文件中的行号,执行native方法是,计数器为空。 -
Java虚拟机启动参数配置
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-Xmn:设置年轻代大小
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
六、Java的类加载机制
- 要想理解Java的类加载机制,就需要先弄清楚以下几个概念:类加载,类的生命周期,类加载器,双亲委派
1.1 类加载:类加载就是将类的.class的二进制文件读取到内存中,并将其放入运行时数据区的方法区中(Perm区)。然后在堆中新建一个java.lang.Class对象,并封装这个类在方法区中的数据结构,作为方法区的访问入口。但是这时候并没有实例化对象。如果确实要使用这个类的属性或者方法,需要将该类实例化,比如new一个该类的对象,并由堆对其分配内存。
1.2 类的生命周期:简单来说,就是“加载 – 连接 – 初始化 – 使用 – 卸载”。
1.3 类加载:类加载器是Java运行时环境的一部分,负责动态的将Java类加载到Java虚拟机中。类通常是按需加载,即在类第一次使用的时候才会加载到虚拟机中。在类加载的时候,负责读取.class文件并将其转换成java.lang.Class的一个实例。
1.4 双亲委派:通俗的来说,就是当类加载器收到通知要对一个类进行加载时,会先去加载其父类,如果该父类还存在父类,则一直向上去寻找到顶层父类。如果此时,父类正在加载其他子类或者由于其他原因无法完成类的加载时,才会加载当前要加载的类。 - 类加载的具体过程
2.1 加载
2.1.1 通过类的全局限定名获取该类的二进制字节流
2.1.2 将这个类字节流代表的静态存储结构转化为方法区运行时数据结构
2.1.3 在堆中生成一个对应的java.lang.Class文件,作为方法区访问的入口。
2.2 校验
2.2.1 此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
2.2.2 校验内容
文件格式验证:基于字节流验证。
元数据验证:基于方法区的存储结构验证。
字节码验证:基于方法区的存储结构验证。
符号引用验证:基于方法区的存储结构验证。
2.3 准备
为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。
2.4 解析
2.4.1 目的:
将类型中的符号引用转化为直接引用。
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在
2.4.2 解析方式:
主要有以下四种:
类或接口的解析
字段解析
类方法解析
接口方法解析
2.5 初始化
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。
java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):
使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
虚拟机启动时,用户会先初始化要执行的主类(含有main)
jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。
标题七、数据结构之红黑树,平衡树,二叉树,B树,B+树
-
二叉树
1.1 左边比根节点小,右边比根节点大。
当插入有序序列时,二叉树退化成链表,所以会用平衡树。 -
平衡树
2.1 在插入的时候调整这棵树,让其节点尽量保持平衡 -
红黑树
3.1 是一种平衡树(如Java中的TreeSet),如果在内存中操作,效率比较高。
如果需要操作的数据量过大时,需要使用B树。 -
B树
4.1 通常叫做文件系统
B数加载的时候,一次只加载一个节点。
B树是一个多路搜索树。N路的B树结构有N个孩子节点,可以进一步降低树的高度。
如果在内存中操作的话,B效率更高,如果在内存中操作,则红黑树效率更高。 -
B+树
5.1 数据都在叶子节点上,又有链表结构,比较适合多条查询(如MySQL索引)
标题八、什么是RPC?
- 概念
RPC,Remote Procedure Call,远程过程调用。允许一台计算机程序远程调用另外一台计算机的子程序,不用关心网络底层调用。
2.序列化
通常有json,xml等。
3.常见RPC框架
dubbo,gRPC,Spring Cloud等。
九、TCP/IP和UDP
- 概念
1.1 TCP:Transmission Control Protocol,传输控制协议。TCP提供的是面向链接,可靠的字节流服务。即客户和服务器交换数据前,必须先在双方建立一个TCP链接,之后才能传递数据。并且提供超时重发,校验数据,流量控制等功能,保证数据能从一段发送到另一端。
1.2 UDP:User Data Protocol,用户数据报协议。是一个简单的面向数据包的运输层协议。不提供可靠性,只把应用程序发给IP层的数据包发送出去,但是不保证它们能到达目的地。由于UDP在传输前不在客户和服务器之间建立连接,且没有超时重发等机制,所以传输数据快。
标题十、.多线程
- 概念
1.1 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
1.2 线程:是进程的一个执行路径,一个进程至少有一个线程,一个进程中的多个线程共享线程中的资源。
1.3 关系:一个进程可以有多个线程,多个线程共享这个进程的堆和方法区资源,但是每个线程都有自己的栈和程序计数器。
1.4 区别:每个进程都有自己独立的地址空间,当一个进程崩溃时,在保护模式下不会对其他进程产生影响。线程是共享进程的堆和方法区的,没有自己独立的内存空间。当一个线程崩溃时,整个进程都会死掉。 - .Runnable和Thread的区别
2.1 并没有实质性的区别,只是写法上的区别而已。Runnable最终也会用到new Thread()。Thread实现了Runnable接口,并进行了扩展。 - .join(),sleep(),yield(),wait()
3.1 join()是让一个线程等待另一个线程执行完才继续执行。
3.2 sleep()是让当前线程先暂停一段时间,并进入阻塞状态。在其睡眠时间内,该线程不是就绪状态,就算当前进程无执行线程也不会立即执行。需等到睡眠时间结束后该线程才会进入就绪状态。
3.3 yield()是让当前线程进入就绪状态。
3.4 wait()是Object的方法,且不是静态的。
标题十一、MySQL事务隔离级别
数据库引擎当且仅当为InnoDb时,才会有事务。
5. 隔离级别
1.1 未提交读 A事务已执行,但未提交,B事务查询到A提交后的数据,A事务回滚。
后果:此时出现脏数据。
1.2 已提交读 A事务执行更新,B事务查询;A事务又执行更新,B事务再次查询是,前后数据不一致。
后果:不可重复读。
1.3 可重复读 A事务不论执行多少次,只要不提交,B事务查询到的结果都不变。B事务只查询B事务开始时的数据快照。
1.4 串行化 不允许读写并发操作。写执行时,读必须等待。
标题十二、HashMap和ConcurrentHashMap
- HashMap put的原理
1.1 如果hashMap未初始化,则执行初始化
1.2 对key求hash值,然后计算下标
1.3 如果没有碰撞,则直接放入桶中
1.4 如果碰撞了,则以链表的方式链接到后面
1.5 如果链表长度超过阈值,则将链表转换为红黑树
1.6 如果链表长度低于6,则将红黑树再次转换为链表
1.7 如果节点已经存在,则替换旧值
1.8 如果桶满了(容量16*加载因子0.75)则需要扩容,resize(扩容2倍后重排) - 为什么转换红黑树长度为8,转换链表长度为6?
2.1 红黑树平均查找长度为log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,查找长度为8/2=4,因为树查找更快。当长度小于等于6时,平均查找时间为6/2=3也很快,但是转换为树结构和生成树的会消耗时间。至于为什么会选择6和8,是因为在6和8中间存在一个中间值7,可以有效的防止链表和树频繁的转换。例如,当一个HashMap中的值在8和7之间来回变动,就会一直进行链表和树的转换,会比较耗时。
3.ConcurrentHashMap
3.1 get方法不加锁
3.2 put、remove方法使用锁
3.3 jdk7使用锁分离机制(segment分段加锁),jdk1.8使用cas + synchronized实现锁操作。
3.4 Iterator对象的使用,运行一边更新,一遍遍历(可以根据原理自己拓展)。
3.5 复合操作,无法保证线程安全,需要额外加锁。
3.6 并发情况下,ConcurrentHashMap比Collections.synchronizedMap()效率更高。