现象
实习生对新开发的推理接口进行压测时,发现tp99异常,大概在9000ms左右 
                
young gc也十分频繁,还伴随着2次的full gc 
                
cpu使用率也飙升到了100% 
                
问题
通过jstask发现大量线程处于CaseFormat.convert方法中1
2
3
4
5
6
7
8
9
10
11
12
13"JSF-BZ-22000-23-T-110" #247 daemon prio=5 os_prio=0 tid=0x00007fb0d8110800 nid=0x527 runnable [0x00007faf366ed000]
   java.lang.Thread.State: RUNNABLE
	at java.util.Arrays.copyOfRange(Arrays.java:3664)
	at java.lang.String.<init>(String.java:207)
	at java.lang.StringBuilder.toString(StringBuilder.java:407)
	at com.google.common.base.CaseFormat.convert(CaseFormat.java:150)
	at com.google.common.base.CaseFormat.to(CaseFormat.java:128)
	at com.jd.jdl.sj.acr.utils.JDBCUtils.Populate(JDBCUtils.java:184)
	at com.jd.jdl.sj.acr.mapper.RecommendedMapper.queryByAssociateData(RecommendedMapper.java:87)
	at com.jd.jdl.sj.acr.algo.AcrAlgoStrategyA.predict(AcrAlgoStrategyA.java:73)
	at com.jd.jdl.sj.acr.algo.AcrAlgoStrategyB.predict(AcrAlgoStrategyB.java:56)
	at com.jd.jdl.sj.predict.service.impl.AlgoServiceImpl.predict(AlgoServiceImpl.java:26)
	at com.jd.jdl.sj.predict.service.impl.AlgoServiceImpl$$FastClassBySpringCGLIB$$211fefd6.invoke(<generated>)
且jmap中发现jvm内存中存在大量String对象 
                
最终定位到问题代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public static List Populate(ResultSet rs, Class cc) throws SQLException, InstantiationException, IllegalAccessException {
    try {
        //结果集 中列的名称和类型的信息
        ResultSetMetaData rsm = rs.getMetaData();
        int colNumber = rsm.getColumnCount();
        List res = new ArrayList();
        Field[] fields = cc.getDeclaredFields();
        //遍历每条记录
        while (rs.next()) {
            //实例化对象
            Object obj = cc.newInstance();
            //取出每一个字段进行赋值
            for (int i = 1; i <= colNumber; i++) {
                Object value = rs.getObject(i);
                //匹配实体类中对应的属性
                for (Field f : fields) {
                    // 驼峰转下划线命名
                    String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, f.getName());
                    if (name.equals(rsm.getColumnName(i))) {
                        boolean flag = f.isAccessible();
                        f.setAccessible(true);
                        f.set(obj, value);
                        f.setAccessible(flag);
                        break;
                    }
                }
            }
            res.add(obj);
        }
        return res;
    } catch (Exception e) {
        throw new BusinessException();
    }
}
这段代码是通过jdbc查询结果后,需要将ResultSet映射成pojo中的结果。
主要问题出在这行驼峰转下划线的代码中:1
String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, f.getName());
查询一次数据库大概返回400行数据,每条数据25个字段,一次查询就会执行2.5w次这行代码,创建出2.5w个String。
解决方案
将驼峰转下划线前置,这样一次调用只会执行25次这个方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public static List Populate(ResultSet rs, Class cc) throws SQLException, InstantiationException, IllegalAccessException {
    try {
        List res = new ArrayList();
        Field[] fields = cc.getDeclaredFields();
        Map<String, Field> fieldMap = new HashMap<>(fields.length);
        for (Field field : fields) {
            String fieldUnderscoreName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
            fieldMap.put(fieldUnderscoreName, field);
        }
        //结果集 中列的名称和类型的信息
        ResultSetMetaData rsm = rs.getMetaData();
        //遍历每条记录
        while (rs.next()) {
            //实例化对象
            Object obj = cc.newInstance();
            //取出每一个字段进行赋值
            for (int i = 1; i <= rsm.getColumnCount(); i++) {
                Object value = rs.getObject(i);
                String columnName = rsm.getColumnName(i);
                Field field = fieldMap.get(columnName);
                if (field == null) {
                    continue;
                }
                //                    boolean flag = field.isAccessible();
                field.setAccessible(true);
                field.set(obj, value);
                //                    field.setAccessible(flag);
            }
            res.add(obj);
        }
        return res;
    } catch (Exception e) {
        throw new BusinessException();
    }
}
压测结果性能大幅提升,tp99在20ms以下。 
                
