互联网大厂Java面试:核心知识大考验
面试官:欢迎你来面试,先简单介绍一下你自己吧。
王铁牛:面试官您好,我叫王铁牛,有[X]年Java开发经验,熟悉各种Java技术,希望能加入咱们公司。
面试官:好,那我开始提问了。第一轮,说说Java中的多线程,线程池有哪些参数,作用是什么?
王铁牛:线程池有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler这些参数。corePoolSize是核心线程数,当提交的任务数小于它时,会创建核心线程来执行任务;maximumPoolSize是最大线程数,当任务数超过corePoolSize且workQueue满了时,会创建非核心线程来执行任务;keepAliveTime是线程池中非核心线程的存活时间;unit是存活时间的单位;workQueue是任务队列,存放提交的任务;threadFactory是线程工厂,用于创建线程;handler是拒绝策略,当线程池满了且任务队列也满了时,会调用它来处理新提交的任务。
面试官:回答得不错,看来你对线程池还是有一定了解的。那再说说HashMap的底层实现原理。
王铁牛:HashMap底层是数组+链表+红黑树。当链表长度超过8且数组长度大于64时,链表会转换为红黑树。插入元素时,先计算key的哈希值,然后根据哈希值找到对应的数组位置,如果该位置为空,则直接插入;如果不为空,则遍历链表或红黑树,找到相同key的节点则更新其value,否则插入新节点。
面试官:嗯,还算清晰。最后一个问题,讲讲Spring的核心特性。
王铁牛:Spring的核心特性有依赖注入、面向切面编程、IoC容器、事务管理等。依赖注入可以方便地将对象之间的依赖关系进行解耦;面向切面编程能让我们将一些通用的功能(如日志、权限验证等)从业务逻辑中分离出来;IoC容器负责创建、配置和管理对象;事务管理可以确保业务操作的原子性、一致性、隔离性和持久性。
面试官:好,第一轮提问结束。接下来进入第二轮,说说JVM的内存结构,以及垃圾回收算法有哪些?
王铁牛:JVM内存结构包括程序计数器、虚拟机栈、本地方法栈、堆和方法区。垃圾回收算法有标记清除算法、标记整理算法、复制算法、分代收集算法等。标记清除算法会先标记出需要回收的对象,然后统一回收;标记整理算法会先标记,然后将存活对象移动到一端,再清理另一端的内存;复制算法会将内存分为两块,每次只使用一块,当这块满了,就将存活对象复制到另一块,然后清理原来的那块;分代收集算法是根据对象的存活周期将堆分为新生代、老年代等,针对不同代采用不同的垃圾回收算法。
面试官:那JUC中的CountDownLatch和CyclicBarrier有什么区别?
王铁牛:CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作之后再执行。它通过一个计数器来实现,调用countDown方法会使计数器减1,当计数器为0时,等待的线程会被唤醒。CyclicBarrier也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。它内部有一个计数器,当每个线程调用await方法时,计数器会减1,当计数器为0时,所有线程会被唤醒,并且可以重置计数器重新使用。
面试官:说说Spring Boot的自动配置原理。
王铁牛:Spring Boot的自动配置原理是基于Spring的条件化配置。它会根据类路径下的依赖和配置文件中的属性,自动配置相应的Bean。通过@Conditional注解等机制,当满足特定条件时,才会创建对应的Bean。比如当类路径下有某个数据库驱动依赖时,就会自动配置数据源等相关的Bean。
面试官:第二轮提问完毕。最后一轮,讲讲MyBatis的缓存机制。
王铁牛:MyBatis有一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,在同一个SqlSession中,对相同的SQL语句进行查询时,会先从一级缓存中获取数据,如果有则直接返回,避免重复查询数据库。二级缓存是Mapper级别的缓存,多个SqlSession可以共享二级缓存。开启二级缓存后,当一个SqlSession查询到数据并提交后,会将数据放入二级缓存中,其他SqlSession查询相同数据时可以从二级缓存中获取。
面试官:Dubbo的集群容错策略有哪些?
王铁牛:Dubbo的集群容错策略有failover、failfast、failsafe、failback、forking等。failover是失败自动切换,当调用失败时,会自动重试其他服务器;failfast是快速失败,只调用一次,失败立即报错;failsafe是失败安全,出现异常时直接忽略,不抛出异常;failback是失败自动恢复,失败后会定时重试;forking是并行调用多个服务器,只要一个成功就返回。
面试官:说说RabbitMq的工作模式。
王铁牛:RabbitMq有直连交换机、扇形交换机、主题交换机等工作模式。直连交换机是根据绑定键将消息路由到绑定的队列;扇形交换机是将消息发送到所有绑定的队列;主题交换机可以根据绑定键的模式匹配,将消息路由到匹配的队列。
面试官:好,面试就到这里,回去等通知吧。
问题答案
- 线程池参数及作用:
- corePoolSize:核心线程数。当提交的任务数小于它时,会创建核心线程来执行任务。核心线程会一直存活在线程池中,除非设置了allowCoreThreadTimeOut为true,此时核心线程在空闲时也会在keepAliveTime后被销毁。
- maximumPoolSize:最大线程数。当任务数超过corePoolSize且workQueue满了时,会创建非核心线程来执行任务,直到线程数达到maximumPoolSize。
- keepAliveTime:线程池中非核心线程的存活时间。非核心线程在空闲时,经过keepAliveTime后会被销毁。
- unit:存活时间的单位,比如TimeUnit.SECONDS表示秒。
- workQueue:任务队列,存放提交的任务。当提交的任务数超过corePoolSize时,会将任务放入workQueue中。常见的有ArrayBlockingQueue(有界阻塞队列)、LinkedBlockingQueue(无界阻塞队列)等。
- threadFactory:线程工厂,用于创建线程。可以通过它来定制线程的名称、优先级等属性。
- handler:拒绝策略。当线程池满了且任务队列也满了时,会调用它来处理新提交的任务。常见的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用者线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
- HashMap底层实现原理:
- HashMap底层是数组+链表+红黑树。
- 计算key的哈希值,通过哈希值找到对应的数组位置。
- 如果该位置为空,则直接插入新节点。
- 如果不为空,则遍历链表或红黑树:
- 若找到相同key的节点,则更新其value。
- 若未找到,则插入新节点。当链表长度超过8且数组长度大于64时,链表会转换为红黑树,以提高查找效率。
- Spring核心特性:
- 依赖注入:方便地将对象之间的依赖关系进行解耦。通过构造器注入、setter注入等方式,将所需的对象注入到目标对象中,使得对象不需要自己创建依赖对象,降低了对象之间的耦合度。
- 面向切面编程(AOP):能让我们将一些通用的功能(如日志、权限验证等)从业务逻辑中分离出来。通过定义切面,在切入点处织入切面逻辑,使得业务逻辑代码更加简洁,关注点分离。
- IoC容器:负责创建、配置和管理对象。它通过读取配置文件或注解等方式,创建对象并注入其依赖关系,实现对象的生命周期管理和依赖注入。
- 事务管理:确保业务操作的原子性、一致性、隔离性和持久性。通过声明式事务或编程式事务,对业务方法进行事务控制,保证数据在操作过程中的完整性和正确性。
- JVM内存结构及垃圾回收算法:
-
程序计数器:记录当前线程执行的字节码指令地址,是线程私有的。
-
虚拟机栈:每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,也是线程私有的。
-
本地方法栈:与虚拟机栈类似,用于执行本地方法,也是线程私有的。
-
堆:是JVM中最大的一块内存区域,被所有线程共享,用于存放对象实例。
-
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
-
垃圾回收算法:
- 标记清除算法:先标记出需要回收的对象,然后统一回收。这种算法会产生内存碎片。
- 标记整理算法:先标记,然后将存活对象移动到一端,再清理另一端的内存,解决了内存碎片问题。
- 复制算法:将内存分为两块,每次只使用一块,当这块满了,就将存活对象复制到另一块,然后清理原来的那块。适用于新生代等对象存活率较低的区域。
- 分代收集算法:根据对象的存活周期将堆分为新生代、老年代等,针对不同代采用不同的垃圾回收算法。新生代一般采用复制算法,老年代采用标记清除或标记整理算法。
-
- CountDownLatch和CyclicBarrier区别:
- CountDownLatch:
- 是一个同步辅助类,它允许一个或多个线程等待其他线程完成操作之后再执行。
- 通过一个计数器来实现,调用countDown方法会使计数器减1,当计数器为0时,等待的线程会被唤醒。
- 主要用于一个线程等待多个线程完成特定操作后再继续执行,比如主线程等待多个子线程完成数据计算后再汇总结果。
- CyclicBarrier:
- 也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。
- 内部有一个计数器,当每个线程调用await方法时,计数器会减1,当计数器为0时,所有线程会被唤醒,并且可以重置计数器重新使用。
- 适用于多个线程相互协作,共同完成某个阶段的任务,比如多个运动员在接力比赛中,需要在某个特定点等待其他队友完成交接后再继续。
- CountDownLatch:
- Spring Boot自动配置原理:
- 基于Spring的条件化配置。
- 通过@Conditional注解等机制,当满足特定条件时,才会创建对应的Bean。
- 根据类路径下的依赖和配置文件中的属性,自动配置相应的Bean。例如当类路径下有某个数据库驱动依赖时,就会自动配置数据源等相关的Bean。它会扫描类路径下的所有jar包,根据依赖情况自动装配合适的Bean,并根据配置文件中的属性进行进一步的定制化配置。
- MyBatis缓存机制:
- 一级缓存:是SqlSession级别的缓存,在同一个SqlSession中,对相同的SQL语句进行查询时,会先从一级缓存中获取数据,如果有则直接返回,避免重复查询数据库。当SqlSession关闭时,一级缓存会被清空。
- 二级缓存:是Mapper级别的缓存,多个SqlSession可以共享二级缓存。开启二级缓存后,当一个SqlSession查询到数据并提交后,会将数据放入二级缓存中,其他SqlSession查询相同数据时可以从二级缓存中获取。二级缓存的范围更大,适用于多个SqlSession对相同数据的查询场景,能提高查询效率。
- Dubbo集群容错策略:
- failover:失败自动切换,当调用失败时,会自动重试其他服务器。适用于读操作等对结果准确性要求不是非常高的场景。
- failfast:快速失败,只调用一次,失败立即报错。适用于对错误比较敏感,不希望进行重试的场景,比如写操作。
- failsafe:失败安全,出现异常时直接忽略,不抛出异常。适用于对结果要求不高,允许部分失败的场景,比如记录日志等操作。
- failback:失败自动恢复,失败后会定时重试。适用于可以容忍暂时失败,后续希望自动恢复的场景,比如网络不稳定时的一些操作。
- forking:并行调用多个服务器,只要一个成功就返回。适用于需要快速获取结果,对响应时间要求较高的场景。
- RabbitMq工作模式:
- 直连交换机:根据绑定键将消息路由到绑定的队列。生产者发送消息时指定路由键,队列绑定到交换机时也指定绑定键,当路由键与绑定键匹配时,消息会被路由到对应的队列。
- 扇形交换机:将消息发送到所有绑定的队列,不考虑路由键。适用于需要将消息广播给多个消费者的场景。
- 主题交换机:可以根据绑定键的模式匹配,将消息路由到匹配的队列。绑定键和路由键都可以使用通配符(如*匹配一个单词,#匹配零个或多个单词),通过灵活的模式匹配来实现消息的精准路由。