5道Java基础面试题带你从入门到精通,面试不再哑火
面试被问原理答不上来,这种尴尬谁懂?很多开发者背了无数八股文,结果面试官换个问法就卡壳。今天不聊虚的,直接拆解Java基础中最高频的5个痛点,带你从入门到精通,把原理吃透,面试稳拿offer。
一、概念速懂:为什么String是不可变的?
很多新手觉得String immutable(不可变)是个设计缺陷,其实这是Java为性能和线程安全做的权衡。
核心逻辑:字符串常量池优化:JVM在编译阶段会将字面量放入字符串常量池。如果String可变,a = hello; b = a; b += world; 会导致a也变成helloworld,破坏引用语义。
线程安全:不可变对象天然线程安全,无需同步。
hashCode缓存:String的hashCode被缓存,因为内容不变,计算一次即可,提升HashMap性能。代码演示:
public class StringImmutableDemo {public static void main(String[] args) {String s1 = Hello;String s2 = Hello;String s3 = new String(Hello);// s1和s2指向常量池同一对象System.out.println(s1 == s2); // true// s3是新对象,地址不同System.out.println(s1 == s3); // false// 内容相同,equals为trueSystem.out.println(s1.equals(s3)); // true// 尝试修改,实际创建了新对象s1 = s1 + World;System.out.println(s1); // Hello World}
}面试加分点: 提到String常量池在JDK1.7后从永久代移到堆中,说明你关注JVM变化。
二、环境准备:JDK版本差异的坑
很多面试官喜欢问JDK版本差异,尤其是JDK8之后。
必知差异:JDK8:Lambda表达式、Stream API、默认方法
JDK11:局部变量类型推断(var)、HTTP Client
JDK17:Sealed类、Records、强封装代码演示:
import java.util.List;
import java.util.stream.Collectors;public class JdkVersionDemo {public static void main(String[] args) {// JDK8 Stream APIListString names = List.of(Alice, Bob, Charlie);ListString upperNames = names.stream().map(String::toUpperCase).collect(Collectors.toList());System.out.println(upperNames);// JDK11 局部变量类型推断var list = List.of(1, 2, 3);var map = List.of(a, b);System.out.println(list);System.out.println(map);// JDK17 Records(简化POJO)record Person(String name, int age) {}var p = new Person(Alice, 25);System.out.println(p.name());System.out.println(p.age());}
}避坑提示: 企业项目常用JDK8或11,面试前确认目标公司技术栈,避免用错版本特性。
三、核心语法:多线程与并发陷阱
并发是Java面试重灾区,尤其是ThreadLocal和线程池。
核心概念:ThreadLocal:线程隔离数据,避免共享变量竞争
线程池:复用线程,避免频繁创建销毁
volatile:保证可见性,不保证原子性代码演示:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;public class ConcurrencyDemo {private static final ThreadLocalSimpleDateFormat threadLocal = ThreadLocal.withInitial(() - new SimpleDateFormat(yyyy-MM-dd));public static void main(String[] args) {// 线程池示例ExecutorService executor = Executors.newFixedThreadPool(3);AtomicInteger counter = new AtomicInteger(0);for (int i = 0; i 10; i++) {executor.submit(() - {// 使用ThreadLocal获取线程私有对象SimpleDateFormat sdf = threadLocal.get();try {Thread.sleep(100);counter.incrementAndGet();System.out.println(Thread.currentThread().getName() + 处理: + sdf.format(new java.util.Date()));} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executor.shutdown();try {executor.awaitTermination(5, TimeUnit.SECONDS);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println(总处理数: + counter.get());}
}面试高频问题:ThreadLocal内存泄漏如何避免?→ 手动remove()
线程池参数如何设置?→ CPU密集型:N+1;IO密集型:2N
volatile能否替代synchronized?→ 不能,volatile不保证原子性四、完整代码示例:JVM内存模型实战
JVM内存模型是基础中的基础,理解堆、栈、方法区的关系至关重要。
核心区域:堆:对象实例,GC主要区域
栈:局部变量、方法调用栈
方法区:类元数据、常量池
程序计数器:字节码行号,线程私有代码演示:
import java.util.ArrayList;
import java.util.List;public class JvmMemoryDemo {// 静态变量,存储在方法区(JDK8后元空间)private static ListString staticList = new ArrayList();public static void main(String[] args) {// 局部变量,存储在栈帧int localVar = 100;String str = Hello JVM;// 对象实例,存储在堆JvmMemoryDemo obj = new JvmMemoryDemo();obj.instanceList.add(instance);// 静态变量,存储在方法区staticList.add(static);// 触发GC观察内存变化for (int i = 0; i 1000000; i++) {new Object(); // 快速创建对象,触发GC}System.gc(); // 建议GC(不保证立即执行)System.out.println(堆内存使用: + Runtime.getRuntime().totalMemory() / 1024 / 1024 + MB);System.out.println(可用内存: + Runtime.getRuntime().freeMemory() / 1024 / 1024 + MB);}// 实例变量,存储在对象头+实例数据中private ListString instanceList = new ArrayList();
}面试技巧: 结合JVisualVM或Arthas工具分析内存泄漏,展示实战能力。
五、常见报错:OOM与ClassNotFound
生产环境最常见的两个问题,面试必问。
OOM(OutOfMemoryError):堆溢出:大量对象无法回收排查:jmap -dump + MAT分析
解决:增加堆内存、修复内存泄漏元空间溢出:类加载过多排查:检查动态代理、反射
解决:增加Metaspace大小ClassNotFound/NoClassDefFound:依赖缺失:jar包未引入
类加载冲突:多个jar包含相同类
初始化失败:静态块抛出异常代码演示:
import java.lang.reflect.*;public class ErrorDemo {public static void main(String[] args) {// 模拟ClassNotFoundtry {Class? clazz = Class.forName(com.example.MissingClass);} catch (ClassNotFoundException e) {System.out.println(类未找到: + e.getMessage());}// 模拟NoClassDefFoundError(静态初始化失败)try {Class? clazz = Class.forName(com.example.BrokenInit);clazz.newInstance();} catch (Exception e) {System.out.println(初始化失败: + e.getMessage());}// 模拟OOMtry {Listbyte[] list = new ArrayList();while (true) {list.add(new byte[1024 * 1024]); // 1MB}} catch (OutOfMemoryError e) {System.out.println(堆溢出: + e.getMessage());}}
}// 模拟静态初始化失败
class BrokenInit {static {throw new RuntimeException(静态块异常);}
}避坑建议: 使用Maven/Gradle严格管理依赖,CI/CD中集成内存监控。
六、小结:从入门到精通的路径
Java基础面试不是背八股,而是理解设计哲学。
学习建议:源码阅读:从String、ArrayList、HashMap入手
JVM调优:学习GC日志分析,理解不同收集器适用场景
并发编程:掌握JMM、线程池、锁机制
实战项目:结合GitHub开源仓库(如Spring Boot、Netty)深入理解关键资源:GitHub仓库:awesome-java 汇总优质Java项目
官方文档:OpenJDK规范、Java SE API文档
工具:JVisualVM、Arthas、JConsole面试被问原理答不上来,本质是只知其然不知其所以然。把每个知识点背后的为什么想清楚,从入门到精通只是时间问题。
这个知识点你面试被问过吗?留言说说
