加群联系作者vx:xiaoda0423
仓库地址:webvueblog.github.io/JavaPlusDoc...
频繁创建短生命周期对象(如 JSON 对象、临时列表等)
ini
public class ObjectReuseDemo {
private static final int LOOP = 10_000_000;
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 改进:使用对象池模拟复用(或线程复用对象)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < LOOP; i++) {
sb.setLength(0); // 清空
sb.append("user_").append(i).append("_info");
String result = sb.toString();
if (i % 2_000_000 == 0) {
System.out.println(result);
}
}
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
}
}
✅ 优化点
- 避免频繁创建临时对象,降低 Eden GC 压力
StringBuilder
在单线程场景比String +
高效
默认大对象直接进入老年代,增加 Full GC 频率
csharp
public class LargeObjectDemo {
public static void main(String[] args) {
System.out.println("分配大数组...");
byte[] big = new byte[10 * 1024 * 1024]; // 10MB
System.out.println("大对象创建完毕,等待回收...");
}
}
✅ 启动参数优化建议
ini
-XX:+UseG1GC
-XX:G1HeapRegionSize=2m # 控制 region 大小
-XX:PretenureSizeThreshold=5m # >5MB 的对象直接进入老年代
💡 提示
- 在 G1 中,可以控制大对象是否直接进入老年代
- 可通过 GC 日志观察大对象分配路径
通用 JVM 启动调优参数模板(适用于以上)
ruby
-Xms512m -Xmx512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=./heap.hprof \
-Xlog:gc*:file=gc.log:time,level,tags
ThreadLocal 使用不当,导致内存泄漏
csharp
public class ThreadLocalLeakDemo {
private static final ThreadLocal<byte[]> local = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 1000; i++) {
pool.submit(() -> {
// 模拟请求处理,ThreadLocal 绑定大对象
local.set(new byte[1024 * 1024]); // 1MB
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
} finally {
// ⚠️ 不清理会导致内存泄漏(线程池中的线程长期持有 ThreadLocal 值)
local.remove();
}
});
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.MINUTES);
}
}
✅ JVM 优化建议
- 在线程池中使用 ThreadLocal 一定要记得
remove()
! - 否则容易造成长时间占用堆内存
- 可配合
-Xmx256m
+-XX:+HeapDumpOnOutOfMemoryError
验证泄漏现象
频繁反射使用,CPU & 内存双压
ini
import java.lang.reflect.Method;
public class ReflectionOveruseDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.lang.String");
for (int i = 0; i < 10_000_000; i++) {
Method method = clazz.getMethod("toUpperCase");
String result = (String) method.invoke("hello");
if (i % 1_000_000 == 0) {
System.out.println(result);
}
}
}
}
✅ 优化建议
- 反射很慢且会缓存方法句柄,可能导致 PermGen/MetaSpace 增长
- 尽量缓存 Method 对象 或使用
MethodHandle
- 启动参数中可设定
-XX:MaxMetaspaceSize=128m
观察增长
频繁使用 Lambda/匿名类 导致 GC 压力
arduino
import java.util.function.Function;
public class LambdaLeakDemo {
public static void main(String[] args) {
for (int i = 0; i < 10000000; i++) {
Function<String, String> func = s -> s.toUpperCase(); // 每次创建新的匿名类
func.apply("hello");
}
System.out.println("执行完成");
}
}
✅ 优化建议
- 使用静态 Lambda 或缓存 Lambda 表达式(尤其是热点路径)
- JVM 在某些版本中对 Lambda 实现仍是匿名内部类,可能造成类加载压力
- 查看 Metaspace 利用率是否飙高,搭配
-XX:+PrintCompilation
观察 JIT 情况
频繁创建线程(绕过线程池)导致内存耗尽
-
每个线程占用约 1MB 栈空间(
-Xss1m
) -
线程数过多 → 内存耗尽 → 报错:
arduinojava.lang.OutOfMemoryError: unable to create new native thread
✅ 优化建议
- 使用线程池代替手动创建
- 减小
-Xss
(如-Xss512k
) - 限制最大并发线程数
Netty 或 NIO DirectMemory 泄漏
java
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class DirectMemoryLeakDemo {
public static void main(String[] args) throws InterruptedException {
List<ByteBuffer> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB
list.add(buffer); // 没有释放!
if (i % 100 == 0) {
System.out.println("已分配 DirectBuffer:" + i + " MB");
Thread.sleep(100);
}
}
}
}
❌ 问题
-
DirectBuffer 走的是堆外内存(不走 GC)
-
默认限制很小,容易 OOM:
arduinojava.lang.OutOfMemoryError: Direct buffer memory
✅ 优化建议
-
增加:
-XX:MaxDirectMemorySize=512m
-
使用
buffer.clear()
或手动释放(如 Netty 的release()
) -
开启 Netty 泄漏检测:
iniResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
静态集合引用,导致类无法卸载
java
import java.util.ArrayList;
import java.util.List;
public class StaticLeakDemo {
public static List<byte[]> staticList = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while (true) {
staticList.add(new byte[1024 * 100]); // 100KB
if (staticList.size() % 100 == 0) {
System.out.println("staticList.size = " + staticList.size());
Thread.sleep(500);
}
}
}
}
❌ 问题
- 静态变量引用的对象永远不会被 GC 回收
- 热部署、类卸载失败、Perm/MetaSpace 占满
✅ 优化建议
- 避免静态变量引用大对象(尤其缓存类)
- 用弱引用
WeakReference
、或放入WeakHashMap
- 分析类加载器占用:
jcmd pid GC.class_stats
服务启动时初始化太慢(类加载 + 大量对象)
java
import java.util.ArrayList;
import java.util.List;
public class SlowStartupDemo {
public static final List<String> cache = new ArrayList<>();
static {
for (int i = 0; i < 1000000; i++) {
cache.add("data_" + i); // 模拟加载配置、预热缓存
}
System.out.println("静态初始化完成");
}
public static void main(String[] args) {
System.out.println("主程序开始执行");
}
}
❌ 问题
- 类加载阶段内存暴增,GC 频繁
- 启动慢,影响容器健康探针
✅ 优化建议
- 延迟加载 / 懒加载
- 启动分析工具:
-XX:+PrintCompilation -XX:+TraceClassLoading
- 拆分初始化逻辑,或放到异步线程中
Netty/Redis连接泄漏模拟
arduino
public class FakeRedisLeak {
public static void main(String[] args) throws Exception {
List<FakeConnection> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
FakeConnection conn = new FakeConnection();
list.add(conn); // 模拟连接没释放
Thread.sleep(50);
}
}
static class FakeConnection {
private final byte[] data = new byte[1024 * 512]; // 模拟缓冲区
}
}
✅ 适合分析
- 服务没正确释放外部资源(池化没生效)
- 连接池配置不当导致"潜在 OOM"
- 搭配 GC log 和
jmap -histo
看堆增长来源
内存管理优化(避免频繁的 GC)
在缓存系统中,频繁的对象创建和销毁会加大 GC 压力。我们通过以下方法减少内存占用和垃圾产生:
- 使用
ThreadLocal
或对象池来缓存一些常用的临时对象,例如SimpleDateFormat
、Pattern
等。
arduino
// 使用 ThreadLocal 缓存线程私有的 SimpleDateFormat 对象
private static final ThreadLocal<SimpleDateFormat> threadLocalDateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
优化点:
- 减少临时对象的创建:减少常用对象的频繁创建,避免 GC 频繁发生。
- 避免对象拆箱装箱:尽量使用原始类型或高效集合来避免自动装箱。
高并发下避免锁竞争
对于高并发请求,减少锁的竞争十分重要。ReentrantLock
、ReadWriteLock
和 Atomic
类可以有效减少锁的竞争,提高性能。
csharp
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public String getData(String key) {
lock.readLock().lock();
try {
// 首先尝试读取缓存
String value = localCache.getIfPresent(key);
if (value == null) {
// 如果缓存中没有,获取数据库数据并更新缓存
lock.writeLock().lock();
try {
value = databaseService.getDataFromDb(key);
localCache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
return value;
} finally {
lock.readLock().unlock();
}
}
优化点:
- 读写锁 :读操作时使用
readLock
,写操作时使用writeLock
,避免了过多的锁竞争。 - 高并发下读多写少的场景:使用读写锁提升并发读的效率。
垃圾回收优化
- 启动时配置 G1 垃圾收集器,提升内存管理效率。
ruby
java -Xms2g -Xmx4g -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError
- 使用 对象池 或 缓存池 避免频繁的对象创建,减少 GC 压力。
最终代码:
ini
public class CacheService {
private final Cache<String, String> localCache;
private final RedisCache redisCache;
private final DatabaseService databaseService;
private final ReadWriteLock lock;
public CacheService() {
localCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(10, TimeUnit.MINUTES).build();
redisCache = new RedisCache(); // 假设 Redis 配置已完成
databaseService = new DatabaseService(); // 假设数据库服务已完成
lock = new ReentrantReadWriteLock();
}
public String getDataFromCache(String key) {
lock.readLock().lock();
try {
String value = localCache.getIfPresent(key);
if (value == null) {
value = redisCache.get(key);
if (value != null) {
localCache.put(key, value);
} else {
lock.writeLock().lock();
try {
value = databaseService.getDataFromDb(key);
redisCache.put(key, value);
localCache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
return value;
} finally {
lock.readLock().unlock();
}
}
}