使用静态聚集持有对象引用,制止GC回收
关键点:
使用static List作为内存泄漏的锚点,其生命周期与ClassLoader同等
每次哀求向列表添加1MB字节数组,这些对象会连续占用堆内存
由于聚集持有强引用,GC无法回收这些对象
最终会导致OutOfMemoryError: Java heap space
可实行代码:
- package io.renren.controller;
- import org.springframework.boot.SpringApplication;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.client.RestTemplate;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * author: lj
- * date: 2025-4
- */
- @RestController
- public class MemoryLeakController {
- // 静态集合会持续持有对象引用
- private static List<byte[]> LEAKING_LIST = new ArrayList<>();
- // 内存泄漏端点
- @GetMapping("/leak")
- public String leakMemory() {
- // 每次请求添加1MB数据(不会被释放)
- LEAKING_LIST.add(new byte[1024 * 1024]);
- return "已泄漏内存: " + LEAKING_LIST.size() + " MB";
- }
- // 触发OOM的测试方法(快速验证)
- public static void main(String[] args) throws InterruptedException {
- SpringApplication.run(MemoryLeakController.class, args);
- // 通过循环请求快速触发OOM
- while(true) {
- new RestTemplate().getForObject("http://localhost:8080/leak", String.class);
- Thread.sleep(100);
- }
- }
- }
复制代码 验证:
1,运行程序(启动时添加JVM参数限定堆巨细):
- //在cmd中先cd到jar包所在目录,执行如下命令启动
- //-Xmx100m 当程序需要更多内存时,JVM会尝试分配最多100MB的堆内存。如果超过这个限制,可能会抛出OutOfMemoryError
- //-Xms100m JVM在启动时分配的最小内存量。如果初始堆内存设置得过低,程序可能在运行过程中频繁扩展堆内存,影响性能。
- //-XX:+HeapDumpOnOutOfMemoryError 在发生OutOfMemoryError时生成堆转储(Heap Dump)的功能
- java -jar -Xmx100m -Xms100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\Temp renren-generator-1.0.0.jar
复制代码 2,访问 http://localhost:8080/leak 触发泄漏
日志 输出体现了内存泄漏位置。
并且在临时目次中生存了一份堆转储文件,稍后使用MAT(Memory Analyzer Tool)分析。
标题定位
使用jvisualvm工具定位标题
在cmd输入jvisualvm指令
选中应用后,可以监控 应用程序的性能。
触发内存泄漏后,查察每次GC的连续时间、回收的内存等信息。OOM之后,点击界面右上角的堆Dump,打开应用的堆转储信息。
查找最大对象
打开java.lang.Object[]的生存堆
查察LEAKING_LIST的引用链,至此标题定位完成。
使用MAT(Memory Analyzer Tool)工具定位标题
下载地点:https://eclipse.dev/mat/download/previous/
我的是JDK8,以是我下载了Memory Analyzer 1.10.0 Release版本。下载完成后,直接解压,运行此中的MemoryAnalyzer.exe文件即可启动MAT工具。
用mat工具打开刚刚临时目次中生存的堆转储文件,点击Leak Suspects天生内存泄漏报表。
点击details查察java.lang.Object[]的生存堆
查察LEAKING_LIST的引用链,至此标题定位完成。
调优发起
1,避免长时间持有大对象引用。
2,定期实行聚集清算操作。
- @Scheduled(fixedRate = 60_000)
- public void cleanLeakingData() {
- LEAKING_LIST.removeIf(data -> /* 清理条件 */);
- }
复制代码 --------------------------------------------------更新---------------------------------------------------------
变种实现方式
- @SpringBootApplication
- @RestController
- @EnableCaching // 关键注解:启用缓存
- public class CacheLeakDemo {
- // 模拟缓存未正确清理
- @Cacheable("leakyCache")
- @GetMapping("/cache-leak")
- public byte[] cacheLeak() {
- return new byte[1024 * 1024]; // 每次缓存1MB
- }
- public static void main(String[] args) {
- SpringApplication.run(CacheLeakDemo.class, args);
- }
- }
复制代码 缓存泄漏原理:
@Cacheable会将每次差别参数的返回结果缓存
由于没有设置逾期时间或巨细限定,缓存会无穷增长
示例中每个哀求天生唯一key(默认基于方法参数),导致缓存不停累积
调优发起
对于缓存使用WeakReference或框架(Caffeine/Ehcache)
- // 使用WeakHashMap解决
- private static Map<byte[], Boolean> SAFE_MAP =
- Collections.synchronizedMap(new WeakHashMap<>());
复制代码- // 使用Caffeine缓存并设置上限
- @Bean
- public CacheManager cacheManager() {
- CaffeineCacheManager manager = new CaffeineCacheManager();
- manager.setCaffeine(Caffeine.newBuilder()
- .maximumSize(100)
- .expireAfterWrite(10, TimeUnit.MINUTES));
- return manager;
- }
复制代码 由于在 Java 中,WeakHashMap 的设计目标就是通过弱引用(Weak Reference)自动清算不再被使用的键值对,从而避免因对象残留导致的内存泄漏。
引用类型对比表:
引用类型GC活动典范应用场景强引用永不回收(除非显式置为null)平凡对象引用软引用内存不足时回收缓存弱引用下次GC立刻回收WeakHashMap/WeakReference虚引用回收时收到关照资源清算跟踪关键机制:
WeakHashMap 的 键(Key)使用弱引用存储
当键对象不再被其他强引用持偶尔,该键值对会被自动移除
值对象(Value)仍使用强引用,必要特别留意解耦
内存泄漏场景 vs WeakHashMap修复方案
- //使用普通HashMap导致泄漏
- public class LeakingCache {
- private static Map<byte[], String> CACHE = new HashMap<>();
- // 添加大对象到缓存
- public static void addToCache(byte[] key, String value) {
- CACHE.put(key, value);
- }
- public static void main(String[] args) {
- // 模拟添加后不再使用key
- byte[] key = new byte[1024 * 1024]; // 1MB
- addToCache(key, "大数据");
-
- key = null; // 删除强引用
-
- // 触发GC
- System.gc();
-
- // 缓存仍然持有key的强引用,导致1MB内存无法回收
- System.out.println("缓存大小: " + CACHE.size()); // 输出1
- }
- }
复制代码- //使用WeakHashMap
- public class SafeCache {
- // 使用WeakHashMap + 同步包装(线程安全)
- private static Map<byte[], String> SAFE_CACHE =
- Collections.synchronizedMap(new WeakHashMap<>());
- public static void addToCache(byte[] key, String value) {
- SAFE_CACHE.put(key, value);
- }
- public static void main(String[] args) {
- byte[] key = new byte[1024 * 1024];
- addToCache(key, "安全数据");
-
- key = null; // 删除最后一个强引用
-
- // 强制GC(生产环境不要主动调用System.gc())
- System.gc();
-
- // 给GC一点时间执行
- try { Thread.sleep(1000); } catch (InterruptedException e) {}
-
- System.out.println("缓存大小: " + SAFE_CACHE.size()); // 输出0
- }
- }
复制代码 实战应用
场景:装备毗连会话管理
- @RestController
- public class DeviceController {
- // 使用WeakHashMap管理临时会话
- private static Map<Device, Session> deviceSessions =
- Collections.synchronizedMap(new WeakHashMap<>());
- @PostMapping("/connect")
- public String connect(@RequestBody Device device) {
- Session session = new Session(device);
- deviceSessions.put(device, session);
- return "Connected";
- }
- // 当Device对象不再被外部引用时,自动清理会话
- }
复制代码 设置验证端点
- @GetMapping("/session-count")
- public int getSessionCount() {
- return deviceSessions.size();
- }
复制代码 测试方法
- 1,发送连接请求
- curl -X POST http://localhost:8080/connect -d '{"id":"device1"}'
- 2,立即调用/session-count查看数量
- 3,停止持有Device对象引用后触发GC
- 4,再次检查会话数量
复制代码 增强版缓存实现(带自动清算)
- public class AdvancedCache<K, V> {
- private final Map<K, V> cache =
- new WeakHashMap<>();
- private final ReferenceQueue<K> queue =
- new ReferenceQueue<>();
- public void put(K key, V value) {
- // 清理已回收的条目
- processQueue();
- cache.put(key, value);
- }
- private void processQueue() {
- Reference<? extends K> ref;
- while ((ref = queue.poll()) != null) {
- // 这里可以触发回调清理相关资源
- System.out.println("清理条目: " + ref);
- }
- }
- }
复制代码 代码测试片断
- // 测试插入100万条数据
- IntStream.range(0, 1_000_000).forEach(i -> {
- Object key = new Object();
- map.put(key, "Value-" + i);
- });
- // 强制GC后统计剩余条目
- System.gc();
- Thread.sleep(1000);
- System.out.println("剩余条目: " + map.size());
复制代码 测试结果:
Map类型初始条目GC后剩余条目内存占用(MB)HashMap1,000,0001,000,00085.3WeakHashMap1,000,0003,2146.7场景:装备状态临时缓存
- public class DeviceStateManager {
- // Key: 设备对象,Value: 最后上报时间
- private final WeakHashMap<Device, Long> lastReportTime =
- new WeakHashMap<>();
- // 更新状态
- public void updateState(Device device) {
- lastReportTime.put(device, System.currentTimeMillis());
- }
- // 获取在线设备列表(需配合ReferenceQueue清理)
- public List<Device> getOnlineDevices() {
- return new ArrayList<>(lastReportTime.keySet());
- }
- }
复制代码 优势分析:
当装备断开毗连且不再被其他模块引用时,自动清算状态
避免因装备频仍上下线导致的内存增长
适看成为二级缓存,共同恒久化存储使用
综上:
WeakHashMap 是办理特定类型内存泄漏的有用工具,但必要充实明白其工作原理和实用场景。在实际物联网 体系中,通常必要联合软引用、引用队列等机制构建更结实的缓存体系。
----------------------------------------------基础信息补充--------------------------------------------------------
除了上方方法,也能通过JDK自带的工具jmap,jconsole来得到一个堆转储文件。
jvm(java捏造机)管理的内存大致包罗三种差别类型的内存地域:
PermanentGeneration space(永久生存地域)、Heap space(堆地域)、JavaStacks(Java栈)。
1,此中永久生存地域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGenspace地域,Class必要存储的内容主要包罗方法和静态属性。
2,堆地域用来存放Class的实例(即对象),对象必要存储的内容主要黑白静态属性。每次用new创建一个对象实例后,对象实例存储在堆地域中,这部分空间也被jvm的垃圾回收机制管理。
3,而Java栈跟大多数编程语言包罗汇编语言的栈功能相似,主要根本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。
容易发生内存溢出标题标内存空间包罗:PermanentGeneration space和Heap space。
第一种OutOfMemoryError:PermGenspace
发生这种标题标原意是程序中使用了大量的jar或class,使java捏造机装载类的空间不够,与PermanentGeneration space有关。办理这类标题有以下两种办法:
1、增长java捏造机中的XX ermSize和XX:MaxPermSize参数的巨细,此中XX ermSize是初始永久生存地域巨细,XX:MaxPermSize是最大永久生存地域巨细。如针对tomcat,在catalina.sh或catalina.bat文件中一系列情况变量名阐明竣事处(约莫在70行左右) 增长一行:
JAVA_OPTS=" -XX ermSize=64M -XX:MaxPermSize=128m"
第二种OutOfMemoryError:Java heap space
发生这种标题标缘故原由是java捏造机创建的对象太多,在举行垃圾回收之间,捏造机分配的到堆内存空间已经用满了,与Heapspace有关。办理这类标题有两种思路:
1、查抄程序,看是否有死循环或不须要地重复创建大量对象。找到缘故原由后,修改程序和算法。
2、增长Java捏造机中Xms(初始堆巨细)和Xmx(最大堆巨细)参数的巨细。如:set JAVA_OPTS= -Xms256m-Xmx1024m
第三种OutOfMemoryError:unable to create new nativethread
这种错误在Java线程个数许多的情况下容易发生
GC
垃圾网络(GC)是Java内存管理的紧张机制之一。它负责自动回收不再使用的对象所占用的内存,以避免内存泄漏和OOM标题标发生。
GC的工作原理主要涉及到两个关键概念:标记-扫除(Mark-Sweep)和分代网络(Generational)。标记-扫除算法会遍历整个堆空间,标记出仍然被引用的对象,然后扫除未被标记的对象所占用的内存。分代网络则是将堆空间分别为新生代和老年代两个地域,根据对象的存活周期采用差别的回收计谋。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|