互联网大厂面试:Java核心知识、JUC、JVM等技术大考验
面试的房间里,气氛紧张而严肃。面试官正襟危坐,面前摆放着王铁牛的简历。王铁牛坐在对面,眼神中透露出一丝紧张和期待。
第一轮面试开始 面试官:“我们先从基础的 Java 核心知识问起。Java 中基本数据类型有哪些?” 王铁牛:“这个我知道,有 byte、short、int、long、float、double、char、boolean。” 面试官:“不错,回答得很准确。那 String 是基本数据类型吗?” 王铁牛:“不是,String 是引用数据类型,它是一个类。” 面试官:“很好。那说说 Java 中的自动装箱和拆箱是什么?” 王铁牛:“自动装箱就是把基本数据类型转换为对应的包装类类型,比如 Integer i = 10; 这里就进行了自动装箱。拆箱就是把包装类类型转换为基本数据类型,像 int j = i; 就是拆箱。” 面试官:“回答得很清晰,基础很扎实。接下来看看 JUC 方面的内容。JUC 包是什么?” 王铁牛:“JUC 是 java.util.concurrent 包的简称,它提供了一些用于多线程编程的工具类和接口。”
第二轮面试开始 面试官:“上一轮你表现得不错。那我们深入聊聊 JVM。JVM 有哪些内存区域?” 王铁牛:“有方法区、堆、虚拟机栈、本地方法栈和程序计数器。” 面试官:“很好。那堆内存又可以细分为哪些区域?” 王铁牛:“可以分为新生代和老年代,新生代又可以分为 Eden 区和两个 Survivor 区。” 面试官:“回答正确。那说说垃圾回收算法有哪些?” 王铁牛:“有标记 - 清除算法、标记 - 整理算法、复制算法和分代收集算法。” 面试官:“看来你对 JVM 掌握得很扎实。再问一个多线程的问题,线程的生命周期有哪些状态?” 王铁牛:“有新建、就绪、运行、阻塞和死亡这几种状态。”
第三轮面试开始 面试官:“前面两轮你都回答得很好。现在我们看看一些框架相关的内容。Spring 框架的核心特性有哪些?” 王铁牛:“有依赖注入和面向切面编程。” 面试官:“那 Spring 的依赖注入有哪些方式?” 王铁牛:“有构造器注入、属性注入和接口注入。” 面试官:“不错。Spring Boot 相比 Spring 有什么优势?” 王铁牛:“Spring Boot 可以快速搭建项目,减少了很多配置,有自动配置的功能。” 面试官:“回答得还行。那 MyBatis 中 #{} 和 {} 的区别是什么?” **王铁牛**:“呃……这个……好像 #{} 是预编译的,能防止 SQL 注入,{} 就是直接替换吧。” 面试官:“回答得不太完整。再问一个 Dubbo 的问题,Dubbo 的服务注册与发现机制是怎样的?” 王铁牛:“这个……我有点不太清楚,好像是通过注册中心来实现的。”
面试结束,面试官放下手中的笔,看着王铁牛说:“今天的面试就到这里了,你前面两轮对于 Java 核心知识、JUC、JVM、多线程等基础部分回答得很不错,展现出了一定的知识储备和理解能力。但在第三轮框架相关的问题上,对于一些细节的回答不够完善,比如 MyBatis 和 Dubbo 的问题,说明你对这些框架的掌握还不够深入。你先回家等通知吧,我们会综合评估后给你答复。”
问题答案详细解析
- Java 中基本数据类型有哪些?
- Java 有 8 种基本数据类型,可分为 4 类:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(理论上 1 位,但实际实现通常占 1 字节)。
- Java 有 8 种基本数据类型,可分为 4 类:
- String 是基本数据类型吗?
- String 不是基本数据类型,而是引用数据类型。它是 Java 中一个预定义的类,用于表示字符串。基本数据类型是 Java 语言内置的,而引用数据类型是通过类或接口来定义的。
- Java 中的自动装箱和拆箱是什么?
- 自动装箱是指 Java 编译器在基本数据类型和对应的包装类之间进行的自动转换。例如,当把一个基本数据类型赋值给对应的包装类对象时,编译器会自动调用包装类的构造方法或 valueOf() 方法进行装箱操作。如 Integer i = 10; 实际上是 Integer i = Integer.valueOf(10);。
- 自动拆箱则相反,当把一个包装类对象赋值给基本数据类型时,编译器会自动调用包装类的 xxxValue() 方法进行拆箱操作。如 int j = i; 实际上是 int j = i.intValue();。
- JUC 包是什么?
- JUC 即 java.util.concurrent 包,它是 Java 5 引入的用于多线程编程的工具包。该包提供了许多高级的并发编程工具和类,如线程池(ExecutorService)、锁(Lock)、信号量(Semaphore)、倒计时门闩(CountDownLatch)等,方便开发者进行高效的并发编程。
- JVM 有哪些内存区域?
- JVM 主要有以下 5 个内存区域:
- 方法区:用于存储类的元数据信息、常量、静态变量等,在 JDK 1.8 之前也被称为永久代,JDK 1.8 及以后改为元空间。
- 堆:是 JVM 中最大的一块内存区域,所有对象实例和数组都在此分配内存,是垃圾回收的主要区域。
- 虚拟机栈:每个线程在创建时都会创建一个虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 本地方法栈:与虚拟机栈类似,不过它是为本地方法服务的。
- 程序计数器:可以看作是当前线程所执行的字节码的行号指示器,每个线程都有独立的程序计数器。
- JVM 主要有以下 5 个内存区域:
- 堆内存又可以细分为哪些区域?
- 堆内存主要分为新生代和老年代。新生代用于存放新创建的对象,又可以进一步细分为 Eden 区和两个 Survivor 区(通常称为 From 区和 To 区)。新创建的对象一般先存放在 Eden 区,当 Eden 区满时,会进行一次 Minor GC,存活的对象会被复制到其中一个 Survivor 区,当这个 Survivor 区满时,存活的对象会被复制到另一个 Survivor 区或老年代。老年代用于存放存活时间较长的对象。
- 垃圾回收算法有哪些?
- 标记 - 清除算法:先标记出需要回收的对象,然后统一清除这些对象。缺点是会产生大量内存碎片。
- 标记 - 整理算法:先标记出需要回收的对象,然后将存活的对象向一端移动,最后清除边界以外的内存。解决了内存碎片问题,但效率相对较低。
- 复制算法:将内存分为大小相等的两块,每次只使用其中一块。当这块内存用完时,将存活的对象复制到另一块内存中,然后清除当前使用的这块内存。优点是效率高,没有内存碎片,但会浪费一半的内存空间。
- 分代收集算法:根据对象的存活周期将内存划分为不同的区域(如新生代和老年代),针对不同区域采用不同的垃圾回收算法。新生代对象存活时间短,采用复制算法;老年代对象存活时间长,采用标记 - 清除或标记 - 整理算法。
- 线程的生命周期有哪些状态?
- 新建(New):线程对象被创建但尚未调用 start() 方法。
- 就绪(Runnable):线程调用 start() 方法后,进入就绪状态,等待获取 CPU 时间片。
- 运行(Running):线程获得 CPU 时间片,正在执行 run() 方法中的代码。
- 阻塞(Blocked):线程因为某些原因(如等待锁、等待输入输出等)暂时停止执行,让出 CPU 时间片。
- 死亡(Terminated):线程执行完 run() 方法中的代码或因异常退出,生命周期结束。
- Spring 框架的核心特性有哪些?
- 依赖注入(Dependency Injection,DI):指对象之间的依赖关系由容器来负责管理,而不是由对象自己创建。通过依赖注入,对象可以在运行时动态地获取它所依赖的对象,降低了对象之间的耦合度。
- 面向切面编程(Aspect - Oriented Programming,AOP):允许开发者在不修改原有代码的情况下,对程序的功能进行增强。AOP 通过将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高了代码的可维护性和复用性。
- Spring 的依赖注入有哪些方式?
- 构造器注入:通过构造方法来注入依赖对象。例如:
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
- 属性注入:通过 setter 方法来注入依赖对象。例如:
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
- 接口注入:这种方式使用较少,通过实现特定的接口来注入依赖对象。
11. Spring Boot 相比 Spring 有什么优势? - 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,通过引入这些依赖可以快速搭建项目,减少了手动配置的工作量。 - 自动配置:Spring Boot 会根据项目中引入的依赖自动进行配置,开发者只需要进行少量的配置或不配置即可运行项目。 - 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,不需要手动部署到外部服务器,方便开发和测试。 - 生产就绪:Spring Boot 提供了很多生产级别的特性,如监控、健康检查等,方便对应用进行管理和维护。 12. MyBatis 中 #{} 和 ${} 的区别是什么? - #{} 是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 占位符,然后使用 PreparedStatement 进行参数设置,这样可以防止 SQL 注入攻击。例如:
<select id="getUserById" parameterType="int" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
- ${} 是字符串替换,MyBatis 在处理 ${} 时,会直接将 ${} 替换为传入的参数值。例如:
<select id="getUserByTableName" parameterType="String" resultType="User">
SELECT * FROM ${tableName}
</select>
由于 ${} 是直接替换,可能会导致 SQL 注入问题,因此在使用时需要谨慎。
13. Dubbo 的服务注册与发现机制是怎样的? - Dubbo 的服务注册与发现机制主要依赖于注册中心。当服务提供者启动时,会将自己提供的服务信息(如服务接口名、服务地址等)注册到注册中心。注册中心可以是 ZooKeeper、Nacos、Redis 等。 - 服务消费者启动时,会从注册中心获取服务提供者的信息,并缓存到本地。当服务消费者需要调用服务时,会根据本地缓存的服务提供者信息进行远程调用。 - 当服务提供者的信息发生变化(如服务下线、服务地址变更等)时,注册中心会通知服务消费者更新本地缓存的信息。这样,服务消费者就可以动态地发现和调用服务提供者提供的服务。