浩鲸科技后端一面:类加载、SpringBoot 装配与 JVM 内存结构串讲
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 介绍一下你的实习经历。
- 讲一下简历项目的整体架构和技术栈。
- Java 类加载机制是怎样的?
- 反射机制了解吗?
- SpringBoot 的装配机制是怎样的?
- 线程池的核心参数与工作流程?
- JVM 的内存结构是怎样的?
- ThreadLocal 的原理?有什么问题?
- 泛型了解吗?泛型擦除是什么?
- 垃圾回收的机制与常见算法?
- String 类相关的问题。
- 你 Python 熟练吗?
《参考解析》
类加载与反射:加载 → 验证 → 准备 → 解析 → 初始化五步,加载由类加载器完成,双亲委派让核心类只被信任的加载器加载(避免自定义 java.lang.String 顶替)。打破双亲委派的场景要能举例:SPI 用线程上下文类加载器、Tomcat 每个 webapp 一个 WebappClassLoader 做隔离。反射是运行期拿 Class 对象构造实例、读字段、调方法,代价在方法查找与访问检查,高频路径要缓存 Method 并 setAccessible(true);Spring 的依赖注入、MyBatis 的映射、动态代理都靠它。
SpringBoot 自动装配:@SpringBootApplication 里的 @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 读 META-INF/spring/...AutoConfiguration.imports(2.7 之前是 spring.factories),拿到候选配置类后由 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等条件注解筛选,满足条件才注册成 Bean。所以「为什么引入某个 starter 就有对应 Bean」的答案是:类路径上出现了触发条件,且用户没自己定义同类型 Bean。
JVM 内存与 GC:线程私有的是程序计数器、虚拟机栈、本地方法栈,共享的是堆与方法区(元空间)。堆分新生代(Eden + 两个 Survivor)与老年代,对象优先在 Eden 分配,Minor GC 后存活对象在 Survivor 间复制、年龄到阈值晋升老年代;大对象直接进老年代。回收算法是标记-清除(碎片)、复制(新生代)、标记-整理(老年代),具体收集器有 G1、ZGC 等。判断对象可回收用可达性分析,GC Roots 包括栈上引用、静态变量、常量、JNI 引用。要能讲一个实际调优案例:Full GC 频繁是晋升过快还是内存泄漏,靠 jstat、堆 dump 与 MAT 看谁在涨。
ThreadLocal:每个 Thread 持有一个 ThreadLocalMap,key 是 ThreadLocal 的弱引用,value 是强引用。key 被回收后 value 还在,线程池里线程长期存活就泄漏,所以必须 try/finally 里 remove()。父子线程传值用 InheritableThreadLocal,但线程池复用线程时它只在创建时复制一次,异步链路要用 TransmittableThreadLocal 或显式透传。
泛型擦除:泛型只存在于编译期,编译后变成原始类型并插入强制转换,所以不能 new T[]、不能对泛型做 instanceof、静态成员不能用类型参数;List<String> 与 List<Integer> 的 getClass() 相同。需要运行期类型时传 Class<T> 或 TypeReference。
String 与线程池:String 不可变,字符串常量池在堆里(1.7 起从永久代挪出),intern() 把运行时字符串放进池并返回池中引用;+ 在循环里会反复建对象,用 StringBuilder。线程池先填核心线程、再入队列、队列满开非核心、到最大线程数且队列满才走拒绝策略,allowCoreThreadTimeOut 才能回收核心线程,队列选错(无界队列)会让最大线程数形同虚设。
面试里的岗位信息:JD 写 Java 方向,但内部 AI 相关工作由 Python 开发,入职可能要转。被问 Python 熟练度时照实回答即可,同时说明可迁移的部分,比硬撑「很熟」安全。