【JAVA面试题】五年前的一份准备面试的文档(更新中...)

1.缓存一致性

JMM内存可见性:说的是在多线程环境下对共享变量的操作,会导致数据不一致

解决办法:

(1)比如说i++,加lock锁,保证一个线程在执行这段代码时,其他线程阻塞,执行完之后另一个线程才能继续执行。但是效率很低,在锁住的时候其他线程不能访问
(2)通过缓存一致性协议,这个协议保证了每个线程中的用的共享变量副本是一致的,他的原理是当一个线程对共享变量进行修改时,会立马发出信号通知其他线程内的共享变量副本置为无效状态,重新获取主内存中的最新值

Redis缓存:无论是先写mysql还是redis,这两步操作的原子性都无法保证,所以会出现mysql和redis中的数据不一致,无论采取何种方式都不能保证强一致性,如果对redis里的数据设置了过期时间能保证最终的一致性,对架构的优化只能降低不一致性的概率,不能从根本上避免不一致性。

(1)先删缓存,再更新数据库。延迟双删,就是在更新完数据之后再删一次,延迟的目的是为了删除在写MySQL期间读线程可能把脏数据再次读到Redis里,延迟的时间参照一次从MySQL读数据并写入Redis的时间
(2)先写数据库,再删缓存
每个线程写完mysql延时一定时间后再删缓存

2.类加载过程

一个java文件需要运行要经过两个过程,就是编译和运行;编译就是把java文件通过javac命令编译成字节码,也就是我们常说的.class文件,运行就是把.class文件交给java虚拟机运行
而我们所说的类加载过程就是指虚拟机把.calss文件中类信息加载进内存,并进行解析生成对应的class对象的过程
举个通俗点的例子来说,JVM在执行某段代码时,遇到了class A, 然而此时内存中并没有class A的相关信息,于是JVM就会到相应的class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。

由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。

  1. 加载:根据查找路径找到相应的class文件,然后导入。类的加载方式分为

隐式加载和显示加载两种。隐式加载指的是程序在使用new关键词创建对象时,会隐式的调用类的加载器把对应的类加载到jvm中。显示加载指的是通过直接调用class.forName()方法来把所需的类加载到jvm中。

  1. 检查:检查夹加载的class文件的正确性。

  2. 准备;给类中的静态变量分配内存空间。

  3. 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址。

  4. 初始化:对静态变量和静态代码块执行初始化工作。

类加载的过程又分为三个部分

加载+链接(验证+准备+解析)+初始化

加载

简单来说就是把class字节码从各个来源通过类加载器装载入内存中

这里有两个重点:

字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译

类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。

注:为什么会有自定义类加载器?

一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。

另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

验证

主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。

对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

准备

主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。

特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。

比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456

解析

将常量池内的符号引用替换为直接引用的过程。

两个重点:

符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。

直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量

举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

初始化

这个阶段主要是对类变量初始化,是执行类构造器的过程。

换句话说,只对static修饰的变量或语句进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

总结

类加载过程只是一个类生命周期的一部分,在其前,有编译的过程,只有对源代码编译之后,才能获得能够被虚拟机加载的字节码文件;在其后还有具体的类使用过程,当使用完成之后,还会在方法区垃圾回收的过程中进行卸载。如果想要了解Java类整个生命周期的话,可以自行上网查阅相关资料,这里不再多做赘述。

在面试过程中类加载过程虽然是一个老生常谈的问题,但是往往从这个问题还可以衍生出很多其他重要的知识点,已经罗列在下文中,如果大家感兴趣的话,可以自行学习,小编也会在之后的文章中,对其中的一些问题进行解答和总结。

3.双亲委派

启动类加载器,扩展类加载器,应用类加载器
为了保证类加载的安全
首先从启动类加载器加载,这个称为父:rt.jar
如果启动类加载器加载不到再通过扩展类加载器加载ext/*.jar
如果扩展类加载器加载不到,会通过应用类加载器加载classpath
层层加载 如果你也写了一个String类 他肯定是用自己的String
SPI机制打破了双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

4.Spring几种注入方式

构造器注入
setter注入
接口注入

5.循环依赖排查和解决

用idea的Show Diagram可以看出来
用命令mvn denpendency:tree可以打印出当前工程的maven依赖路径
在依赖中加入exclusion,排除这个依赖
第一个就是重新设计 让他不需要进行循环依赖
(1)用@Lazy懒加载加在需要注入的Bean上:他并不是完全初始化bean,而是创建一个代理将它注入另一个bean,注入的Bean只有在第一次需要时才会完全创建
(2)用setter注入,bean的生命周期要设置为单例模式,如果是prototype会发生死循环
(3)打破循环的另一种方法是在其中一个bean上使用@Autowired注入依赖项,然后使用@PostConstruct注释的方法来设置其他依赖项
(4如果其中一个bean实现ApplicationContextAware,则bean可以访问Spring上下文,并可以从那里提取其他bean。实现InitializingBean我们指出这个bean必须在设置了所有属性后执行一些操作; 在这种情况下,我们想手动设置我们的依赖项)如果其中一个bean实现ApplicationContextAware,则bean可以访问Spring上下文,并可以从那里提取其他bean。实现InitializingBean我们指出这个bean必须在设置了所有属性后执行一些操作; 在这种情况下,我们想手动设置我们的依赖项

6.ioc实现和aop实现

IOC:控制反转也叫依赖注入,IOC利用java反射机制,AOP利用代理模式。所谓控制反转是指,本来被调用者的实例是有调用者来创建的,这样的缺点是耦合性太强,IOC则是统一交给spring来管理创建,将对象交给容器管理,你只需要在spring配置文件中配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。
AOP:面向切面编程。(Aspect-Oriented Programming)
AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理

7.cglib的底层原理

java的动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前用InvokeHandler来处理。

而cglib动态代理是利用asm的开源包,将被代理对象类的class文件加载进来,通过转换父类的的字节码文件生成子类来处理。

用jdk的动态代理被代理类必须要实现接口
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类,JDK动态代理要实现InvocationHandler接口
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final

8.bean生命周期

实例化
属性赋值
初始化
销毁
实例化和属性赋值都是Spring帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段
多个扩展点

9.容器加载过程

1.加载bean资源文件,resource的定位
2.加载beanDefinition,把用户定义好的bean表示成ioc容器内部的数据结构
3.注册到ioc容器中

10.java内存区域讲一下 每个区域干什么用的 jvm垃圾回收算法 如何判断对象是否死亡如何判断类是无用类

堆(运行时创建的对象)和方法区(类的信息)是由该java虚拟机实例中所有线程共享的

pc寄存器(下一条将被执行的指令)和栈(局部变量、入参、返回值、计算的中间结果)是线程私有的

JVM 会先加载 class 文件到内存里
然后会调用 main 方法
这个 main 线程会创建一个 程序计数器,属于当前线程私有的,用于记录代码执行到那一行指令
这个 main 线程会创建一个 方法栈。属于当前线程私有的,用于记录当前方法所需的局部变量,操作数栈,动态链接,方法出口等等信息
会把这个 main 方法压入 线程栈中
如果遇到创建对象 ,这个时候,会启动类加载机制,把对应的类加载进内存,进行 加载、验证、准备、解析 等等过程。在堆内存开辟一片内存空间。把这个对象的堆内存地址,赋予 main 方法中 调用和这个对象的 变量
如果调用这个对象的某个方法,会为这个方法创建一个栈帧,压入 main 线程的 虚拟机栈中,执行完毕之后,出栈。

11.class对象可以被回收吗

12.事务的隔离级别 幻读和不可重复读区别

读未提交=》脏读

读已提交=》不可重复读

可重复读=》幻读

串行化(锁表)

不可重复读:同时操作,事务一分别读取事务二操作时和提交后的数据,读取的记录内容不一致.
幻读:(和可重复读类似,但是事务二的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致)

13.mysql索引的底层实现 为什么不用别的实现

要获取磁盘上的数据,必须先通过移动臂先找到柱面,然后找到盘面,然后找到磁道。磁盘io的代价主要花费在所需要的的柱面上,树的深度会造成磁盘的频繁io。一次io读出的数据大小是固定的,非叶子节点不存储数据就可以减少磁盘io,所以b+树更适合做索引
MongoDB为什么使用B-树而不是B+树

(1)hash索引不能按大小排序,无法按范围查找

(2)二叉查找树

1、若它的左子树不为空,则左子树上所有的节点值都小于它的根节点值。
2、若它的右子树不为空,则右子树上所有的节点值均大于它的根节点值。
3、它的左右子树也分别可以充当为二叉查找树。

(3)平衡二叉树

具有二叉查找树的全部特性。
每个节点的左子树和右子树的高度差至多等于1。
在插入的过程中,会出现一下四种情况破坏AVL树的特性,我们可以采取如下相应的旋转。

1、左-左型:做右旋。

2、右-右型:做左旋转。

3、左-右型:先做左旋,后做右旋。

4、右-左型:先做右旋,再做左旋。

(3)B树

是一颗多路平衡查找树

(1)根结点至少有两个孩子

(2)每个非根结点至少有M/2个孩子,至多有M个孩子;

(3)每个非根结点至少有M/2-1个关键字,至多有M-1个关键字,并且关键字是按升序进行排列的

(4)key[i]和key[i+1]之间的孩子节点的值介于key[i]和key[i+1]之间

(5)所有的叶子结点都在同一层

B树每个节点都存储key和data,并且叶子节点指针为null

(4)B+树

只有叶子节点存储data,叶子节点包含了这颗树的所有键值,叶子节点不包含指针

**(1)InnoDB的主键采用聚簇索引存储,使用的是B+Tree作为索引结构,但是叶子节点存储的是索引值和数据本身(注意和MyISAM的不同)。

(2)InnoDB的二级索引不使用聚蔟索引,叶子节点存储的是KEY字段加主键值。因此,通过二级索引查询首先查到是主键值,然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。

(3)MyISAM的主键索引和二级索引叶子节点存放的都是列值与行号的组合,叶子节点中保存的是数据的物理地址

(4)MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址

(5)为什么用B+Tree 不是BTree:
B-Tree:如果一次检索需要访问4个节点,数据库系统设计者利用磁盘预读原理,把节点的大小设计为一个页,那读取一个节点只需要一次I/O操作,完成这次检索操作,最多需要3次I/O(根节点常驻内存)。数据记录越小,每个节点存放的数据就越多,树的高度也就越小,I/O操作就少了,检索效率也就上去了。
B+Tree:非叶子节点只存key,大大滴减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。

14.联合索引的最左匹配原则讲一下

b+树是按照从左到右的顺序来建立搜索树的
建了一个(a,b,c)的复合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引

15.聚簇索引

聚簇索引的顺序就是数据的物理存储顺序,叶节点就是数据节点。非聚簇索引的顺序与数据物理排列顺序无关,叶节点仍然是索引节点,只不过有一个指针指向对应的数据块

16.redis缓存雪崩 缓存击穿 缓存穿透

缓存穿透:

描述:

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大

解决:

1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿:

描述:

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决:

1.设置热点数据永远不过期。
2.加互斥锁

缓存雪崩:

描述:

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库

解决:

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.双缓存,缓存降级。
3.设置热点数据永远不过期。

17.数据库慢查询优化

1.开启mysql慢查询

18.hashMap底层原理 初始化 put 扩容 默认长度 转化为红黑树

hashmap是数组加链表的数据结构
红黑树的添加删除更快(旋转达到平衡最多三次) 平衡二叉树查找更快
链表长度大于等于8调用treeifyBin方法,该方法中如果数组长度小于64就选择扩容 而不是转化成红黑树
加载因子0.75

19.线程池的七个参数

corepoolsize 核心线程数
maximumpoolsize 最大线程数量
keepalivetime 空闲线程存活时间
unit keepalivetime的计量单位
workquene 工作队列
三种阻塞队列
BlockingQueue workQueue = null;

workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界

workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界

workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
threadfactory 线程工厂
handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略

四种拒绝策略
RejectedExecutionHandler rejected = null;

rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常

rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常

rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列

rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务

20.阻塞队列详细讲一下 阻塞队列为什么可以实现生产者消费者 原理讲一下

有7种阻塞队列
使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。通过查看JDK源码发现ArrayBlockingQueue使用了Condition来实现

#HashMap 为什么线程不安全?

#redis和数据库中的数据怎么保持一致性

#具体场景的高并发设计,比如排行榜top10,并发查询量大的时候如何处理(top是在不停变化的,100w+的数据一直在变化)
#你擅长哪一个
#线程池参数设定,为什么这么设定,作用?7大参数

21.mybatis一级缓存和二级缓存

一级缓存没有过期时间,只有生命周期
二级缓存有过期时间,但是没有后台线程检测
1).Mybatis在开启一个数据库会话时,会创建一个新的SqlSession对象。SqlSession对象的缓存是Mybatis的一级缓存,在操作数据库时需要创建SqlSession对象,在对象中有hashMap用于保存缓存数据(对象的id作为key,而对象作为 value保存的)。一级缓存的作用范围是SqlSession范围的,当一个SqlSession中执行两次相同的sql第一次执行完就会将数据库查询到的数据写进缓存,第二次查询时直接去缓存中查找,从而提高数据库的效率。
(2)如果SqlSession执行DML(insert,update,delete)操作,并且提交到数据库,Mybatis会清空SqlSession的一级缓存,这样做的目的是为了保存最新的数据,避免出现脏读的现象。当Mybatis清空SqlSession的一级缓存(生命周期结束)
17.linkedlist数据结构
linkedList底层是一个双向链表

22.explain查看sql的执行计划

id:选择标识符

select_type:表示查询的类型。

table:输出结果集的表

partitions:匹配的分区

type:表示表的连接类型ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)

possible_keys:表示查询时,可能使用的索引

key:表示实际使用的索引

key_len:索引字段的长度

ref:列与索引的比较

rows:扫描出的行数(估算的行数)

filtered:按表条件过滤的行百分比

Extra:执行情况的描述和说明

23.乐观锁和悲观锁

悲观锁:借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观锁

悲观锁主要分为:

共享锁(读锁):共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改

排他锁(写锁):他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

1传统的关系型数据库使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

2.Java 里面的同步 synchronized 关键字的实现

乐观锁:
CAS 实现:
1.Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。

2.版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

24.数据库分库分表

25.SPI机制

26.spring有哪些扩展点

27.CountDownLatch

同步辅助器
允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成 才继续操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的架狗师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值