第一轮面试 面试官:首先问你几个基础问题。Java 中 ArrayList 和 LinkedList 的区别是什么?在实际业务场景中,什么情况下会优先选择 ArrayList 呢? 王铁牛:ArrayList 是基于数组实现的,LinkedList 是基于链表实现的。在需要频繁随机访问的场景下,优先选择 ArrayList,因为数组可以通过下标直接访问,效率高。 面试官:嗯,回答得不错。那 HashMap 在 JDK1.7 和 JDK1.8 中有什么主要区别? 王铁牛:1.8 之后 HashMap 引入了红黑树,当链表长度达到 8 且数组长度达到 64 时,链表会转成红黑树,提高查找效率。1.7 只是单纯的链表结构。 面试官:很好。Spring 框架中,IOC 和 AOP 分别是什么,在项目中有什么实际应用场景? 王铁牛:IOC 是控制反转,把对象创建和管理的控制权交给 Spring 容器,比如在一个电商项目中,各个服务层对象的创建都由 Spring 容器管理。AOP 是面向切面编程,像日志记录、事务管理这些功能,通过 AOP 可以不侵入业务代码实现,比如在订单处理的业务方法前后添加事务管理。
第二轮面试 面试官:接下来深入一些。多线程编程中,线程池的核心参数有哪些,分别有什么作用?在高并发业务场景下,如何合理配置线程池参数? 王铁牛:核心参数有 corePoolSize、maximumPoolSize、keepAliveTime 等。corePoolSize 是核心线程数,maximumPoolSize 是最大线程数,keepAliveTime 是线程存活时间。在高并发场景下,要根据任务类型和服务器资源来配置,比如 CPU 密集型任务,核心线程数可以设置为 CPU 核心数。但具体怎么精准配置,我还不是特别清楚。 面试官:JVM 的内存模型是怎样的,堆和栈分别存储什么内容,在内存溢出时,如何判断是堆溢出还是栈溢出? 王铁牛:JVM 内存模型分为堆、栈、方法区等。堆存储对象实例,栈存储局部变量和方法调用。内存溢出时,如果是堆溢出,可能是创建了太多对象,栈溢出可能是方法递归调用太深。不过具体排查方法,我不太确定。 面试官:MyBatis 中 #{} 和 {} 的区别是什么,在 SQL 注入防范方面有什么不同? **王铁牛**:#{} 是预编译处理,{} 是字符串替换。#{} 能防止 SQL 注入,因为预编译时会把参数当成占位符,而 ${} 直接替换字符串,容易导致 SQL 注入。
第三轮面试 面试官:现在问几个更复杂的问题。Dubbo 框架在分布式系统中有哪些核心功能,如何实现服务治理的?在微服务架构下,Dubbo 与 Spring Cloud 有什么区别和适用场景? 王铁牛:Dubbo 有服务注册与发现、负载均衡等功能。通过注册中心实现服务治理。Dubbo 和 Spring Cloud 区别嘛,Dubbo 更专注于服务治理,Spring Cloud 功能更全面。适用场景,Dubbo 适合性能要求高的场景,Spring Cloud 适合快速搭建微服务。具体细节我说不太清楚。 面试官:RabbitMQ 在消息队列应用中有哪些常见的问题,比如消息丢失、消息重复消费,如何解决这些问题?在高并发的电商下单场景中,如何利用 RabbitMQ 实现削峰填谷? 王铁牛:消息丢失可以通过开启持久化解决,消息重复消费可以通过幂等性处理。在电商下单场景,把订单消息放入队列,服务器按一定速率处理,实现削峰填谷。但具体代码实现和细节,我不太熟悉。 面试官:xxl - job 是如何实现分布式任务调度的,在实际项目中,如何处理任务调度失败的情况? 王铁牛:xxl - job 通过调度中心和执行器实现分布式任务调度。任务调度失败,可能重试几次,或者记录日志通知运维人员。具体处理方式,我还得再研究研究。
面试总结:从这三轮面试来看,你对于一些基础的 Java 核心知识、框架概念有一定的了解,回答得也比较准确,这一点值得肯定。但在一些进阶和复杂的技术问题上,尤其是涉及到实际业务场景中的深度应用和问题解决,还存在不足。对于技术细节和原理的掌握不够深入,在面对复杂问题时,思路不够清晰,回答不够全面准确。回去之后可以针对这些薄弱点进一步学习和研究。今天的面试就到这里,你回去等通知吧。
问题答案:
- ArrayList 和 LinkedList 的区别及适用场景:
- 区别:
- 数据结构:ArrayList 基于数组,LinkedList 基于双向链表。数组在内存中是连续存储的,链表节点在内存中不连续,通过指针相连。
- 随机访问效率:ArrayList 支持随机访问,通过下标直接定位元素,时间复杂度为 O(1);LinkedList 随机访问效率低,需要从头或尾遍历链表,时间复杂度为 O(n)。
- 插入和删除效率:在 ArrayList 中间插入或删除元素,需要移动大量元素,时间复杂度为 O(n);LinkedList 在中间插入或删除元素只需修改指针,时间复杂度为 O(1)。但如果在 ArrayList 尾部插入或删除元素,时间复杂度为 O(1)。
- 适用场景:
- ArrayList:适用于频繁随机访问的场景,如数据查询。因为数组的随机访问特性,能快速定位元素。例如在一个学生成绩查询系统中,需要频繁根据学生编号查询成绩,使用 ArrayList 存储学生成绩信息效率较高。
- LinkedList:适用于频繁插入和删除操作的场景,如实现栈或队列。例如在一个任务队列中,不断有新任务加入(插入)和已完成任务移除(删除),使用 LinkedList 更合适。
- 区别:
- HashMap 在 JDK1.7 和 JDK1.8 的区别:
- 数据结构:
- JDK1.7:采用数组 + 链表的结构。当发生哈希冲突时,新元素以头插法插入到链表头部。
- JDK1.8:采用数组 + 链表 + 红黑树的结构。当链表长度达到 8 且数组长度达到 64 时,链表会转成红黑树,以提高查找效率。新元素以尾插法插入到链表尾部。
- 性能:
- JDK1.7:在哈希冲突严重时,链表会很长,查找效率降低,时间复杂度为 O(n)。
- JDK1.8:引入红黑树后,在哈希冲突严重时,查找效率提高,红黑树的查找时间复杂度为 O(logn)。
- 数据结构:
- Spring 中 IOC 和 AOP:
- IOC(控制反转):
- 概念:把对象创建和管理的控制权交给 Spring 容器,而不是由应用程序自己创建和管理对象。
- 实际应用场景:在企业级开发中,一个大型项目可能有多个服务层、持久层对象。例如在一个电商项目中,订单服务、商品服务等对象的创建和依赖关系管理都由 Spring 容器负责。通过配置文件或注解,Spring 容器可以自动创建对象并注入依赖,开发者只需要关注业务逻辑,提高了代码的可维护性和可测试性。
- AOP(面向切面编程):
- 概念:将一些与业务无关但又横切于多个业务模块的功能(如日志记录、事务管理、权限控制等)抽取出来,形成一个独立的切面,通过动态代理等技术,在不修改原有业务代码的情况下,将这些切面功能织入到业务逻辑中。
- 实际应用场景:以电商订单处理为例,在订单创建、支付等业务方法前后添加事务管理,保证数据的一致性。通过 AOP,只需要定义一个事务管理的切面,然后配置在需要的业务方法上,而不需要在每个业务方法中重复编写事务管理代码,提高了代码的复用性和可维护性。
- IOC(控制反转):
- 线程池核心参数及高并发场景配置:
- 核心参数:
- corePoolSize:核心线程数,线程池初始化时创建的线程数,这些线程会一直存活,即使处于空闲状态,除非设置了 allowCoreThreadTimeOut 为 true。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数。当任务队列已满且核心线程都在忙碌时,线程池会创建新线程,直到达到最大线程数。
- keepAliveTime:线程存活时间,当线程数大于核心线程数时,多余的空闲线程等待新任务的最长时间,超过这个时间,线程会被销毁。
- unit:keepAliveTime 的时间单位,如 TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)等。
- workQueue:任务队列,用于存放等待执行的任务。常见的任务队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)等。
- threadFactory:线程工厂,用于创建新线程,可自定义线程的名称、优先级等属性。
- handler:拒绝策略,当任务队列已满且线程数达到最大线程数时,新任务到来的处理策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用者线程处理任务)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
- 高并发场景配置:
- 任务类型:
- CPU 密集型任务:核心线程数可设置为 CPU 核心数或 CPU 核心数 + 1。因为 CPU 密集型任务主要消耗 CPU 资源,过多的线程会增加线程上下文切换开销,降低性能。例如在一个图像识别项目中,图像数据处理是 CPU 密集型任务,可根据服务器 CPU 核心数配置线程池核心线程数。
- I/O 密集型任务:核心线程数可设置为 2 * CPU 核心数。I/O 密集型任务在等待 I/O 操作完成时,线程处于空闲状态,可利用更多线程处理其他任务。比如在一个文件上传下载系统中,文件读写是 I/O 密集型任务,适当增加核心线程数可提高系统吞吐量。
- 服务器资源:要考虑服务器的内存、CPU 等资源。如果服务器内存有限,任务队列不宜设置过大,否则可能导致内存溢出。同时,最大线程数也不能设置过高,避免过多线程竞争资源,导致系统性能下降。
- 任务类型:
- 核心参数:
- JVM 内存模型及内存溢出判断:
- JVM 内存模型:
- 堆(Heap):是 JVM 中最大的一块内存区域,用于存储对象实例和数组。堆被所有线程共享,根据对象的生命周期不同,堆又分为新生代(Young Generation)和老年代(Old Generation),新生代又分为 Eden 区和两个 Survivor 区。对象一般首先在 Eden 区分配内存,经过几次垃圾回收后,如果对象还存活,会被移动到老年代。
- 栈(Stack):每个线程都有自己的栈,用于存储局部变量、方法调用和返回值等。栈由一个个栈帧组成,每个方法调用对应一个栈帧。栈的大小在编译期就确定了,一般比较小。
- 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量等数据。在 JDK8 及以后,方法区被元空间(Meta Space)取代,元空间使用本地内存。
- 程序计数器(Program Counter Register):每个线程都有一个程序计数器,用于记录当前线程执行的字节码指令地址。它是线程私有的,占用内存空间非常小。
- 本地方法栈(Native Method Stack):与 Java 栈类似,不过它是为 JVM 执行本地方法(Native Method)服务的。
- 内存溢出判断:
- 堆溢出:
- 原因:通常是创建了太多对象,超出了堆的最大内存限制。例如在一个爬虫项目中,如果没有合理控制爬取的数据量,不断创建对象存储爬取到的数据,可能导致堆内存溢出。
- 排查方法:通过分析垃圾回收日志,查看是否频繁进行 Full GC 且老年代持续增长。使用工具如 VisualVM 等,监控堆内存的使用情况,查看对象的创建和存活情况,找出创建过多对象的代码位置。
- 栈溢出:
- 原因:常见于方法递归调用太深,没有正确设置递归终止条件。例如在一个计算阶乘的方法中,如果没有终止条件,会不断压栈,导致栈溢出。
- 排查方法:查看异常堆栈信息,找到递归调用的方法,检查递归终止条件是否正确设置。可以通过调整栈的大小参数(如 -Xss)来缓解栈溢出问题,但根本还是要修复递归调用的代码逻辑。
- 堆溢出:
- JVM 内存模型:
- MyBatis 中 #{} 和 ${} 的区别及 SQL 注入防范:
- 区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为?,然后使用 PreparedStatement 进行参数设置。这样可以防止 SQL 注入,因为参数值不会被当成 SQL 语句的一部分解析,而是作为普通参数传递。
- **{} 时,会直接将 ${} 替换为变量的值,这就存在 SQL 注入风险。例如,如果变量值是恶意的 SQL 语句,就会被直接执行。
- SQL 注入防范:
- #{}:由于采用预编译机制,将参数值与 SQL 语句分离,有效防止了 SQL 注入。在开发中,对于用户输入的参数,如登录名、密码等,都应使用 #{} 方式。
- **{},要对传入的值进行严格的校验和过滤,避免恶意 SQL 语句传入。
- 区别:
- Dubbo 核心功能、服务治理及与 Spring Cloud 的区别和适用场景:
- Dubbo 核心功能:
- 服务注册与发现:通过注册中心(如 Zookeeper),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务地址,实现服务的动态发现和调用。
- 负载均衡:Dubbo 提供多种负载均衡策略,如随机、轮询、最少活跃调用数等,在多个服务提供者之间均衡分配请求,提高系统的可用性和性能。
- 远程调用:Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等,实现不同服务之间的远程通信。
- 服务治理:
- 服务监控:通过 Dubbo 监控中心,实时监控服务的调用次数、响应时间、成功率等指标,方便运维人员了解系统运行状态。
- 服务降级:当某个服务出现故障或性能下降时,为了保证整体系统的可用性,可对该服务进行降级处理,返回默认值或错误提示,避免级联故障。
- 服务限流:为了防止某个服务被过度调用,导致系统资源耗尽,可对服务进行限流,限制单位时间内的请求次数。
- 与 Spring Cloud 的区别:
- 功能定位:
- Dubbo:更专注于服务治理,如服务注册与发现、负载均衡、远程调用等,在高性能 RPC 调用方面表现出色。
- Spring Cloud:功能更全面,涵盖了服务治理、配置管理、熔断器、网关等多个方面,提供了一站式的微服务解决方案。
- 技术架构:
- Dubbo:采用 RPC 通信方式,性能较高,但对服务接口的兼容性要求较高。
- Spring Cloud:基于 RESTful 风格的 HTTP 通信,灵活性高,易于与其他系统集成,但性能相对 RPC 略低。
- 功能定位:
- 适用场景:
- Dubbo:适合对性能要求较高,服务接口相对稳定的场景,如传统企业的内部系统,对服务调用性能和效率要求严格。
- Spring Cloud:适合快速搭建微服务架构,对灵活性和扩展性要求较高的场景,如互联网创业公司,需要快速迭代和集成各种第三方服务。
- Dubbo 核心功能:
- RabbitMQ 常见问题及电商下单场景应用:
- 消息丢失问题及解决:
- 生产者消息丢失:
- 原因:生产者发送消息后,可能因为网络问题等原因,消息未成功到达 RabbitMQ 服务器。
- 解决方法:开启生产者确认机制(confirm 机制),生产者发送消息后,RabbitMQ 会返回确认信息,告知生产者消息是否成功接收。如果未收到确认信息,生产者可进行重试。
- 消息在队列中丢失:
- 原因:RabbitMQ 服务器重启或崩溃,队列中的消息未持久化,导致消息丢失。
- 解决方法:对队列和消息都进行持久化。创建队列时设置 durable 属性为 true,发送消息时设置 MessageProperties 的 DELIVERY_MODE_PERSISTENT 标志,这样消息会被持久化到磁盘,即使服务器重启,消息也不会丢失。
- 消费者消息丢失:
- 原因:消费者接收消息后,还未处理完就宕机,RabbitMQ
- 生产者消息丢失:
- 消息丢失问题及解决: