2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]

这里是参考B站上的大佬做的面试题笔记。大家也可以去看视频讲解!!!

文章目录

  • 1、面向对象
  • 2、JDK、JRE、JVM区别和联系
  • 3、==和equals
  • 4、final
  • 5、String 、StringBuffer、StringBuilder区别及使用场景
  • 6、重载和重写的区别
  • 7、接口和抽象类
  • 8、List和Set
  • 9、hashcode和equals
  • 10、ArrayList和Linkedlist
  • 11、HashMap和HashTable的区别及底层实现
  • 12、ConcurrentHashMap原理简述,jdk7和jdk8的区别
  • 13、如何实现一个IOC容器
  • 14、什么是字节码,作用是什么
  • 15、java类加载器有哪些
  • 16、双亲委派模型
  • 17、java中的异常体系
  • 18、GC如何判断对象可以被回收
  • 19、线程的生命周期及状态
  • 20、sleep、wait、join、yield的区别
  • 21、对线程安全的理解
  • 22、Thread和Runnable的区别
  • 23、说说你对守护线程的理解
  • 24、ThreadLocal的原理和使用场景
  • 25、ThreadLocal内存泄漏问题,如何避免
  • 26、并发、并行、串行
  • 27、并发三大特性
  • 28、为什么使用线程池释下线程池解释/li>
  • 29、线程池处理流程
  • 30、线程池中阻塞队列的作用什么是先添加队列而不是先创建最大线程/li>
  • 31、线程池复用的原理
  • 32、spring是什么/li>
  • 33、对Aop的理解
  • 34、对IOC的理解
  • 35、BeanFactory和ApplicationContext有什么什么区别/li>
  • 36、简述spring bean的生命周期
  • 37、spring支持的几种bean作用域
  • 38、Spring框架中的单例Bean是线程安全的吗
  • 39、spring框架中使用了哪些设计模式及应用场景
  • 40、spring事务的实现方式原理以及隔离级别
  • 41、spring的事务传播机制
  • 42 、spring事务什么时候会失效
  • 43 、什么的是bean的自动装配、有哪些方式/li>
  • 44 、spring 、 springmvc 、springboot的区别
  • 45 、springmvc的工作流程
  • 46 、Spring MVC的主要组件
  • 47 、springboot自动配置原理
  • 48 、 如何理解springboot的starter
  • 49 、 什么是嵌入式服务器,为什么使用嵌入式服务器
  • 50、mybatis的优缺点
  • 51、mybatis和hibernate的对比
  • 52 、#{}和${}的区别
  • 53 、mybatis插件运行原理及开发流程
  • 54 、索引的基本原理
  • 55 、mysql聚簇和非聚簇索引的区别
  • 56 、mysql索引结构,各自的优劣
  • 57 、索引的设计原则
  • 58 、mysql锁的类型有哪些
  • 59 、mysql执行计划怎么看
  • 60 、事务的基本特性和隔离级别
  • 61、怎么处理慢查询
  • 62、ACID靠什么保证的
  • 63、什么是MVCC
  • 64、mysql主从同步原理
  • 65、简述Myisam和innodb的区别
  • 66、简述MySQL中索引类型对数据库的性能的影响
  • 67、RDB和AOF机制
  • 68、Redis的过期键的删除策略
  • 69、Redis线程模型,单线程为什么这么快
  • 70、缓存雪崩、缓存穿透、缓存击穿
  • 71、简述Redis事务实现
  • 72、redis集群方案
  • 73、redis主从复制的核心原理
  • 74、CAP理论,BASE理论
  • 75、负载均衡算法、类型
  • 76、分布式架构下,Session共享有什么方案
  • 77、简述你对RPC、RMI的理解
  • 78、分布式id生成方案
  • 79、分布式锁解决方案
  • 80、分布式事务解决方案
  • 81、如何实现接口幂等性

1、面向对象

1.1、 什么是面向对象/strong>

对比面向过程、是两种不同的处理问题的角度
面向过程更注重事情的没一个步骤及顺序,面向对象更注重事情有哪些参与者(对象),及各自需要做什么。

例如:洗衣机洗衣服

  • 面向过程会将任务拆解成一系列的步骤(函数),1、打开洗衣机—->2、放衣服——>3、放洗衣粉—->4、清洗——>5、烘干
  • 面向对象会拆出人和洗衣机两个对象:人:打开洗衣机,放衣服,放洗衣粉。洗衣机:清洗、烘干

从以上例子可以看出:面向过程比较直观高效,而面向对象更易于复用,扩展和维护。

1.2 、面向对象

封装

  • 封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现。

  • 1、javabean的属性私有,提供get/set对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定,而不能由外部胡乱修改。

  • 2、orm框架:操作数据库,我们不需要关心链接时如何建立的,sql时如何执行的,只需要引入mybatis,调方法即可。

继承

  • 继承基类的方法,并做出自己的改变或扩展
  • 子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的

多态

  • 基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。
  • 继承,方法重写,父类引用指向子类对象
  • 父类类型 变量名 = new 子类对象;
  • 变量名.方法名();

无法调用子类特有的功能


2、JDK、JRE、JVM区别和联系

JDK:

  • java Develpment Kit java 开发工具

JRE:

  • java Runtime Environment java 运行时环境

JVM:

  • java Virtual Machine java 虚拟机

2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]

3、==和equals

  • 对比的时中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
  • equals:object中默认也是采用比较,通常会重写
  • String类中被复写的equals()方法其实是比较两个字符串的内容

举例

2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]

双亲委派模型的好处:

  • 主要是为了安全性,避免用户自己编写的类动态替换java的核心类,比如String。
  • 同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。

17、java中的异常体系

  • java中的所有的异常都来自顶级父类Throwable
  • Throwable下有两个子类ExceptionError
  • Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
  • Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常CheckException检查异常
  • RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过

铁汁们、铁汁们、铁汁们、都看到这里了。拜托、拜托、拜托、一键三连呗


18、GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用计数加1,引用释放时计数减1,计数为0时可以回收。
  • 可达性分析法:从GC Root开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

GC Toots的对象有哪些:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中国JNI(即一般说的Native方法)引用的对象。

可达性算法中的不可达对象并不是并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变成(GC ROOts)不可达时,GC会判断该对象是否覆盖了finalize()方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize()方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize()方法。执行finalize()方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”

每个对象只能触发一次finalize()方法

由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它


19、线程的生命周期及状态

1、线程通常有五种状态,创建就绪运行阻塞死亡状态

2、阻塞的情况又分为三种:

  • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入”等待池中“。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notifynotifyAll方法才能被唤醒,wait是object类的方法

  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入”锁池“中

  • 其他阻塞:运行的线程执行sleep或join方法,或者发出来I/O请求时,JVM会把该线程设置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法

1、新建状态(new):新建了一个线程对象

2、就绪状态(Runnable):线程对象创建后、其他线程调用了该对象的start()方法。该状态的线程位于
可运行线程池中,变得可运行,等待获取cpu的使用权。

3、运行状态(Runnbaleing):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

5、死亡状态(Dead):线程执行完了或者因异常推出了run方法,该线程结束生命周期


20、sleep、wait、join、yield的区别

1、锁池

所有需要竞争同步锁的线程都会放在锁池中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程区竞争同步锁,当某个线程得到后会进入就绪队列进行等待CPU资源分配。

2、等待池

当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁的。只有调用了notify()或notifyAll()后等待池的线程才会去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池中的所有线程放到锁池中。

1、sleep是Thread类的静态本地方法,wait是object类的本地方法

2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也就会抛出interruptexception异常返回,这点和wait是一样的

3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字

4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

5、sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信

6、sleep会让出CPU执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。

yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行。

join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那么线程B会进入到阻塞队列,直到线程A结束或中断线程


21、对线程安全的理解

不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问

当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。

  • 是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏

在java中,堆是java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

  • 是每个线程独有的,保存其运行状态局部自动变量的。栈在线程开始的时候初始化,每个线程的栈相互独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显示的分配和释放。

目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。

在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程的所有线程都可以访问到该区域,这就是造成问题的潜在原因。


22、Thread和Runnable的区别

Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run()方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果知识简单的执行一个任务,那就实现Runnable


23、说说你对守护线程的理解

守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程类似于整个进程的一个默默无闻的小喽啰;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;

注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠谱;

守护线程的作用是什么br> 举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它开始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

应用场景:

  • 1、来为其它线程提供服务支持的情况;
  • 2、或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。

必须在之前设置,否则会抛出一个异常。你不能把正在运行的常规线程设置为守护线程。

在Daemon线程中产生的新线程也是Daemon的。

守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断

java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用java的线程池。


24、ThreadLocal的原理和使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值

ThreadLocalMap由一个个Entry对象构成。

继承自,一个由对象和构成。由此可见,的key是对象,并且是一个弱引用,当没指向key的强引用后,该key就会被垃圾收集器回收。

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:

  • 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
  • 2、线程间数据隔离
  • 3、进行事务操作,用于存储线程事务信息。

25、ThreadLocal内存泄漏问题,如何避免

内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。

不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。

强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出错误,使程序异常终止,也不回收这种对象。

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样就可以使JVM在合适的时间就会回收该对象。

弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用类来表示。可以在缓存中使用弱引用。

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本

2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]

30、线程池中阻塞队列的作用什么是先添加队列而不是先创建最大线程/h2>

1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源

2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。

就好比一个企业里有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式人工数(task>core)的情况下、工厂领导(线程池)不是首先扩招工人,还是这10个人,但是任务可以稍微积压一下,即先放到队列区(代价低)。10个正式工慢慢干,迟早会干完的。要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)


31、线程池复用的原理

  • 线程池将线程任务进行解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。

  • 来源:架构师全栈之路

    声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年7月3日
下一篇 2022年7月3日

相关推荐