跳到主要内容
Java 核心面试题汇总:JVM 并发与数据库 | 极客日志
Java java 算法
Java 核心面试题汇总:JVM 并发与数据库 Java 面试题涵盖类加载机制、垃圾回收算法、JVM 调优参数、并发锁机制、Redis 持久化、数据库事务隔离级别及范式、集合框架原理、线程池、Spring 特性及 AOP、MyBatis 占位符差异、排序算法等核心知识点。内容涉及 JVM 内存模型、GC 策略如标记清除复制整理分代收集,以及 ConcurrentHashMap 分段锁、G1 收集器原理。同时包含 Redis 与 Memcached 对比、MySQL 锁机制、ACID 特性、HashSet/HashMap 去重扩容原理、四种线程池类型、volatile 与 synchronized 区别、Spring IoC/AOP 优势、MyBatis #与$区别及快速排序实现。适合准备 Java 后端开发面试的技术人员参考复习。
NodeJser 发布于 2025/1/16 更新于 2026/4/25 3 浏览类的加载机制是什么?有哪些实现方式?
类加载机制
类的加载指的是将类的.class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装在方法区内的数据结构。类的加载最终是在堆区内的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且向 Java 提供了访问方法区内的数据结构的接口。
类加载三种方式
命令行启动应用时候由 JVM 初始化加载
通过 Class.forName 方法动态加载
通过 ClassLoader.loadClass 方法动态加载
JVM 的常见垃圾回收算法?
标记 - 清除算法 :前后线标记处所有需要回收的对象,在标记完成后统一回收有被标记的对象。
复制算法 :将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,将其存放到另外一块上面,然后再把已使用过的内存空间一次清理掉。
标记 - 整理算法 :标记过程与'标记 - 清除'算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有一端移动,然后直接清理掉一端边界以外的内存。
分代收集算法 :一般是把 Java 堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。新生代发现有大批对象死去,选用复制算法。老年代中因为对象存活率高,必须使用'标记 - 清除'或'标记 - 整理'算法来进行回收。
JVM 调优的常见命令行工具有哪些?JVM 常见的调优参数有哪些?
(1)JVM 调优的常见命令工具包括
jps 命令用于查询正在运行的 JVM 进程
jstat 可以实时显示本地或远程 JVM 进程中类装载、内存、垃圾收集、JIT 编译等数据
jinfo 用于查询当前运行的 JVM 属性和参数的值
jmap 用于显示当前 Java 堆和永久代的详细信息
jhat 用于分析使用 jmap 生成的 dump 文件,是 JDK 自带的工具
jstack 用于生成当前 JVM 的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因
(2)JVM 常见的调优参数包括
-Xmx:指定 java 程序的最大堆内存,使用 java -Xmx5000M -version 判断当前系统能分配的最大堆内存
-Xms:指定最小堆内存,通常设置成跟最大堆内存一样,减少 GC
-Xmn:设置年轻代大小。整个堆大小 = 年轻代大小 + 年老代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8
-Xss:指定线程的最大栈空间,此参数决定了 java 函数调用的深度,值越大调用深度越深,若值太小则容易出栈溢出错误 (StackOverflowError)
-XX:PermSize:指定方法区(永久区)的初始值,默认是物理内存的 1/64,在 Java 8 永久区移除,代之的是元数据区,由 -XX:MetaspaceSize 指定
-XX:MaxPermSize:指定方法区的最大值,默认是物理内存的 1/4,在 Java 8 中由 -XX:MaxMetaspaceSize 指定元数据区的大小
-XX:NewRatio=n:年老代与年轻代的比值,-XX:NewRatio=2,表示年老代与年轻代的比值为 2:1
-XX:SurvivorRatio=n:Eden 区与 Survivor 区的大小比值,-XX:SurvivorRatio=8 表示 Eden 区与 Survivor 区的大小比值是 8:1:1,因为 Survivor 区有两个 (from, to)
ConcurrentHashMap 的锁机制
容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问 HashTable 的线程都必须竞争同一把锁。那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是 ConcurrentHashMap 所使用的锁分段技术。首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
G1 收集器简介?以及它的内存划分怎么样的?
(1)简介 Garbage-First(G1,垃圾优先)收集器是服务类型的收集器,目标是多处理器机器、大内存机器。它高度符合垃圾收集暂停时间的目标,同时实现高吞吐量。Oracle JDK 7 update 4 以及更新发布版完全支持 G1 垃圾收集器。
(2)G1 的内存划分方式 它是将堆内存被划分为多个大小相等的 heap 区,每个 heap 区都是逻辑上连续的一段内存 (virtual memory)。其中一部分区域被当成老一代收集器相同的角色 (eden, old),但每个角色的区域个数都不是固定的。这在内存使用上提供了更多的灵活性。
重写 equals 方法时,需要遵循哪些约定? 重写 equals 方法时需要遵循通用约定:自反性、对称性、传递性、一致性、非空性。
自反性 :对于任何非 null 的引用值 x,x.equals(x) 必须返回 true。这一点基本上不会有啥问题。
对称性 :对于任何非 null 的引用值 x 和 y,当且仅当 x.equals(y) 为 true 时,y.equals(x) 也为 true。
传递性 :对于任何非 null 的引用值 x、y、z。如果 x.equals(y)==true,y.equals(z)==true,那么 x.equals(z)==true。
一致性 :对于任何非 null 的引用值 x 和 y,只要 equals 的比较操作在对象所用的信息没有被修改,那么多次调用 x.equals(y) 就会一致性地返回 true,或者一致性的返回 false。
非空性 :所有比较的对象都不能为空。
优化后的锁机制简单介绍一下,包括自旋锁、偏向锁、轻量级锁、重量级锁?
自旋锁 线程自旋说白了就是让 CPU 在做无用功,比如:可以执行几次 for 循环,可以执行几条空的汇编指令,目的是占着不放,等待获取锁的机会。如果旋的时间过长会影响整体性能,时间过短又达不到延迟阻塞的目的。
偏向锁 偏向锁就是一旦线程第一次获得了监视对象,之后让监视对象'偏向'这个线程,之后的多次调用则可以避免 CAS 操作。说白了就是置个变量,如果发现为 true 则无需再走各种加锁/解锁流程。
轻量级锁 轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
重量级锁 重量锁在 JVM 中又叫对象监视器(Monitor),它很像 C 中的 Mutex,除了具备 Mutex(0|1) 互斥的功能,它还负责实现了 Semaphore(信号量) 的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait 队列),前者负责做互斥,后一个用于做线程同步。
偏向锁、轻量级锁、重量级锁的对比
Redis 与 Memcached 区别对比?如何选择这两个技术?
区别
Redis 和 Memcache 都是将数据存放在内存中,都是内存数据库。不过 memcache 还可用于缓存其他东西,例如图片、视频等等。
Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,hash 等数据结构的存储。
虚拟内存 -- Redis 当物理内存用完时,可以将一些很久没用到的 value 交换到磁盘。
过期策略 -- memcache 在 set 时就指定,例如 set key1 0 0 8,即永不过期。Redis 可以通过例如 expire 设定,例如 expire name 10。
分布式 -- 设定 memcache 集群,利用 magent 做一主多从;redis 可以做一主多从。都可以一主一从。
存储数据安全 -- memcache 挂掉后,数据没了;redis 可以定期保存到磁盘(持久化)。
灾难恢复 -- memcache 挂掉后,数据不可恢复;redis 数据丢失后可以通过恢复。
Redis 支持数据的备份,即 master-slave 模式的。
选型 若是简单的存取 key-value 这样的数据用好一些。若是要支持数据持久化,多数据类型 (如集合、散列之类的),用列表类型做队列之类的高级应用,就用 redis。
Redis 的持久化机制是什么?各自的优缺点? Redis 提供两种持久化机制 RDB 和 AOF 机制。
1)RDB 持久化方式 是指用数据集快照的方式记录 redis 数据库的所有键值对。
优点:
只有一个文件 dump.rdb,方便持久化。
容灾性好,一个文件可以保存到安全的磁盘。
性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。
相对于数据集大时,比 AOF 的启动效率更高。
缺点:
数据安全性低。
2)AOF 持久化方式 是指所有的命令行记录以 redis 命令请求协议的格式保存为 aof 文件。
优点:
数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
通过 append 模式写文件,即使中途宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
机制的 rewrite 模式。
缺点:
文件会比 RDB 形式的文件大。
数据集大的时候,比 rdb 启动效率低。
MySQL 的数据库表锁、行锁、页级锁?
表级 :直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许。
行级 :仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
页级 :表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
数据库的四大特征,数据库的隔离级别?
数据库的四大特征
原子性(Atomicity) :原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性(Consistency) :一个事务执行之前和执行之后都必须处于一致性状态。
隔离性(Isolation) :隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
持久性(Durability) :持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。
数据库的隔离级别
Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
Repeatable read (可重复读):可避免脏读、不可重复读的发生。
Read committed (读已提交):可避免脏读的发生。
Read uncommitted (读未提交):最低级别,任何情况都无法保证。
Set 集合从原理上如何保证不重复
在往 set 中添加元素时,如果指定元素不存在,则添加成功。也就是说,如果 set 中不存在 (e==null ? e1==null : e.equals(e1)) 的元素 e1,则 e1 能添加到 set 中。
具体来讲:当向 HashSet 中添加元素的时候,首先计算元素的 hashcode 值,然后用这个(元素的 hashcode)%(HashMap 集合的大小)+1 计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用 equals 方法比较元素是否相等,相等就不添加,否则找一个空位添加。
HashMap 和 HashTable 的主要区别是什么?两者底层实现的数据结构是什么?
HashMap 和 HashTable 的区别 二者都实现了 Map 接口,是将惟一键映射到特定的值上;主要区别在于:
HashMap 没有排序,允许一个 null 键和多个 null 值,而 Hashtable 不允许;
HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和 containsKey,因为 contains 方法容易让人引起误解;
Hashtable 继承自 Dictionary 类,HashMap 是 Java1.2 引进的 Map 接口的实现;
Hashtable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。Hashtable 和 HashMap 采用的 hash/rehash 算法大致一样,所以性能不会有很大的差异。
HashMap 和 HashTable 的底层实现数据结构 HashMap 和 HashTable 的底层实现都是数组 + 链表结构实现的。
HashMap 何时扩容,扩容的算法是什么?
HashMap 何时扩容 当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值 --- 即当前数组的长度乘以加载因子的值的时候,就要自动扩容。
扩容的算法是什么 扩容 (resize) 就是重新计算容量,向 HashMap 对象里不停的添加元素,而对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然 Java 里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。
Java 的虚拟机 JVM 的两个内存:栈内存和堆内存的区别是什么? Java 把内存划分成两种:一种是栈内存,一种是堆内存。两者的区别是:
栈内存 :在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存 :堆内存用来存放由 new 创建的对象和数组。在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。
Java 中对异常是如何进行分类的?
Java 异常结构中定义有 Throwable 类。Exception 和 Error 为其子类。
其中 Exception 表示由于网络故障、文件损坏、设备错误、用户输入非法情况导致的异常;
而 Error 标识 Java 运行时环境出现的错误,例如:JVM 内存耗尽。
数据库设计中常讲的三范式是指什么?
第一范式 1NF(域的原子性) :如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
第二范式 2NF(表中除主键外的字段都完全依赖主键) :第二范式是在第一范式基础上建立的。第二范式有两个重点:(1) 表中必须有主键;(2) 其他非主属性必须完全依赖主键,不能只依赖主键的一部分(主要针对联合主键而言)。
第三范式 3NF(表中除主键外的字段都完全直接依赖,不能是传递依赖) :不能是传递依赖,即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。第二范式和第三范式区分的关键点:2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列。
Java 中的线程池共有几种?
newCachedThreadPool :创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
newFixedThreadPool :创建一个指定工作线程数量的线程池。
newScheduledThreadPool :创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
SingleThreadExecutor :创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
volatile 与 synchronized 区别 volatile 和 synchronized 简介:在 Java 中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:
使用 synchronized 关键字
使用 volatile 关键字:用一句话概括 volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
两者的区别
volatile 本质是在告诉当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile 仅能使用在变量级别,synchronized 则可以使用在变量、方法。
volatile 仅能实现变量的修改可见性,而 synchronized 则可以保证变量的修改可见性和原子性。
volatile 不会造成线程的阻塞,而 synchronized 可能会造成线程的阻塞。
Spring 框架的特性
方便解耦,简化开发 :通过 Spring 提供的 IoC 容器,我们可以将对象之间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。
AOP 的支持 :通过 Spring 提供的 AOP 功能,方便进行面向切面的编程。
声明事务的支持 :在 Spring 中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
方便程序的测试 :可以用非容器依赖的方式进行几乎所有的测试工作。例如:Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序。
方便集成各种优秀框架 :Spring 不排斥各种优秀的框架,相反,Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Hibernate、Hessian、Quartz)等的直接支持。
降低 API 的使用难度 :Spring 对很多难用的 Java EE API(如 JavaMail,远程调用等)提供了一个薄薄的封装层,通过 Spring 的简易封装,这些 API 的使用难度大为降低。
AOP 的应用场景 AOP 用来封装横切关注点,具体可以在下面的场景中使用:Authentication 权限、Caching 缓存、Context passing 内容传递、Error handling 错误处理、Lazy loading 懒加载、Debugging 调试、logging, tracing, profiling and monitoring 记录跟踪优化校准、Performance optimization 性能优化、Persistence 持久化、Resource pooling 资源池、Synchronization 同步、Transactions 事务。
MyBatis 中 # 和 $ 区别
${} 是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如 ${driver} 会被静态替换为 com...Driver。
#{} 是 sql 的参数占位符,Mybatis 会将 sql 中的 #{ } 替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem.getName。
常见排序算法 排序的方法有:插入排序(直接插入排序、希尔排序),交换排序(冒泡排序、快速排序),选择排序(直接选择排序、堆排序),归并排序,分配排序(箱排序、基数排序)。
快速排序的伪代码 //使用快速排序方法对 a[0:n-1] 排序
从 a[0:n-1] 中选择一个元素作为 middle,该元素为支点;
把余下的元素分割为两段 left 和 right,使得 left 中的元素都小于等于支点,
而 right 中的元素都大于等于支点;
递归地使用快速排序方法对 left 进行排序;
递归地使用快速排序方法对 right 进行排序;
所得结果为 left + middle + right。
快速排序的 Java 代码实现 public void quickSort (int [] arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high);
quickSort(arr, low, pivotIndex - 1 );
quickSort(arr, pivotIndex + 1 , high);
}
}
private int partition (int [] arr, int low, int high) {
int pivot = arr[high];
int i = (low - 1 );
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1 ];
arr[i + 1 ] = arr[high];
arr[high] = temp;
return i + 1 ;
}
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online