《互联网大厂面试:Java 核心、JUC、JVM 等知识大考验》

72 阅读13分钟

互联网大厂面试:Java 核心、JUC、JVM 等知识大考验

在互联网大厂的一间安静的面试室内,严肃的面试官正对面坐着紧张的求职者王铁牛,一场关于 Java 技术知识的面试拉开了帷幕。

第一轮面试开始 面试官:“首先问你几个 Java 核心知识的问题。Java 中基本数据类型有哪些?” 王铁牛:“有 byte、short、int、long、float、double、char、boolean。” 面试官:“回答正确,基础很扎实。那在 Java 里,什么是自动装箱和自动拆箱?” 王铁牛:“自动装箱就是把基本数据类型自动转换为对应的包装类,自动拆箱就是把包装类自动转换为对应的基本数据类型。比如 Integer i = 10 就是自动装箱,int j = i 就是自动拆箱。” 面试官:“非常棒,理解得很清晰。那 Java 中的访问修饰符有哪些,它们的作用范围是怎样的?” 王铁牛:“有 public、protected、default(默认,不写修饰符时)、private。public 可以在任何地方被访问;protected 可以在同一个包内以及不同包的子类中访问;default 只能在同一个包内访问;private 只能在本类中访问。” 面试官:“回答得很好,看来你对 Java 核心基础掌握得很牢固。”

第二轮面试开始 面试官:“接下来谈谈 JUC、多线程和线程池相关的知识。JUC 包是什么,它有什么作用?” 王铁牛:“JUC 就是 java.util.concurrent 包,它提供了一些用于多线程编程的工具类,能帮助我们更方便地进行并发编程。” 面试官:“不错。那多线程有几种创建方式?” 王铁牛:“有三种,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。” 面试官:“回答正确。那线程池有哪些创建方式,各自的特点是什么?” 王铁牛:“可以通过 Executors 工具类创建,像 newFixedThreadPool 是创建固定大小的线程池,newCachedThreadPool 是创建可缓存的线程池,newSingleThreadExecutor 是创建单线程的线程池。还有通过 ThreadPoolExecutor 自定义创建。” 面试官:“回答得很全面,对这块知识有一定的了解。”

第三轮面试开始 面试官:“现在来问一些关于框架的问题。Spring 框架的核心特性有哪些?” 王铁牛:“有 IOC(控制反转)和 AOP(面向切面编程)。IOC 是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改原有代码的基础上对程序进行增强。” 面试官:“很好。那 Spring Boot 和 Spring 有什么区别和联系?” 王铁牛:“Spring Boot 是基于 Spring 的,它简化了 Spring 项目的开发,提供了自动配置等功能,让我们可以更快速地搭建项目。” 面试官:“不错。MyBatis 中 #{} 和 {} 的区别是什么?” **王铁牛**:“#{} 是预编译处理,会把参数当成一个字符串,能防止 SQL 注入;{} 是直接替换,会有 SQL 注入的风险。” 面试官:“回答得很准确。Dubbo 是什么,它的作用是什么?” 王铁牛:“Dubbo 是一个分布式服务框架,能实现服务的注册、发现和调用,提高系统的可扩展性和性能。” 面试官:“整体表现不错。不过接下来这个问题,RabbitMQ 中的消息确认机制是怎样的?” 王铁牛:“嗯……这个……好像是和消息发送有关,具体我有点不太清楚了。” 面试官:“那 xxl - job 是做什么的,它的核心原理是什么?” 王铁牛:“xxl - job 是一个分布式任务调度平台,原理嘛……我没太研究过,不太能说清楚。” 面试官:“最后问你 Redis,Redis 的持久化机制有哪些?” 王铁牛:“我记得有 RDB 和 AOF,具体细节我就说不太准了。”

面试官总结:“王铁牛,通过这三轮面试,能看出你对 Java 核心知识、JUC、多线程、线程池以及一些框架的基础部分掌握得还可以,对于一些常见问题回答得比较准确,展现出了一定的知识储备和学习能力。在回答简单问题时表现不错,思路清晰,说明你有扎实的基本功。但在面对一些稍微复杂和深入的问题,比如 RabbitMQ 的消息确认机制、xxl - job 的核心原理以及 Redis 持久化机制的细节时,你回答得不够清晰,甚至有些答不上来,这反映出你在这些领域的学习还不够深入和全面。我们后续会综合评估所有面试者的情况,你先回家等通知吧。”

问题答案详细解析

第一轮:Java 核心知识

  1. Java 中基本数据类型有哪些?
    • Java 有 8 种基本数据类型,分为 4 类:
      • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
      • 浮点类型:float(4 字节)、double(8 字节)。
      • 字符类型:char(2 字节)。
      • 布尔类型:boolean(理论上 1 位,但 JVM 通常按 1 字节处理)。
  2. 什么是自动装箱和自动拆箱?
    • 自动装箱:是指 Java 编译器在基本数据类型和对应的包装类之间进行自动转换。例如,当把一个基本数据类型赋值给对应的包装类对象时,会自动进行装箱操作。如 Integer i = 10; 这里 10 是 int 类型,会自动装箱为 Integer 类型。
    • 自动拆箱:相反,当把一个包装类对象赋值给对应的基本数据类型时,会自动进行拆箱操作。如 int j = i; 这里 i 是 Integer 类型,会自动拆箱为 int 类型。
  3. Java 中的访问修饰符有哪些,它们的作用范围是怎样的?
    • public:被 public 修饰的类、方法、变量可以在任何地方被访问,没有访问权限限制。
    • protected:可以在同一个包内的类中访问,也可以在不同包的子类中访问。
    • default(默认,不写修饰符时):只能在同一个包内的类中访问。
    • private:只能在本类中访问,其他类无法直接访问。

第二轮:JUC、多线程和线程池

  1. JUC 包是什么,它有什么作用?
    • JUC 即 java.util.concurrent 包,是 Java 提供的用于并发编程的工具包。它提供了许多用于多线程编程的类和接口,比如线程池相关的类(ExecutorService、ThreadPoolExecutor 等)、并发集合类(ConcurrentHashMap、CopyOnWriteArrayList 等)、锁机制(ReentrantLock、ReadWriteLock 等)以及一些同步工具类(CountDownLatch、CyclicBarrier 等),可以帮助开发者更方便、高效地进行并发编程。
  2. 多线程有几种创建方式?
    • 继承 Thread 类:创建一个类继承 Thread 类,重写 run 方法,在 run 方法中定义线程要执行的任务。然后创建该类的对象并调用 start 方法启动线程。示例代码如下:
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running");
    }
}
// 在 main 方法中使用
public static void main(String[] args) {
    MyThread thread = new MyThread();
    thread.start();
}
- 实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run 方法。然后将该类的对象作为参数传递给 Thread 类的构造函数,再调用 Thread 对象的 start 方法启动线程。示例代码如下:
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable thread is running");
    }
}
// 在 main 方法中使用
public static void main(String[] args) {
    MyRunnable runnable = new MyRunnable();
    Thread thread = new Thread(runnable);
    thread.start();
}
- 实现 Callable 接口:创建一个类实现 Callable 接口,实现 call 方法,该方法可以有返回值。然后使用 FutureTask 包装 Callable 对象,再将 FutureTask 对象作为参数传递给 Thread 类的构造函数,最后调用 Thread 对象的 start 方法启动线程。可以通过 FutureTask 的 get 方法获取线程执行的结果。示例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 2;
    }
}
// 在 main 方法中使用
public static void main(String[] args) throws Exception {
    MyCallable callable = new MyCallable();
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    Thread thread = new Thread(futureTask);
    thread.start();
    Integer result = futureTask.get();
    System.out.println("Result: " + result);
}
  1. 线程池有哪些创建方式,各自的特点是什么?
    • 通过 Executors 工具类创建:
      • newFixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量是固定的。当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,则任务会被放入任务队列中等待执行。适用于需要控制并发线程数量的场景。
      • newCachedThreadPool:创建一个可缓存的线程池,线程池中的线程数量会根据任务的数量动态调整。如果有新任务提交,而线程池中没有空闲线程,则会创建新的线程来执行任务;如果线程空闲时间超过 60 秒,则会被回收。适用于任务数量不确定、执行时间较短的场景。
      • newSingleThreadExecutor:创建一个单线程的线程池,线程池中只有一个线程,任务会按照提交的顺序依次执行。适用于需要保证任务顺序执行的场景。
    • 通过 ThreadPoolExecutor 自定义创建:可以根据自己的需求灵活配置线程池的核心线程数、最大线程数、任务队列、线程空闲时间等参数。示例代码如下:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // 核心线程数
                5, // 最大线程数
                60, // 线程空闲时间
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10) // 任务队列
        );
        // 提交任务
        executor.execute(() -> System.out.println("Task is running"));
        // 关闭线程池
        executor.shutdown();
    }
}

第三轮:框架相关

  1. Spring 框架的核心特性有哪些?
    • IOC(控制反转):也称为依赖注入(DI),是指将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。在传统的编程中,对象的创建和依赖关系的管理是由程序员手动完成的,而在 Spring 中,这些工作由 Spring 容器负责。通过 IOC,降低了代码的耦合度,提高了代码的可维护性和可测试性。例如,一个类依赖另一个类的对象,不需要在类中手动创建该对象,而是通过 Spring 容器注入进来。
    • AOP(面向切面编程):是一种编程范式,它允许开发者在不修改原有代码的基础上,对程序进行增强。在 AOP 中,将一些通用的功能(如日志记录、事务管理等)封装成切面,然后在程序的特定切入点(如方法调用、异常抛出等)插入这些切面,从而实现对程序的增强。Spring AOP 基于动态代理实现,有 JDK 动态代理和 CGLIB 动态代理两种方式。
  2. Spring Boot 和 Spring 有什么区别和联系?
    • 联系:Spring Boot 是基于 Spring 构建的,它继承了 Spring 的核心特性,如 IOC 和 AOP。Spring Boot 可以使用 Spring 的所有功能,并且可以和 Spring 生态系统中的其他组件(如 Spring Data、Spring Security 等)无缝集成。
    • 区别:Spring Boot 主要是为了简化 Spring 项目的开发,它提供了自动配置功能,通过约定大于配置的原则,减少了开发者的配置工作量。开发者只需要引入相应的依赖,Spring Boot 就会自动进行配置,快速搭建起一个可运行的项目。而 Spring 项目需要开发者手动进行大量的配置,如配置数据源、事务管理器等。
  3. MyBatis 中 #{} 和 ${} 的区别是什么?
    • #{}:是预编译处理,在 SQL 语句中使用 #{} 时,MyBatis 会将其替换为占位符 ?,然后使用 PreparedStatement 进行预编译,将参数作为一个字符串传递给 SQL 语句。这样可以防止 SQL 注入攻击,因为参数会被自动进行转义处理。例如:
<select id="getUserById" parameterType="int" resultType="User">
    SELECT * FROM users WHERE id = #{id}
</select>
- ${}:是直接替换,在 SQL 语句中使用 ${} 时,MyBatis 会将其直接替换为参数的值。这样可能会导致 SQL 注入攻击,因为参数没有经过任何处理就直接拼接到 SQL 语句中。例如:
<select id="getUserByUsername" parameterType="String" resultType="User">
    SELECT * FROM users WHERE username = '${username}'
</select>
一般情况下,建议使用 #{} 来防止 SQL 注入,只有在需要动态生成 SQL 语句的表名、列名等场景下才使用 ${}。

4. Dubbo 是什么,它的作用是什么? - Dubbo 是一个高性能的分布式服务框架,由阿里巴巴开源。它提供了服务注册与发现、远程调用、集群容错、负载均衡等功能,用于解决分布式系统中服务之间的通信和调用问题。在分布式系统中,各个服务可能部署在不同的服务器上,Dubbo 可以帮助服务之间进行高效的远程调用,提高系统的可扩展性和性能。例如,一个电商系统中有订单服务、商品服务、用户服务等,这些服务可以通过 Dubbo 进行相互调用,实现系统的功能。 5. RabbitMQ 中的消息确认机制是怎样的? - RabbitMQ 的消息确认机制主要有两种:生产者确认和消费者确认。 - 生产者确认:生产者在发送消息时,可以通过开启确认模式来确保消息已经成功到达 RabbitMQ 服务器。有两种确认模式: - 普通确认模式:生产者发送一条消息后,调用 waitForConfirms() 方法等待服务器的确认。如果在指定时间内收到确认,则表示消息发送成功;否则表示发送失败。 - 批量确认模式:生产者可以批量发送消息,然后调用 waitForConfirmsOrDie() 方法等待服务器对批量消息的确认。如果所有消息都被确认,则表示批量发送成功;如果有消息未被确认,则抛出异常。 - 消费者确认:消费者在接收到消息后,需要向 RabbitMQ 服务器发送确认消息,表示消息已经被成功处理。有两种确认模式: - 自动确认:消费者接收到消息后,RabbitMQ 会自动认为消息已经被处理,立即将消息从队列中删除。这种模式简单,但可能会导致消息丢失,因为如果消费者在处理消息时出现异常,消息已经被删除,无法再次处理。 - 手动确认:消费者需要手动调用 basicAck() 方法向服务器发送确认消息。在消息处理完成后,调用该方法表示消息已经被成功处理,服务器会将消息从队列中删除。如果处理过程中出现异常,可以调用 basicNack()basicReject() 方法拒绝消息,服务器可以选择将消息重新放回队列或丢弃。 6. xxl - job 是做什么的,它的核心原理是什么? - xxl - job 是一个分布式任务调度平台,用于解决分布式系统中任务的调度和执行问题。它可以实现任务的定时执行、任务的分布式执行、任务的失败重试等功能,提高了任务调度的可靠性和灵活性。 - 核心原理:xxl - job 主要由调度中心和执行器两部分组成。 - 调度中心:负责任务的管理和调度,包括任务的添加、修改、删除、启动、停止等操作,以及任务的定时触发。调度中心会根据任务的配置信息(如 cron 表达式)定时触发任务,并将任务信息发送给相应的执行器。 - 执行器:负责任务的执行,部署在各个业务服务器上。执行器会与调度中心建立连接,接收调度中心发送的任务信息,并执行相应的任务。执行器执行任务完成后,会将执行结果返回给调度中心。 7. Redis 的持久化机制有哪些? - RDB(Redis Database):是 Redis 的一种快照持久化方式,它会在某个时间点将 Redis 内存中的数据以二进制的形式