SpringBoot服务问题学习

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 端口入站”)。
】

最后

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值