2024最新java八股文面试汇总

小明 2025-05-05 18:14:09 5

目录

  • 1.Java基础
    • 1.1 说说JVM内存模型
    • 1.2 说说JVM的垃圾回收算法
    • 1.3 类加载机制
    • 1.4 谈谈你对泛型的了解
    • 1.5 创建线程的方式
    • 1.6 说说反射机制
    • 1.7 动态代理有哪几种
    • 1.8 JVM中怎么判断能被垃圾回收机制回收
    • 1.9 谈谈Java中的值传递和引用传递
    • 1.10 Java的四种引用方式
    • 1.11 JVM堆内存分配
    • 1.12 JVM垃圾收集器有哪些
    • 1.13 HashCode()和equals()的区别
    • 1.14 常见的设计模式
    • 1.15 Object类的方法有哪些
    • 1.16 抽象类和接口的区别
    • 1.17 final、finally、finalize的区别
    • 2.多线程
      • 2.1 谈谈synchronized的原理及底层实现
      • 2.2 synchronized与Lock的区别
      • 2.3 什么是可重入锁
      • 2.4 死锁产生的条件(如何解决?怎么判断死锁?)
      • 2.5 讲一讲ThreadLocal
      • 2.6 ThreadLocal的内存泄漏,如何避免呢?
      • 2.7 谈谈进程和线程
      • 2.8 谈一谈ReentrantLock
      • 2.9 ReentrantLock和Synchronized区别
      • 2.10 讲一讲CAS
      • 2.11 讲一讲volatile关键字
      • 2.12 线程池核心参数和工作原理
      • 2.13 wait()、sleep()和yield()方法的区别
      • 3.集合
        • 3.1 ArrayList底层原理
        • 3.2 ArrayList和LinkedList的区别
        • 3.3 HashMap 1.7 和1.8的区别
        • 3.4 HashMap1.8为什么使用红黑树
        • 3.5 什么是哈希碰撞,怎么解决?
        • 3.6 请你说说ConcurrentHashMap
        • 3.7 请你说说CopyOnWriteArraylist
        • 3.8 HashMap和Hashtable的区别
        • 3.9 List与Set接口有什么区别
        • 4.异常
          • 4.1 throw和throws的区别
          • 4.2 异常分类
          • 5.Spring、SpringBoot、SpringMVC
            • 5.1 谈谈IOC和AOP
            • 5.2 SpringBoot启动流程
            • 5.3 SpringBoot的自动装配
            • 5.4 @Autowired和@Resource注解的区别
            • 5.5 Spring事务失效原因
            • 5.6 说说BeanFactory和FactoryBean的区别
            • 5.7 SpringBoot自定义注解
            • 5.8 SpringMVC的三层框架
            • 5.9 SpringMVC的执行流程
            • 5.10 说说Spring Boot的起步依赖(starter自动化配置)
            • 5.11 bean的生命周期
            • 5.12 bean的作用域
            • 5.13 Springmvc拦截器和过滤器的区别
            • 5.14 @RestController和@Controller的区别
            • 5.15 @Component和@Bean的区别
            • 5.16 springboot中事务如何开启
            • 5.17 bean实例化的三种方式
            • 6.Mybatis
              • 6.1 Mybatis的三种传值方式
              • 6.2 Mybatis的一级缓存和二级缓存
              • 7.Mysql
                • 7.1索引查询失效的几个情况
                • 7.2 Mysql的事务特性 及事务的传播方式
                • 7.3 Mysql的事务隔离级别
                • 7.4 Mysql数据库常用的锁
                • 7.5 Mysql与Redis的区别
                • 7.6 Innodb和Myisam的区别
                • 7.7 请你说说InnoDB的MVCC机制(难点)
                • 7.8 数据库索引为什么使用B+数
                • 7.9 讲一讲sql优化
                • 7.10 count(*)和count(1)的区别
                • 7.11 mysql索引
                • 7.12 left join、right join、inner join的区别
                • 7.13 为什么mysql默认隔离级别设置为RR(可重复读)
                • 7.14 MySQL普通索引和唯一索引区别
                • 7.15 常见mysql的慢查询优化方式
                • 7.16 mysql主从复制
                • 7.17 当前读与快照读的区别
                • 7.18 sql语句执行顺序
                • 8.Redis
                  • 8.1 谈谈Redis 的应用场景
                  • 8.2 如何利用Redis实现一个分布式锁?
                  • 8.3 Redis的基本数据类型及底层数据结构
                  • 8.4 Redis如何与数据库保持双写一致性
                  • 8.5 Redis的持久化策略
                  • 8.6 说说缓存穿透、击穿、雪崩以及如何避免
                  • 8.7 redis是单线程还是多线程,为什么?
                  • 8.8 什么是IO多路复用机制?
                  • 9.MQ
                    • 9.1 为什么要使用MQ
                    • 9.2 如何解决MQ消息丢失问题?
                    • 10.Dubbo
                    • 11.计算机网络
                      • 11.1 TCP和UDP的区别
                      • 11.2 TCP三次握手和四次挥手过程
                      • 11.3 七层网络协议
                      • 在这里插入图片描述
                      • 11.3 Http和Https详解
                      • 11.4 Http状态码
                      • 11.5 TCP粘包
                      • 12.Linux
                        • 12.1 linux常用命令
                        • 13.JWT

                          1.Java基础

                          1.1 说说JVM内存模型

                          JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。

                          类加载子系统:可以根据指定的全限定名来载入类或接口。

                          执行引擎:负责执行那些被载入类的方法中的指令。

                          运行时数据区:包含五部分的内容:栈、堆、本地方法栈(为Native方法提供服务)、方法区(元空间)、程序计数器(用来保存线程执行的位置)。

                          栈:存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包含局部变量和对象的引用

                          堆:存储对象实例的以及数组

                          线程私有:栈、本地方法栈、程序计数器

                          线程共享:堆、方法区


                          1.2 说说JVM的垃圾回收算法

                          1. 标记-清除,先标记要清除的对象,然后统一回收这些对象,不需要额外的空间,但是需要两次扫描耗时严重并且会产生内存碎片;
                          2. 标记-复制(年轻代),将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,效率高且没有内存碎片,但是需要双倍的空间,年轻代中使用复制算法;
                          3. 标记-整理(老年代),标记存活对象,然后将标记的存活对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片,但是需要移动对象。

                          1.3 类加载机制

                          类加载机制总共包括个阶段:加载,验证,准备,解析,初始化

                          加载:通过类加载器,使用字节码文件创建类对象。

                          验证:主要是验证当前的字节流包含的信息是否符合当前的虚拟机环境。

                          准备:为类变量(static修饰的变量)分配内存,并赋值.

                          解析:主要是将常量池的符号引用变成直接引用。

                          初始化:执行类构造器方法的过程。


                          1.4 谈谈你对泛型的了解


                          1.5 创建线程的方式

                          1. 继承Thread类
                          public class ThreadTEST {
                              public static void main(String[] args) {
                                  Thread.currentThread().setName("main thread");
                                  MyThread myThread1 = new MyThread();
                                  MyThread myThread2 = new MyThread();
                           
                                  myThread1.start();
                                  myThread2.start();
                              }
                          }
                           
                          class MyThread extends Thread {
                              @Override
                              public void run() {
                                  for (int i = 0; i  
                          
                          1. 实现Runnable接口
                          public class RunnableTest {
                           
                              public static void main(String[] args) {
                                  Thread thread = new Thread(new MyRunnable());
                                  //开启线程
                                  thread.start();
                                  for(int i = 0; i 
                                      System.out.println(Thread.currentThread().getName() + i);
                                  }
                              }
                          }
                           
                          class MyRunnable implements Runnable {
                           
                              @Override
                              public void run() {
                                  for (int i = 0; i order by->limit 
                          

                          8.Redis

                          8.1 谈谈Redis 的应用场景

                          1. 缓存

                            热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据。

                          2. 分布式锁

                            详见:8.2 如何利用Redis实现一个分布式锁


                          8.2 如何利用Redis实现一个分布式锁?

                          1. SETNX + EXPIRE

                          这个方案中,setnx和expire两个命令分开了,「不是原子操作」。如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,「别的线程永远获取不到锁啦」。

                          2. SETNX + value值是(系统时间+过期时间)

                          这个方案的优点是,巧妙移除expire单独设置过期时间的操作,把「过期时间放到setnx的value值」里面来。解决了方案一发生异常。

                          3.使用Lua脚本(包含SETNX + EXPIRE两条指令)

                          实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令)

                          4.SET的扩展命令(SET EX PX NX)

                          除了使用,使用Lua脚本,保证SETNX + EXPIRE两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数!(SET key value[EX seconds][PX milliseconds][NX|XX]),它也是原子性的!

                          SET key value[EX seconds][PX milliseconds][NX|XX]

                          NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。

                          EX seconds :设定key的过期时间,时间单位是秒。

                          PX milliseconds: 设定key的过期时间,单位为毫秒

                          XX: 仅当key存在时设置值

                          但是呢,这个方案还是可能存在问题:「锁被别的线程误删」。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢。

                          5.SET EX PX NX + 校验唯一随机值

                          既然锁可能被别的线程误删,那我们给value值设置一个标记当前线程唯一的随机数,在删除的时候,校验一下,不就OK了嘛。

                          参考链接地址:Redis实现分布式锁的7种方案,及正确使用姿势!


                          8.3 Redis的基本数据类型及底层数据结构

                          1.String

                          字符串对象的值底层都是由简单动态字符串实现的

                          get 							查询对应键值
                          set  			添加键值对
                          append  	将给定的追加到原值的末尾
                          strlen 					获取值的长度
                          senx  		只有在key 不存在时设置key的值
                          incr 						将key中存储的数字值增1, 只能对数字值操作,如果为空,新增值为1
                          decr 						将key中存储的数字值减1, 只能对数字之操作,如果为空,新增值为-1
                          incrby/decrby  	将key中存储的数字值增减,自定义步长
                          mset    		同时设置一个或多个key-value对
                          mget   								同时获取一个或多个value
                          msetnx    	同时设置一个或多个key-value对,当且仅当所有给定的key都不存在
                          getrange   			 获得值的范围,类似java中的substring
                          setrange   		用覆盖所存储的字符串值,从开始
                          setex   			设置键值的同时,设置过去时间,单位秒
                          getset  					 以新换旧,设置了新值的同时获取旧值
                          

                          2.list

                          底层数据结构是双向链表

                          常用操作命令
                          lpush/rpush     	从左边/右边插入一个或多个值。
                          lpop/rpop 							从左边/右边吐出一个值。值在键在,值光键亡。
                          rpoplpush    				从列表右边吐出一个值,插到列表左边
                          lrange   				按照索引下标获得元素(从左到右)
                          lindex  					按照索引下标获得元素(从左到右)
                          llen 								获得列表长度
                          linsert  before  		在的后面插入 插入值
                          lrem   								从左边删除n个value(从左到右)
                          

                          3.set

                          底层数据结构是hash+整数数组

                          4.zset

                          底层数据结构是ziplist+跳表

                          5.hash

                          底层数据结构是ziplist+hash


                          8.4 Redis如何与数据库保持双写一致性

                          先更新数据库再删除缓存,若有错误需要重试


                          8.5 Redis的持久化策略

                          1. RDB:在指定的时间间隔内,将内存中的数据集的快照写入磁盘,文件名dump.rdb 适合大规模的数据恢复,对数据库的完整性和一致性要求不是很高,如果数据库意外down掉,就会失去最后一次快照的所有修改。
                          2. AOF:以日志的形式记录每个写操作,只允许追加文件,不允许改写文件,redis启动时会读取这个文件,并从头到尾执行一遍,以此来恢复数据,文件名appendonly.aof。完整性较高,但是会对磁盘持续的进行IO,代价太大。

                          8.6 说说缓存穿透、击穿、雪崩以及如何避免

                          缓存穿透:指大量请求访问一个本身不存在的数据,使得请求直达存储层,导致负载过大。

                          处理手段:使用过滤器进行拦截,若请求的数据不存在则直接返回空值。

                          缓存击穿:指一份热点数据缓存失效,突然涌入大量的访问请求,使得请求直达存储层,导致负载过大。

                          处理手段:1. 永不过期:对热点数据不设置过期时间 2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值

                          缓存雪崩:大量数据同时过期,缓存层无法提供服务,使得请求直达存储层,导致负载过大。

                          处理手段:1. 永不过期:对热点数据不设置过期时间 2.避免数据同时过期,设置随机过期时间


                          8.7 redis是单线程还是多线程,为什么?

                          对于键值对读写是单线程的,而对于持久化、异步删除是依赖其他线程来执行的。事实上他底层并不是单线程的

                          1. 采用单线程则避免了线程间的切换和加锁过程,减小了消耗。
                          2. redis采用IO多路复用机制,使其能够处理并发请求。

                          8.8 什么是IO多路复用机制?

                          select / poll / epoll 这是三个多路复用接口

                          1. select/poll

                            select 实现多路复用的方式与poll的方式类似:将已连接的 Socket 都放到一个FD(文件描述符)集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 进行标记, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

                          2. epoll

                            epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述符,减少了内核和用户空间大量的数据拷,提高了效率。

                            epoll 内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中


                          9.MQ

                          9.1 为什么要使用MQ

                          1.实现异步处理

                          有时候我们会遇到这样的场景,用户在客户端提交了一个请求,后端处理这个请求的业务相对比较复杂,如果这个请求使用的是同步调用,客户端就会出现发送请求后过了很久才相应的情况,这对用户体验来说是十分致命的.如果说用户并不关心请求是否处理,对于一些耗时的非事务性的业务处理,我们可以使用mq异步请求的方式,将处理信息放入队列,由后端监听队列自行处理,在将消息存入队列后直接返回客户端相应,加快响应速度.

                          2.实现分布式系统之间的应用解耦

                          在分布式系统中,经常会出现一个服务会有多个消费端调用,而且可能每个消费方需要接入的逻辑不一致问题。将消息写入消息队列,需要消息的系统自己从消息队列中订阅,实现解耦。

                          3.流量削峰

                          例如秒杀活动,可能在短时间内会有很大请求同时到后端,如果后端对每个请求都执行业务操作,例如查询数据库和写数据库,会造成服务器压力过大,同时,在同一时间进行大量数据库操作,可能会出现数据异常,我们可以使用mq实现缓冲,将所有请求先放入消息队列中,服务端每次处理业务先从消息队列获取.


                          9.2 如何解决MQ消息丢失问题?

                          1. 开启生产者消息手动确认机制
                          2. 开启消息持久化和队列持久化
                          3. 开启消费者消息手动确认机制

                          10.Dubbo


                          11.计算机网络

                          11.1 TCP和UDP的区别

                          1. TCP是面向连接的,UDP是无连接的。
                          2. TCP是可靠传输的,UDP是不可靠传输的。
                          3. TCP是是面向字节流传输的,UDP是面向报文传输的。

                          11.2 TCP三次握手和四次挥手过程

                          通道的建立-------三次握手:

                          (1)在建立通道时,客户端首先要向服务端发送一个SYN同步信号。

                          (2)服务端在接收到这个信号之后会向客户端发出SYN同步信号和ACK确认信号。

                          (3)当服务端的ACK和SYN到达客户端后,客户端与服务端之间的这个“通道”就会被建立起来。

                          通道的关闭——四次挥手:

                          (1)在数据传输完毕之后,客户端会向服务端发出一个FIN终止信号。

                          (2)服务端在收到这个信号之后会向客户端发出一个ACK确认信号。

                          (3)如果服务端此后也没有数据发给客户端时服务端会向客户端发送一个FIN终止信号。

                          (4)客户端在收到这个信号之后会回复一个确认信号,在服务端接收到这个信号之后,服务端与客户端的通道也就关闭


                          11.3 七层网络协议

                          11.3 Http和Https详解

                          Http和Https详解


                          11.4 Http状态码

                          1. 200:成功
                          2. 301:永久性重定向,表示资源已被分配了新的 URL
                          3. 302:临时性重定向,表示资源临时被分配了新的 URL
                          4. 404:服务器无法根据客户端的请求找到资源
                          5. 500:服务器内部错误

                          11.5 TCP粘包

                          粘包是指发送方发送的若干包数据到接收方接收时粘成一包

                          原因:

                          1. 当连续发送数据时,为了提高效率,tcp协议会将较小的内容拼接成大的内容,一次性发送到服务器端,因此造成粘包
                          2. 当发送内容较大时,由于服务器端方法中的buffer_size较小,不能一次性完全接收全部内容,因此在下一次请求到达时,接收的内容依然是上一次没有完全接收完的内容,因此造成粘包现象。

                          解决:

                          在每次使用tcp协议发送数据流时,在开头标记一个长度信息,并固定该报文长度,当服务端接收数据,判断客户端发送数据流长度,并只接收该长度字节数据,就可以实现拆包,完美解决tcp粘包问题.


                          12.Linux

                          12.1 linux常用命令

                          1. 打包指令:tar[主选项+辅选项] 文件或目录
                          2. vim
                          3. cd
                          4. pwd:显示用户当前所在的目录
                          5. ls:对于目录,该命令列出该目录下的所有子目录与文件
                          6. grep:用于查找文件里符合条件的字符串
                          7. ps:用来列出系统中当前正在运行的那些进程
                          8. ping :检测是否与主机连通

                          13.JWT

                          1. 前端通过Web表单将自己的用户名和密码发送到后端的接口
                          2. 后端核对用户名和密码成功后,将用户信息作为JWT Payload (负载),将其与Header(头部)分别进行编码拼接后,进行签名形成一个JWT(Token)返回给前端。
                          3. 以token为key,用户信息为value,存入到redis中。
                          4. 前端收到 后端返回的token后,将其存储在 HTTP 请求的头信息Authorization字段里面
The End
微信