深圳硅基仿生科技 Java开发 二面
#JAVA##JAVA面经##JAVA内推#
1. 反射机制在 JVM 层面是如何动态获取类元信息的?
思路 核心是JVM的类加载流程+运行时数据区的Class对象:类加载完成后,JVM会在方法区生成该类的元信息(字段、方法、构造器等),并创建Class对象作为元信息的访问入口;反射通过Class对象调用native方法(底层C++实现)直接读取方法区的类元信息,无需编译期绑定。
回答示例
JVM层面,反射获取类元信息依赖「类加载+运行时数据区」:
- 类加载阶段:JVM通过类加载器(ClassLoader)将.class文件加载到内存,解析生成类元信息(包含字段、方法、构造器的描述信息),存储在方法区(JDK8后是元空间);
- 运行时访问:类加载完成后,JVM会创建一个对应类的Class对象(唯一),作为访问方法区元信息的“入口”;
- 反射实现:Class类的核心方法(如getDeclaredField、getMethod)底层调用JVM的native方法(如JVM_GetFieldFromName),直接从方法区读取类的元信息,实现运行时动态获取,而非编译期静态绑定。
2. 反射调用为什么会比直接调用性能更低?
思路 从编译期优化、访问检查、方法调用方式三个核心维度分析:直接调用有编译优化,反射需运行时解析+权限检查+动态方法分派,无JIT优化。
回答示例
反射调用性能低的核心原因是「运行时动态解析+无编译优化」,具体有三点:
- 编译期优化缺失:直接调用的方法在编译期就确定方法签名,JVM可做内联、常量折叠等优化;反射调用需运行时解析方法名/字段名,无法提前优化,且JIT编译器对反射调用的优化支持有限;
- 访问检查开销:反射需在运行时检查字段/方法的访问权限(如private),即使设置setAccessible(true),也会额外消耗性能;直接调用的权限检查在编译期完成;
- 方法调用方式不同:直接调用通过「方法表」快速定位方法执行入口;反射调用需通过Method.invoke()间接调用,涉及参数拆箱/装箱、类型校验,且invoke方法是native方法,跨JNI调用有额外开销。
3. 项目中自定义全局异常处理器的核心原理是什么?
思路 基于Spring的AOP思想+异常拦截机制:通过@ControllerAdvice/@RestControllerAdvice注解扫描所有Controller,结合@ExceptionHandler注解匹配指定异常类型,实现异常的统一捕获和处理。
回答示例
全局异常处理器的核心是Spring的「异常拦截+AOP思想」,原理如下:
- 注解扫描:@RestControllerAdvice(组合了@Component和@ControllerAdvice)会被Spring扫描并注册为Bean,默认拦截所有Controller层的请求;
- 异常匹配:在处理器类中定义@ExceptionHandler注解的方法,注解中指定要处理的异常类型(如Exception、自定义BusinessException);
- 异常拦截与处理:当Controller层抛出异常时,Spring的DispatcherServlet会将异常转发给全局异常处理器,匹配对应的@ExceptionHandler方法,执行自定义逻辑(如封装返回结果、记录日志),替代默认的异常处理流程;
- 核心本质:相当于在所有Controller外层套了一层“异常拦截切面”,统一接管异常,避免每个接口重复写try-catch。
4. ArrayList 在高并发场景下为什么会出现线程安全问题?
思路 核心是底层数组的修改操作非原子性+快速失败机制:add/remove等操作分多步执行,并发修改会导致数组越界、数据覆盖、迭代器异常。
回答示例
ArrayList 是线程不安全的,高并发下的问题根源在于「修改操作非原子性+无锁保护」:
- add 方法非原子性:add操作分三步(检查容量→扩容→赋值elementData[size++] = e),并发执行时:
- 多个线程同时扩容,可能导致数组元素覆盖;
- size++ 是非原子操作(读取-自增-赋值),可能导致size计数错误,甚至数组下标越界(ArrayIndexOutOfBoundsException);
- 迭代器快速失败:并发修改ArrayList时,迭代器(Iterator)会触发fail-fast机制,抛出ConcurrentModificationException;
- 无锁保护:ArrayList 未对修改操作加锁,无法保证多线程下的操作原子性和可见性,最终导致数据不一致。
5. 单例模式双重校验锁中 volatile 禁止指令重排的具体作用是什么?
思路 聚焦对象实例化的三步操作(分配内存→初始化→指向引用):volatile 禁止“初始化”和“指向引用”的指令重排,避免线程获取到未初始化的半成对象。
回答示例
双重校验锁(DCL)中volatile的核心作用是禁止对象实例化过程中的指令重排,避免获取到“半初始化”的单例对象,具体分析:
- 对象实例化的三步指令:
1. 分配内存空间(new); 2. 初始化对象(调用构造器); 3. 将对象引用指向内存地址(instance = 内存地址); - 指令重排的风险:JVM为优化性能,可能将步骤2和3重排(1→3→2),此时线程A执行完步骤3(instance非null),但对象未初始化;
- volatile 的作用:添加volatile后,会禁止上述指令重排,保证步骤2一定在步骤3前执行;这样线程B在判断instance != null时,拿到的一定是完全初始化的对象,避免空指针或数据错误。
6. MySQL InnoDB 分页深度优化时,如何避免大量无效 IO?
思路 核心是避免OFFSET的全表扫描:用“主键/索引有序遍历”替代OFFSET,减少磁盘IO;结合覆盖索引、子查询优化。
回答示例
深度分页(如LIMIT 100000, 10)的核心问题是OFFSET会扫描前100000行数据(无效IO),优化思路是「基于索引有序遍历,跳过无效数据」:
- 主键索引优化(适用于主键自增):
原理:通过id > 100000直接定位到起始位置,无需扫描前100000行,仅扫描10行,大幅减少IO;-- 替代 LIMIT 100000, 10 SELECT * FROM table WHERE id > 100000 L
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏


查看9道真题和解析