ConcurrentHashMap内存泄漏
问题描述
🍇 实现统计数据的定时上报
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.unicom.opn.api.core.domain.constant.ApiConstant;
import com.unicom.qh.common.redis.core.RedisOperations;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class StatisticService {
@Autowired
private RedisOperations redisOperations;
private final Map<String, LongAdder> localStats = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public StatisticService() {
// 启动定时任务
scheduler.scheduleWithFixedDelay(this::syncToRedis, 0, 60, TimeUnit.SECONDS);
// 每1分钟执行一次,syncToRedis方法
}
public void provPreLog(Object provinceId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String key = sdf.format(new Date());
localStats.computeIfAbsent(key , k -> new LongAdder()).increment();
}
private void syncToRedis() {
localStats.forEach((key, adder) -> {
long value = adder.sumThenReset();
if (value > 0) {
log.info("[统计数据] -> 存储数据 [{}] 数量 {}",key,value);
redisOperations.incrby(key, value, 60 * 60 * 24 * 3L);
}
});
// 遍历localStats集合(map),遍历每个key,将key-adder对应成redis的key-value进行上报;
// 并将已经上报的adder清零。
// 但localStats集合的key不进行删除,那么会存在key无线增多的情况;
// 故此会出现内存泄漏的问题。
}
}
问题解决
🍇 针对内存泄漏,进行修改,引入caffine
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.unicom.opn.api.core.domain.constant.ApiConstant;
import com.unicom.qh.common.redis.core.RedisOperations;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class StatisticService {
@Autowired
private RedisOperations redisOperations;
// 🍇 引入caffine
private final Cache<String, LongAdder> localStats = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.MINUTES) // 设置过期时间
.maximumSize(10_000) // 设置最大key个数
.build();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public StatisticService() {
// 启动定时任务
scheduler.scheduleWithFixedDelay(this::syncToRedis, 0, 60, TimeUnit.SECONDS);
// 每1分钟执行一次,syncToRedis方法
}
public void provPreLog(Object provinceId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String key = sdf.format(new Date());
// 🍇
localStats.get(key , k -> new LongAdder()).increment();
}
private void syncToRedis() {
// 🍇
localStats.asMap().forEach((key, adder) -> {
long value = adder.sumThenReset();
if (value > 0) {
log.info("[统计数据] -> 存储数据 [{}] 数量 {}",key,value);
redisOperations.incrby(key, value, 60 * 60 * 24 * 3L);
}
});
}
}
涉及知识
LongAdder.sumThenReset()
ConcurrentHashMap并发异常
问题描述
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.unicom.opn.api.core.domain.constant.ApiConstant;
import com.unicom.qh.common.redis.core.RedisOperations;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class StatisticService {
@Autowired
private RedisOperations redisOperations;
private final Map<String, LongAdder> localStats = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public StatisticService() {
// 启动定时任务
scheduler.scheduleWithFixedDelay(this::syncToRedis, 0, 60, TimeUnit.SECONDS);
// 每1分钟执行一次,syncToRedis方法
}
public void provPreLog(Object provinceId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String key = sdf.format(new Date());
localStats.computeIfAbsent(key , k -> new LongAdder()).increment();
}
private void syncToRedis() {
localStats.forEach((key, adder) -> {
long value = adder.sumThenReset();
if (value > 0) {
log.info("[统计数据] -> 存储数据 [{}] 数量 {}",key,value);
redisOperations.incrby(key, value, 60 * 60 * 24 * 3L);
}
});
// 遍历localStats集合(map),遍历每个key,将key-adder对应成redis的key-value进行上报;
// 并将已经上报的adder清零。
// 当执行sumThenReset时,分为sum和reset两个步骤,当刚执行完sum,此时又执行了increment,然后又执行了reset。
// 会导致新增操作increment的个数,没有被sum统计到,就直接被reset重置了。
}
}
问题解决
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.unicom.opn.api.core.domain.constant.ApiConstant;
import com.unicom.qh.common.redis.core.RedisOperations;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PreDestroy;
@Service
@Slf4j
public class StatisticService {
@Autowired
private RedisOperations redisOperations;
private volatile Map<String, LongAdder> localStats = new ConcurrentHashMap<>();
// 修改修饰符,从final改为volatile
// final 修饰集合类元素,可以执行增、删、改操作,但是不能修改元素地址。
// volatile 表示强制将线程本地缓存中的localStats对象刷新到主存中,同时导致其他线程中的localStats对象无效。
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
/**
* 类级别定义ThreadLocal缓存的SimpleDateFormat
*/
private static final ThreadLocal<SimpleDateFormat> SDF = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyyMMdd")
);
public StatisticService() {
// 启动定时任务
scheduler.scheduleWithFixedDelay(this::syncToRedis, 0, 60, TimeUnit.SECONDS);
}
public void provPreLog(Object provinceId) {
localStats.computeIfAbsent(provinceId, k -> new LongAdder()).increment();
}
private void syncToRedis() {
final Map<String, LongAdder> dataToSync = localStats;
localStats = new ConcurrentHashMap<>();
// 将原始localStats赋值给临时dataToSync
// 原始localStats重置为空
// 针对increment操作采用的是localStats
// 针对上报redis操作采用的是dataToSync
// 解决了数据丢失问题
dataToSync.forEach((key, adder) -> {
long value = adder.sum();
if (value > 0) {
log.info("[统计数据] -> 存储数据 [{}] 数量 {}",key,value);
redisOperations.incrby(key, value, 60 * 60 * 24 * 3L);
}
});
}
@PreDestroy
// 表示该方法在对象被销毁前执行,常用于资源清理、优雅关闭服务等操作。
public void destroy() {
// 强制同步剩余数据
syncToRedis();
// 优雅关闭定时任务线程池
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow(); // 超时则强制关闭
}
} catch (InterruptedException e) {
scheduler.shutdownNow(); // 异常则强制关闭
}
log.info("统计服务已关闭,数据同步完成");
}
}
jar包反编译
🍇 cfr反编译 到 class
采用CFR实现jar包反编译,但编译后的内容全是.class文件
下载地址:https://www.benf.org/other/cfr/
下载最新版,当前为cfr-0.152.jar
将其放入本地目录D:/xx/xx/cfr-0.152.jar
并将需要反编译的test.jar包和反编译后的数据存储文件夹test 提前准备
进入该目录下,在路径上输入cmd,进入命令行模式
执行命令:java -jar cfr-0.152.jar test.jar --outputdir test
其中 test.jar 和 test可以带相对路径。
🍇 jad编译class 到 java
参考博客:https://cloud.tencent.com/developer/article/2381140
下载jad;https://varaneckas.com/jad/
下载windows适配版:Jad 1.5.8g for Windows 9x/NT/2000 on Intel platform
下载后解压缩,有jad.exe 和 Readme.txt
将jad.exe放到要反编译文件的同级目录下,即反编译文件d:/test/project/classes 则jad.exe存在于d:/test/project/jad.exe
然后在路径处出入cmd,进入命令行;
执行命令:jad -8 -o -r -sjava -d src /**/*.class

sdk获取私网ip
Android:通过 java.net.NetworkInterface 轮询所有网卡,再过滤出非 loopback 且为 site-local 的地址 。
iOS:上层 App 一般调用 getifaddrs 或直接封装好的工具函数(如 getMobilePrivateIPAddress)一次性拿到当前活跃接口的私网地址,不再主动轮询 。
鸿蒙:应用侧通常不直接轮询网卡,而是借助 @ohos.net.connection 模块或 Wi-Fi SDK(getIpInfo 等接口)直接获取当前已连接的 Wi-Fi/蜂窝网络私网 IP,无需自己枚举网卡 。
局域网服务联调?
两台pc机,一台服务器,另一台客户端;
🍑 需要提供信息
提供服务端pc的ip,以及所启动服务的url和port;
🍑 问题1:
客户端不能访问服务器,总是显示超时;
通过修改防火墙配置,未解决;
【
进入入站规则管理
打开「Windows Defender 防火墙→高级设置→入站规则」,在规则列表中搜索 “8080” 端口相关的规则。
若没有任何允许 8080 端口的规则,需新建入站规则:
选择 “端口”→输入 “8080”→选择 “允许连接”→勾选 “专用” 网络(因你们处于局域网)→命名规则(如 “允许 8080 端口入站”)。
】

被折叠的 条评论
为什么被折叠?



