互联网大厂面试:Java核心知识、框架与中间件大考验
在互联网大厂的一间安静的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的水货程序员王铁牛。面试正式开始。
第一轮提问 面试官:首先问几个基础的 Java 核心知识问题。Java 中基本数据类型有哪些? 王铁牛:嗯,有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那 String 类为什么是不可变的? 王铁牛:因为 String 类被 final 修饰,它的成员变量也是 final 的,所以不可变。 面试官:很好。那再说说多线程中 synchronized 和 ReentrantLock 的区别。 王铁牛:synchronized 是 Java 的关键字,是内置的锁机制,而 ReentrantLock 是一个类。synchronized 会自动释放锁,ReentrantLock 需要手动释放。
第二轮提问 面试官:看来基础还可以。那聊聊 JVM 相关的。JVM 的内存区域是如何划分的? 王铁牛:有方法区、堆、虚拟机栈、本地方法栈和程序计数器。 面试官:不错。那垃圾回收算法有哪些? 王铁牛:有标记 - 清除算法、标记 - 整理算法、复制算法。 面试官:接着问,线程池的核心参数有哪些? 王铁牛:有核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列和拒绝策略。
第三轮提问 面试官:现在问些框架和中间件的问题。Spring 的核心特性有哪些? 王铁牛:有依赖注入和面向切面编程。 面试官:那 Spring Boot 的自动配置原理是什么? 王铁牛:嗯……就是根据 classpath 里的依赖和配置文件,自动配置 Spring 应用。 面试官:MyBatis 的一级缓存和二级缓存是怎么回事? 王铁牛:一级缓存是会话级别的,二级缓存是全局的。大概就是这样吧。
面试官总结:王铁牛,通过这三轮的面试,在一些基础的 Java 核心知识、JVM 以及简单的框架概念方面,你表现出了一定的知识储备。对于像基本数据类型、String 类特性、JVM 内存区域划分、Spring 核心特性等简单问题,你都能较为准确地回答,这说明你有一定的学习和积累。然而,当遇到一些稍微复杂的问题,比如 Spring Boot 自动配置原理和 MyBatis 缓存机制的深入理解时,你的回答就显得比较模糊和浅显,没有深入展开阐述其背后的技术细节和工作流程。在多线程和线程池的一些应用场景方面,虽然你能说出基本概念,但对于其在实际业务中的应用和可能遇到的问题,没有展现出足够的理解。在后续的工作中,如果你想在 Java 开发领域有更深入的发展,需要进一步深入学习这些技术的底层原理和实际应用场景。回去之后你可以再对这些知识点进行系统的学习和总结。目前面试就到这里,你回家等通知吧。
问题答案
- Java 中基本数据类型有哪些?
- Java 中有 8 种基本数据类型,分为 4 类:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节),用于存储单个字符。
- 布尔类型:boolean,只有两个值 true 和 false。
- Java 中有 8 种基本数据类型,分为 4 类:
- String 类为什么是不可变的?
- String 类被 final 修饰,这意味着它不能被继承。其内部维护了一个 private final char[] value 数组来存储字符串内容。由于 value 数组被 final 修饰,一旦被初始化,其引用不能再指向其他数组。并且 String 类没有提供修改这个数组内容的公共方法,所以 String 对象一旦创建,其内容就不能被改变。不可变的好处包括:安全性(在多线程环境中可以安全使用)、可以作为 HashMap 的键(保证哈希码的一致性)、字符串常量池的实现(多个相同字符串可以共享同一个对象)等。
- 多线程中 synchronized 和 ReentrantLock 的区别
- 实现层面:synchronized 是 Java 的关键字,是 Java 语言内置的同步机制;ReentrantLock 是 java.util.concurrent.locks 包下的一个类。
- 锁的获取和释放:synchronized 会自动获取和释放锁,当代码块或方法执行完毕,锁会自动释放;ReentrantLock 需要手动调用 lock() 方法获取锁,unlock() 方法释放锁,通常在 finally 块中释放锁以确保锁一定会被释放。
- 锁的特性:ReentrantLock 提供了更多的高级特性,如可中断锁(lockInterruptibly() 方法)、公平锁(构造函数中可以指定是否为公平锁)等,而 synchronized 不具备这些特性。
- 性能:在 JDK 1.6 之前,synchronized 的性能较差,JDK 1.6 及以后对 synchronized 进行了大量优化,在大多数情况下两者性能差异不大,但在高并发场景下,ReentrantLock 的性能可能会更好。
- JVM 的内存区域是如何划分的?
- 程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,线程私有。
- 虚拟机栈:也是线程私有的,它的生命周期与线程相同。每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 本地方法栈:与虚拟机栈类似,只不过它是为虚拟机使用到的 Native 方法服务。
- 堆:是 Java 虚拟机所管理的内存中最大的一块,被所有线程共享。所有的对象实例和数组都在堆上分配内存。堆是垃圾回收的主要区域。
- 方法区:被所有线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在 JDK 1.8 之前,方法区也被称为永久代,JDK 1.8 及以后使用元空间代替了永久代。
- 垃圾回收算法有哪些?
- 标记 - 清除算法:首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。缺点是会产生大量的内存碎片,可能导致后续大对象无法分配到连续的内存空间。
- 标记 - 整理算法:先标记出需要回收的对象,然后将存活的对象向一端移动,最后直接清理掉端边界以外的内存。避免了内存碎片的问题,但移动对象的开销较大。
- 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。优点是实现简单,运行高效,缺点是可用内存缩小为原来的一半。现在的商业虚拟机都采用这种算法来回收新生代。
- 线程池的核心参数有哪些?
- corePoolSize:核心线程数,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数量。
- keepAliveTime:空闲线程存活时间,当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间就会被销毁。
- unit:时间单位,用于指定 keepAliveTime 的时间单位,如 TimeUnit.SECONDS 等。
- workQueue:任务队列,用于存储提交但尚未被执行的任务。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue 等。
- threadFactory:线程工厂,用于创建线程,通过自定义线程工厂可以对线程进行一些定制化操作,如设置线程名等。
- handler:拒绝策略,当任务队列已满且线程池中的线程数量达到最大线程数时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程处理该任务)、DiscardPolicy(直接丢弃该任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
- Spring 的核心特性有哪些?
- 依赖注入(DI):是一种设计模式,通过将对象的依赖关系从对象本身的创建中分离出来,由外部容器负责将依赖对象注入到需要的对象中。这样可以降低代码的耦合度,提高代码的可维护性和可测试性。例如,在 Spring 中可以通过构造函数注入、Setter 方法注入等方式实现依赖注入。
- 面向切面编程(AOP):是一种编程范式,它允许开发者在不修改原有业务逻辑的基础上,对程序进行增强。AOP 的主要应用场景包括日志记录、事务管理、权限验证等。在 Spring 中,AOP 主要通过代理模式实现,有 JDK 动态代理和 CGLIB 代理两种方式。
- Spring Boot 的自动配置原理是什么?
- Spring Boot 的自动配置是基于 Spring 的条件注解和类路径扫描实现的。当 Spring Boot 应用启动时,会加载 META - INF/spring.factories 文件,该文件中定义了一系列的自动配置类。然后根据类路径下的依赖和配置文件中的属性,使用条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来判断是否需要对某个组件进行自动配置。如果满足条件,就会将相应的 Bean 定义注册到 Spring 容器中,从而实现自动配置。例如,如果类路径下存在 MyBatis 的相关依赖,Spring Boot 会自动配置 MyBatis 的相关组件。
- MyBatis 的一级缓存和二级缓存是怎么回事?
- 一级缓存:是会话级别的缓存,每个 SqlSession 都有一个独立的一级缓存。当同一个 SqlSession 执行相同的 SQL 查询时,第一次查询会将结果存储在一级缓存中,后续相同的查询会直接从缓存中获取结果,而不需要再次访问数据库。一级缓存的生命周期与 SqlSession 相同,当 SqlSession 关闭或清空缓存时,一级缓存中的数据会被清除。
- 二级缓存:是全局级别的缓存,多个 SqlSession 可以共享同一个二级缓存。需要在 MyBatis 的配置文件中开启二级缓存,并在 mapper.xml 文件中配置使用二级缓存。二级缓存的作用域更大,当一个 SqlSession 执行查询并将结果存储在二级缓存中后,其他 SqlSession 执行相同的查询时也可以从二级缓存中获取结果。二级缓存的生命周期更长,直到手动清空或满足一定的刷新条件才会失效。