排查JVM异常与JProfiler分析Dump记录

77

添加参数

-Djava.rmi.server.hostname=192.168.129.129 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.authenticate=false -
Dcom.sun.management.jmxremote.ssl=false

参数解析

-Djava.rmi.server.hostname=192.168.129.129 #被监控服务器ip
-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=1099 #开放连接的端口
-Dcom.sun.management.jmxremote.authenticate=false  #关闭认证
-Dcom.sun.management.jmxremote.ssl=false #关闭SSL校验



#保存GC日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -Xloggc:/jvm-gc-logs/gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=20M

#保存OOM日志
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/jvm-oom-logs/ -XX:ErrorFile=/jvm-oom-logs/hs_err.log

实战:

最近公司生产环境服务出现运行时间久系统卡顿的问题,每次重启完流畅度就恢复,CPU内存占用未见异常,观察Grafana发现gc比较频繁,jvm内存增加较快,尝试导出Dump快照通过JProfiler查看分析发现惊天大坑

JProfiler启动

jprofiler_2026-01-15_10-40-35.png

发现端倪,char[]高达1230MB的占用😁👍

jprofiler_2026-01-15_10-56-52.png

切换最大对象查看来自哪个类

jprofiler_2026-01-15_10-57-25.png

前往项目查找,发现以下为某离职同事前作,貌似没有什么问题

public class FilterPath {
    private static StringBuffer PATH_LISTS = new StringBuffer();

    public static String getPathLists(){
            String name = "hamp_point_end";
            PATH_LISTS.append("/"+name+"/login");
            PATH_LISTS.append("/"+name+"/favicon.ico");
            PATH_LISTS.append("/"+name+"/swagger");
            PATH_LISTS.append("/"+name+"/configuration");
            PATH_LISTS.append("/"+name+"/images");
            PATH_LISTS.append("/"+name+"/v2/api-docs");
            PATH_LISTS.append("/"+name+"/BaseUser/selectByUserName");
            PATH_LISTS.append("/"+name+"/BaseUserSecurity/getSecurityNotSession");
            PATH_LISTS.append("/"+name+"/BaseSendSmsController/sendCodeByAccount");
            PATH_LISTS.append("/"+name+"/BaseSendSmsController/compareCode");
            PATH_LISTS.append("/"+name+"/BaseUser/test");
            PATH_LISTS.append("/"+name+"/BaseDingAPIController/test");
            PATH_LISTS.append("/"+name+"/as/alarmRealHisTime/test");
            PATH_LISTS.append("/"+name+"/auth");
            PATH_LISTS.append("/"+name+"/ServerController/server");
            PATH_LISTS.append("/"+name+"/BaseUserSecurity/judgeSecurity");
            PATH_LISTS.append("/"+name+"/BaseConfigController/selectByDoor");
            PATH_LISTS.append("/"+name+"/BaseUser/selectByAccountToBaseUser");
            PATH_LISTS.append("/"+name+"/BaseUser/updateByAccount");
            PATH_LISTS.append("/"+name+"/BaseRoleController/selectByRid");
            PATH_LISTS.append("/"+name+"/captcha");
            PATH_LISTS.append("/"+name+"/verifyCaptcha");
        return PATH_LISTS.toString();
    }
}

查看此类方法引用💩,小刀拉屁股开了眼,每次接口请求都调用一次此方法重新增加路径并返回静态StringBuffer.....

@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private BaseUserMapper userMapper;
    @Autowired
    private BaseAccessFeatureMapper baseAccessFeatureMapper;
    @Autowired
    private RedisService redisService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if("10.25.20.188".equals(request.getRemoteAddr())){
            chain.doFilter(request, response);
        }else{

            String tenantId = request.getHeader(Constants.TENANT_ID);
            if(tenantId!=null && !"".equals(tenantId)){
                TenantContextHolder.setTenantId(tenantId);
            }else{
                TenantContextHolder.setTenantId("0");
            }
            String token = request.getHeader("token");//获取header中的验证信息
            String roleId = ((String)redisService.get("roleId"+token));
            String uid = ((String)redisService.get(token));
            System.out.println("******查询*********"+request.getRemoteAddr()+tenantId);
            System.out.println("******查询*********"+"token:"+token+"uid:"+uid+"roleId:"+roleId);
            if("superAdmin".equals(token)||request.getRequestURI().indexOf("login")!=-1 || request.getRequestURI().indexOf("favicon.ico")!=-1 || request.getRequestURI().indexOf("swagger")!=-1 ||
                    request.getRequestURI().indexOf("configuration")!=-1 || request.getRequestURI().indexOf("images")!=-1 || request.getRequestURI().indexOf("/v2/api-docs")!=-1
                    || FilterPath.getPathLists().indexOf(request.getRequestURI()) != -1 || request.getRequestURI().indexOf("druid")!=-1 ||request.getRequestURI().indexOf("webSocket")!=-1){
                BaseUser baseUser = new BaseUser();
                if (roleId != null && !"".equals(roleId)) {
                    baseUser.setRoles((String) redisService.get(roleId.substring(7)));
                }
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(baseUser, null, baseUser.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                //设置为已登录
                SecurityContextHolder.getContext().setAuthentication(authentication);
                chain.doFilter(request, response);
                return;
            } else {
....

屎上雕花,修改成初始化首次加载返回固定静态StringBuffer😁👍

@Component
public class FilterPath {
    private static final Logger logger = LoggerFactory.getLogger(FilterPath.class);
    private static StringBuffer PATH_LISTS = new StringBuffer();

    public static String getPathLists() {
        return PATH_LISTS.toString();
    }

    @PostConstruct
    public void setPathLists() {
        logger.info("加载匿名接口到缓存中................");
        String name = "hamp_point_end";
        PATH_LISTS.append("/" + name + "/login");
        PATH_LISTS.append("/" + name + "/favicon.ico");
        PATH_LISTS.append("/" + name + "/swagger");
        PATH_LISTS.append("/" + name + "/configuration");
        PATH_LISTS.append("/" + name + "/images");
        PATH_LISTS.append("/" + name + "/v2/api-docs");
    }
}