在开发过程中经常遇到 OOM(内存溢出)问题,如何解决?
3. SpringBoot 配置文件的加载顺序优先级是什么?如果目录同时存在 application.properties 和 application.yml,优先加载哪个?
加载顺序优先级(从高到低):
当前项目
classpath
下的/config
目录下的配置文件。当前项目
classpath
根目录下的配置文件。项目外部的
/config
目录下的配置文件(如通过--spring.config.location
指定的外部配置路径)。项目外部的根目录下的配置文件(同样可通过
--spring.config.location
等指定)。(此外,命令行参数
--key=value
的优先级是最高的,会在所有配置文件之后生效;还有环境变量、Profile 激活的配置等也会参与优先级判定,但上述是常见的文件类优先级逻辑。)
同时存在 application.properties 和 application.yml 时:
SpringBoot 优先加载 application.properties。因为
.properties
格式的加载顺序在.yml
之前(.yml
本质上是 YAML 格式,属于一种更简洁的结构化配置方式,但优先级低于.properties
)。
4. spring 的动态代理模式有几种?默认是哪种?如何切换?
Spring 动态代理主要分为两种技术实现思路:
JDK 动态代理:基于 Java 原生的
java.lang.reflect.Proxy
和InvocationHandler
实现。要求被代理的目标类必须实现至少一个接口,代理类会继承Proxy
并实现目标接口,在运行时生成字节码。CGLIB 动态代理:基于 ASM 字节码操作框架,在运行时对目标类的字节码进行增强,生成一个子类来作为代理类。这种方式不需要目标类实现接口,但会生成目标类的子类(所以如果目标类是
final
类,就无法被 CGLIB 代理)。
默认代理:
Spring 在早期版本中,默认会根据被代理对象是否有接口来选择代理方式:如果有接口,优先用 JDK 动态代理;如果没有接口,则用 CGLIB。
从 Spring 4.0 之后,这个“自动选择”的逻辑有所调整,但大体上还是优先尝试 JDK 动态代理,若不满足(无接口)则 fallback 到 CGLIB。
如何切换代理模式:
可以通过配置属性或者注解来指定使用哪种代理:
全局配置(application.properties / application.yml):
# 强制使用 CGLIB 代理(即便目标类有接口,也走 CGLIB) spring.aop.proxy-target-class=true
或者
spring:aop:proxy-target-class: true # true 表示强制 CGLIB;false 表示优先 JDK
局部配置(@EnableAspectJAutoProxy 注解):
在开启 AOP 的
@Configuration
类上,给@EnableAspectJAutoProxy
加上参数:@EnableAspectJAutoProxy(proxyTargetClass = true) // true 表示强制 CGLIB
5. REST API 是无状态的吗,如何保障 API 的安全调用?
REST API 是否无状态:
REST(Representational State Transfer)架构风格本身要求 API 是无状态(Stateless) 的。也就是说,服务端不保存客户端的状态信息,每次请求都应该包含理解该请求所需的全部信息(比如身份凭证、参数等),服务端仅根据本次请求的内容来做处理,不依赖于之前的请求上下文。
如何保障 API 安全调用?常见手段有以下几类:
身份认证(Authentication):
基于 Token:如 JWT(JSON Web Token)。客户端登录成功后,服务端生成包含用户信息的 JWT 并返回,后续客户端每次请求都在请求头(如
Authorization: Bearer <token>
)中携带该 Token,服务端验证 Token 合法性与有效性即可完成认证。OAuth2:适用于第三方授权场景,通过授权码模式、隐式授权模式、密码模式、客户端模式等,让资源所有者(用户)授权第三方应用访问受保护资源。
HTTP Basic Auth:将用户名和密码进行 Base64 编码后放在请求头的
Authorization
字段,服务端解码后验证。不过这种方式明文传输风险较高,一般要配合 HTTPS 使用。
授权(Authorization):
认证通过后,还需判断该用户是否有权限访问某个 API 或操作某个资源。常见做法是基于角色的访问控制(RBAC)、基于资源的访问控制(RBAC 扩展或 ABAC 等),在服务端代码或借助框架(如 Spring Security)来做权限校验。
数据加密与完整性:
HTTPS:强制使用 HTTPS 协议,对请求和响应进行 TLS 加密,防止中间人窃听、篡改数据。
签名(Signature):对请求参数按一定规则排序后,用密钥生成签名,服务端收到请求后同样生成签名做比对,确保请求在传输过程中没有被篡改。常用于对安全性要求极高的场景(如金融类 API)。
防重放攻击:
给每个请求加上唯一标识(如时间戳 + 随机数)或者使用 nonce(一次性随机数),服务端记录已使用的标识,拒绝重复的请求,避免攻击者截获请求后重复发送来达到非法目的。
输入校验与过滤:
对客户端传入的参数进行严格的格式、长度、范围等校验,防止 SQL 注入、XSS(跨站脚本)等攻击。可以使用框架自带的校验注解(如 Spring 的
@Valid
结合 JSR-303 规范),也可以手动编写校验逻辑。
6. 在开发过程中经常遇到 OOM(内存溢出)问题,如何解决?
OOM(Out Of Memory)指 Java 虚拟机在分配对象时,没有足够的内存空间,抛出 java.lang.OutOfMemoryError
异常。常见原因与解决思路如下:
1. 先定位 OOM 场景与堆栈信息
获取内存快照:
使用 JDK 自带的
jmap
命令、可视化工具(如 Eclipse MAT、VisualVM、YourKit 等)抓取堆转储文件(.hprof
)。例如:jmap -dump:format=b,file=heap.hprof <pid>
然后在工具里分析内存占用大的对象、引用链,看是哪些类实例过多、占用内存异常。
分析 OOM 报错信息:
OOM 的具体子类(如
java.lang.OutOfMemoryError: Java heap space
、java.lang.OutOfMemoryError: Metaspace
、java.lang.OutOfMemoryError: GC overhead limit exceeded
等)能帮我们缩小问题范围。
2. 针对不同场景的常见原因与解决
Java Heap Space 溢出(堆内存不足):
原因:对象太多且无法被 GC 回收(存在内存泄漏),或者对象本身就很大(如加载了大文件到内存、超大集合)。
解决:
检查是否存在内存泄漏:看是否有对象被意外地长期引用(比如静态集合里一直 add 对象但没 remove,或者单例对象持有大对象引用不释放等)。借助内存分析工具找到泄漏点,修正代码逻辑。
调整堆内存大小:如果业务确实需要较大堆空间,可以适当增大
-Xmx
(最大堆)和-Xms
(初始堆)参数,但要结合服务器资源合理设置。优化对象创建与使用:减少不必要的对象创建(比如用对象池复用对象);对大集合做分页、分批处理,避免一次性加载过多数据到内存。
Metaspace 溢出:
原因:Metaspace 是存放类元数据的地方(JDK 8+ 取代了永久代 PermGen)。如果应用动态生成大量类(如频繁使用 CGLIB 代理、动态字节码技术,或者框架内部生成过多代理类),且没有及时卸载,就会导致 Metaspace 耗尽。
解决:
减少动态类的生成:检查是否有不必要的动态代理、字节码增强操作;对框架配置做优化(比如减少 AOP 切面过多、减少不必要的自动代理)。
调整 Metaspace 参数:增大
-XX:MaxMetaspaceSize
(最大元空间大小),并配合-XX:MetaspaceSize
(初始元空间大小)设置合理阈值;同时关注-XX:+UseCompressedClassPointers
等压缩指针参数对 Metaspace 的影响。
GC Overhead Limit Exceeded:
原因:GC 回收时间占比过高(比如连续多次 GC 都只能回收很少内存),JVM 认为继续 GC 没意义,直接抛出 OOM。本质还是堆内存不足或存在大量无法回收的对象。
解决:
先排查是不是 Heap Space 或 Metaspace 的问题,参考上面两类场景的处理方式。
如果业务场景允许,可以适当调整
-XX:-UseGCOverheadLimit
关闭这个检查(不推荐,属于治标不治本,最好还是先找内存问题根源)。
Direct Memory 溢出(NIO 直接内存):
原因:Java NIO 中通过
java.nio.DirectByteBuffer
分配的直接内存(不受 JVM 堆管理,由操作系统本地内存分配),如果分配过多且没释放,会导致 Direct Memory 不足。常见于大量使用 Netty 等基于 NIO 的网络框架,或者手动频繁分配直接内存的场景。解决:
检查直接内存的分配与释放逻辑,确保不再使用的
DirectByteBuffer
能被正确回收(可以通过-XX:MaxDirectMemorySize
限制直接内存大小)。调整 JVM 参数,比如
-XX:MaxDirectMemorySize=256m
(限制最大直接内存为 256MB),并监控实际使用情况。
Stack Overflow(栈溢出,虽不属于 Heap OOM,但也是常见内存问题):
原因:线程请求的栈深度超过了 JVM 所允许的最大深度(比如递归调用没有终止条件)。
解决:
检查递归、循环等逻辑,确保有正确的终止条件。
调整栈大小参数
-Xss
(如-Xss2m
表示每个线程栈大小为 2MB),但要注意栈太大可能导致线程数减少,需权衡。
3. 通用优化与监控思路
代码层面:做好对象生命周期管理、减少不必要的对象创建、合理使用集合类(避免扩容带来的额外开销)、及时关闭资源(IO 流、数据库连接等)。
JVM 调优:根据应用特点调整堆、Metaspace、Direct Memory 等参数;选择合适的 GC 算法(如 G1、ZGC 等低延迟 GC,适合对停顿敏感的业务)。
监控与告警:在生产环境部署 APM 工具(如 Prometheus + Grafana、SkyWalking 等),对内存使用率、GC 频率与耗时等指标做实时监控,出现异常时及时告警,便于快速定位问题。