Java实战面试题(三)
2026/6/5 13:41:10 网站建设 项目流程

目录

1. Java 四种引用类型区别与场景

2. HashMap 底层原理,JDK 1.7 和 1.8 差异

3. ConcurrentHashMap 实现原理

4. ArrayList 扩容机制与 Arrays.asList 坑点

5. Java8 Stream 常用操作与注意事项

6. ThreadLocal 原理、内存泄漏原因与解决

7. volatile 作用,能否保证原子性

8. CAS 原理、ABA 问题、Atomic 原子类

9. ThreadPoolExecutor 七大参数、拒绝策略

10. JVM 类加载过程、双亲委派及破坏场景

11. JVM 内存结构:堆、栈、元空间区别

12. 垃圾回收算法与 G1/ZGC 特点

13. Spring IoC 原理、Bean 生命周期

14. Spring AOP、JDK 与 CGLIB 动态代理区别

15. Spring 事务传播机制、隔离级别

16. 事务失效常见场景

17. SpringBoot Starter 自动配置原理


1. Java 四种引用类型区别与场景

引用类型回收时机主要场景
强引用永不回收(除非无引用可达)普通Object obj = new Object()
软引用内存不足时回收缓存(如图片缓存),配合SoftReference
弱引用下次 GC 时必回收WeakHashMap、ThreadLocal 中的 Key
虚引用任何时候都可能被回收,无法通过它获得对象跟踪对象回收状态,管理直接内存(如 NIO ByteBuffer)

补充:软/弱引用需配合ReferenceQueue使用,以便在对象被回收后清理相关资源。


2. HashMap 底层原理,JDK 1.7 和 1.8 差异

  • 底层结构

    • 1.7:数组 + 单向链表。

    • 1.8:数组 + 链表 +红黑树(链表长度 ≥8 且数组长度 ≥64 时转红黑树,节点数 ≤6 退化为链表)。

  • 哈希计算:1.8 扰动简化(高 16 位异或低 16 位一次),1.7 多次扰动。

  • 扩容机制

    • 1.7:扩容时头插法,多线程下可能形成环形链表导致 CPU 100%。

    • 1.8:尾插法,避免死链;通过hash & oldCap判断元素在新数组的位置,高位链移至原索引+旧容量处。

  • 线程安全:两者均非线程安全,并发下应使用ConcurrentHashMap


3. ConcurrentHashMap 实现原理

  • JDK 1.7分段锁(Segment),默认 16 个 Segment,每个 Segment 内是小型 HashMap,独立加锁ReentrantLock,并发度 16。

  • JDK 1.8CAS + synchronized,锁粒度细化到桶的首节点

    • put时,若桶为空,用 CAS 尝试直接插入;若桶非空,对首节点加synchronized锁,再进行插入/更新。

    • 扩容支持多线程协同,每个线程负责迁移一部分桶。

    • 同样采用红黑树优化链表过长问题。

  • 与 Hashtable 区别:Hashtable 锁整个表,并发度极低;ConcurrentHashMap 锁粒度细,不允许 null 键/值。


4. ArrayList 扩容机制与 Arrays.asList 坑点

  • 扩容机制

    • 默认初始容量 10(或指定)。

    • 添加元素时若容量不足,会调用grow,新容量 =oldCapacity + (oldCapacity >> 1)(即 1.5 倍),然后Arrays.copyOf复制到新数组。

  • Arrays.asList 坑点

    • 返回的是Arrays内部类ArrayList不是java.util.ArrayList),是固定大小的列表,不支持add/remove,否则抛UnsupportedOperationException

    • 数组修改会影响列表内容(因为底层共用同一个数组)。

    • 建议:需要可变列表时用new ArrayList<>(Arrays.asList(...))包装。


5. Java8 Stream 常用操作与注意事项

  • 常用操作

    • 中间操作(惰性)filtermapflatMapdistinctsortedlimitskip

    • 终端操作(触发计算)collectforEachreducecountanyMatchfindFirst

  • 注意事项

    • Stream不存储数据,不修改数据源,只能消费一次(用完就关闭,再使用抛异常)。

    • parallelStream并行流使用共享的ForkJoinPool,注意线程安全问题,尽量避免在并行流中修改外部非线程安全集合。

    • forEach不保证顺序(并行时),如需顺序输出用forEachOrdered

    • collect归约常用Collectors.toList()groupingBypartitioningBy


6. ThreadLocal 原理、内存泄漏原因与解决

  • 原理:每个Thread内部维护一个ThreadLocalMap,Key 是ThreadLocal的弱引用,Value 是实际存放的值。get()时通过当前线程获取 Map 取值。

  • 内存泄漏原因

    • ThreadLocal 被回收后,Key 变成了 null,但Value 依然被 Entry 强引用,且 Entry 被 Thread 的 Map 引用。若线程不结束(如线程池中的线程),这些 Value 永远无法被 GC,造成内存泄漏。

  • 解决方法

    • 使用完必须调用remove()方法,显式清除 Entry。

    • ThreadLocalMap 在get/set时会启发式清理部分 key 为 null 的 entry,但不能完全依赖。


7. volatile 作用,能否保证原子性

  • 两大作用

    1. 可见性:一个线程修改 volatile 变量后,会立即刷新到主内存,并通知其他线程缓存行失效,保证读操作获取最新值。

    2. 禁止指令重排:通过内存屏障(LoadStore、StoreStore 等)防止 volatile 变量与其他操作的顺序被重排。

  • 能否保证原子性不能。例如count++是“读-改-写”复合操作,volatile 不能保证这三步不被中断。需要原子性应使用AtomicIntegersynchronized


8. CAS 原理、ABA 问题、Atomic 原子类

  • CAS 原理:Compare And Swap。比较内存当前值 V 与期望值 A,如果相等则更新为 B,否则不更新。是 CPU 级原子指令(cmpxchg)。

  • ABA 问题:变量从 A 改成 B,又改回 A,CAS 无法感知,以为没变。解决:使用版本号/时间戳,如AtomicStampedReferenceAtomicMarkableReference

  • Atomic 原子类AtomicIntegerAtomicLongAtomicReference等,底层用 CAS + 循环重试(自旋)保证原子操作,无锁,适合并发竞争不激烈的场景。

  • LongAdder:高并发下比AtomicLong性能更好,采用分段累加思想,最后求和。


9. ThreadPoolExecutor 七大参数、拒绝策略

  • 七大参数

    1. corePoolSize:核心线程数

    2. maximumPoolSize:最大线程数

    3. keepAliveTime:非核心线程空闲存活时间

    4. unit:时间单位

    5. workQueue:工作队列(如LinkedBlockingQueueArrayBlockingQueue

    6. threadFactory:线程工厂(命名)

    7. handler:拒绝策略

  • 拒绝策略(4 种)

    • AbortPolicy(默认):抛RejectedExecutionException异常

    • CallerRunsPolicy:由提交任务的线程自己执行,产生反压

    • DiscardPolicy:静默丢弃任务

    • DiscardOldestPolicy:丢弃队列中最旧的任务,重新提交当前任务


10. JVM 类加载过程、双亲委派及破坏场景

  • 加载过程(5 步)

    1. 加载:通过类全限定名获取二进制字节流,转为方法区运行时数据结构,生成Class对象。

    2. 验证:文件格式、元数据、字节码、符号引用验证。

    3. 准备:为静态变量分配内存并赋零值(final static修饰的常量直接赋定义值)。

    4. 解析:将常量池内的符号引用替换为直接引用。

    5. 初始化:执行<clinit>方法,初始化静态变量和静态代码块。

  • 双亲委派模型:类加载器收到加载请求时,先委托父加载器尝试加载,只有父加载器无法加载时,子加载器才自己加载。目的是保证核心库的类型安全(如java.lang.String)。

  • 破坏场景

    • JDBC 引入线程上下文类加载器(Thread.currentThread().getContextClassLoader())打破,让父加载器可以请求子加载器加载类(SPI 机制)。

    • Tomcat 类加载器打破,实现 Web 应用的隔离与热部署。

    • OSGi 模块化热替换,网状委派结构。


11. JVM 内存结构:堆、栈、元空间区别

  • :存放对象实例和数组,线程共享,GC 主战场。细分新生代(Eden + Survivor)、老年代。

  • 虚拟机栈:线程私有,每个方法对应一个栈帧,存储局部变量表(基本类型、对象引用)、操作数栈、动态链接、返回地址等。会出现StackOverflowError

  • 元空间(JDK8+):取代永久代,使用本地内存,存放类元信息、运行时常量池、静态变量、JIT 代码缓存等,线程共享。避免永久代 OOM,但受本地内存大小限制。


12. 垃圾回收算法与 G1/ZGC 特点

  • 常用算法

    • 标记-清除:标记存活,清除未标记。产生碎片。

    • 标记-复制:分两块区域,将存活对象复制到另一块,清空原区。用于新生代,无碎片,浪费空间。

    • 标记-整理:标记后,存活对象向一端移动,清理边界外内存。用于老年代,无碎片,耗时长。

  • G1 特点

    • 将堆划分为多个 Region,兼顾吞吐量与低延迟(可设置暂停时间目标)。

    • 采用并发标记、混合回收,优先回收价值最高的 Region(Garbage First)。

    • 使用SATB(Snapshot-At-The-Beginning)标记算法,保证并发阶段一致性。

  • ZGC 特点

    • 基于染色指针(Colored Pointers)读屏障,实现并发标记-整理,暂停时间 <1ms,且停顿时间不随堆大小增加,支持 TB 级堆。


13. Spring IoC 原理、Bean 生命周期

  • IoC 原理:控制反转,通过DI(依赖注入)实现。Spring 容器(ApplicationContext)在初始化时创建并管理 Bean,通过反射、工厂等完成依赖组装。配置方式包括 XML、注解、Java Config。

  • Bean 生命周期(简化版):

    1. 实例化:反射创建 Bean 实例。

    2. 属性填充populateBean):注入属性值和依赖。

    3. 初始化:若实现*Aware接口则回调(如BeanNameAware);@PostConstruct方法;InitializingBean#afterPropertiesSet;自定义init-method

    4. 使用:Bean 就绪。

    5. 销毁@PreDestroyDisposableBean#destroydestroy-method


14. Spring AOP、JDK 与 CGLIB 动态代理区别

  • AOP 原理:基于动态代理,在目标方法执行前后插入增强(Advice)。

  • JDK 动态代理

    • 基于接口反射生成代理类,要求被代理对象必须实现接口。

    • 通过Proxy.newProxyInstanceInvocationHandler实现。

  • CGLIB 代理

    • 通过修改字节码生成目标类的子类,适用于无接口的类。

    • 不能代理final类或final方法。

  • Spring 选择策略

    • 默认:目标实现了接口用 JDK 代理,否则用 CGLIB。

    • 可通过proxy-target-class="true"强制使用 CGLIB。

    • Spring Boot 2.x 默认使用 CGLIB(即使有接口)。


15. Spring 事务传播机制、隔离级别

  • 传播机制(7 种)

    • REQUIRED(默认):有则加入,无则新建。

    • SUPPORTS:有则加入,无则非事务运行。

    • MANDATORY:有则加入,无则抛异常。

    • REQUIRES_NEW:永远新建事务,挂起当前。

    • NOT_SUPPORTED:非事务运行,挂起当前事务。

    • NEVER:非事务运行,有事务则抛异常。

    • NESTED:嵌套事务,保存点回滚。

  • 隔离级别DEFAULT(使用数据库默认)、READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE


16. 事务失效常见场景

  1. 未开启事务管理:如 Spring Boot 没加@EnableTransactionManagement

  2. 方法非 public:AOP 无法代理私有方法。

  3. 自调用:同类中方法内部调用this.method()不走代理,事务失效。解决:注入自身(@Autowired自己),或抽到另一个类。

  4. 异常被吞try-catch未抛出运行时异常(默认仅回滚RuntimeExceptionError)。

  5. 回滚异常类型不匹配:抛出受检异常未指定@Transactional(rollbackFor = Exception.class)

  6. 数据库引擎不支持事务:如 MyISAM。

  7. 多线程@Transactional不能跨线程传播。

  8. 传播行为设置错误:如NOT_SUPPORTEDNEVER


17. SpringBoot Starter 自动配置原理

  • 入口@SpringBootApplication内含@EnableAutoConfiguration

  • 过程

    1. @EnableAutoConfiguration通过@Import加载AutoConfigurationImportSelector

    2. 选择器利用SpringFactoriesLoader读取所有 jar 包下的META-INF/spring.factories,获取 key 为EnableAutoConfiguration的配置类列表。

    3. 对每个配置类,根据@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty等条件注解决定是否加载该 Bean。

    4. 满足条件的配置类自动创建 Bean 并注册到容器(如DataSourceAutoConfiguration根据 classpath 和配置自动创建数据源)。

  • 自定义 Starter:编写一个AutoConfiguration类,并在spring.factories中注册,封装默认 Bean 和属性绑定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询