记Introspector.getBeanInfo()引起的Full GC
背景
开发环境压力测试,100并发,FullGC频繁,一个转换工具类BeanWithMapHandlerUtil
//把JavaBean转化为map public static Map<String, Object> beanToMap(Object bean) throws BaseAppException { Map<String, Object> map = new HashMap<>(); //获取JavaBean的描述器 BeanUtils.describe() <String, String> try { BeanInfo b = Introspector.getBeanInfo(bean.getClass(), Object.class); //获取属性描述器 PropertyDescriptor[] pds = b.getPropertyDescriptors(); //对属性迭代 for (PropertyDescriptor pd : pds) { //属性名称 String propertyName = pd.getName(); //属性值,用getter方法获取 Method m = pd.getReadMethod(); //用对象执行getter方法获得属性值 Object properValue = m.invoke(bean); // 判断是否有 list if (properValue instanceof java.util.List) { List list = (List) properValue; if (CollectionUtils.isNotEmpty(list)) { Class childClass = list.get(0).getClass(); if (isCommonDataType(childClass) || isWrapClass(childClass)) { map.put(propertyName, list); } else { List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>(); Object obj; for (int i = 0; i < list.size(); i++) { obj = list.get(i); // list里是map或String,不会存在list里直接是list的 Field[] fieldChilds = obj.getClass().getDeclaredFields(); Map<String, Object> resultChild = new HashMap<String, Object>(); for (Field field : fieldChilds) { // 重置属性可见(而且一般属性都是私有的),否则操作无效 boolean accessible2 = field.isAccessible(); if (!accessible2) { field.setAccessible(true); } // 获取属性名称及值存入Map String key = field.getName(); Object objVal = field.get(obj); if (null != objVal && !"".equals(objVal)) { resultChild.put(key, field.get(obj)); } } mapList.add(resultChild); } map.put(propertyName, mapList); } } } else { //把属性名-属性值 存到Map中 if (null != properValue && !"".equals(properValue)) { map.put(propertyName, properValue); } } } } catch (IllegalAccessException e) { throw new BaseAppException("beanToMap IllegalAccessException {}", e); } catch (IntrospectionException e) { throw new BaseAppException("beanToMap IntrospectionException {}", e); } catch (InvocationTargetException e) { throw new BaseAppException("beanToMap InvocationTargetException {}", e); } return map; }
通过thread -n 10 查看线程堆栈信息,线程处在阻塞状态
原因
"http-nio-8080-exec-44" Id=499 cpuUsage=3% BLOCKED on java.lang.Object@53793dcd owned by "http-nio-8080-exec-185" Id=640 at java.lang.ClassLoader.loadClass(ClassLoader.java:404) - blocked on java.lang.Object@53793dcd at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:103) at java.beans.Introspector.findCustomizerClass(Introspector.java:1301) at java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1295) at java.beans.Introspector.getBeanInfo(Introspector.java:425) at java.beans.Introspector.getBeanInfo(Introspector.java:262) at java.beans.Introspector.getBeanInfo(Introspector.java:224)
BeanInfo b = Introspector.getBeanInfo(bean.getClass(), Object.class);
可以看到java.lang.ClassLoader#loadClass(java.lang.String, boolean)中有一把synchronized锁,请求DTO转化成Map,并行变串行
解决方案
Map<String, Object> reqMa = JsonUtil.json2Map(JsonUtil.object2Json(bean));
Object转Map换成JsonUtil工具类,验证如下:
public static void main(String[] args) { RestInvocationDto req = new RestInvocationDto(); req.setServer("test"); req.setReqParams("test"); req.setConfigServerKey("test"); req.setBusinessUrl("test"); Thread threadA = new Thread(new Runnable() { @SneakyThrows @Override public void run() { Long begin = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { Map<String, Object> reqMap = BeanWithMapHandlerUtil.beanToMap(req); } System.out.println("ThreadA costs " + (System.currentTimeMillis() - begin) + "ms"); } }); Thread threadB = new Thread(new Runnable() { @SneakyThrows @Override public void run() { Long begin = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { JsonUtil.json2Map(JsonUtil.object2Json(req)); } System.out.println("ThreadB costs " + (System.currentTimeMillis() - begin) + "ms"); } }); threadA.start(); threadB.start(); }
ThreadB costs 2321ms
ThreadA costs 8312ms
不积跬步,无以至千里;不积小流,无以成江海
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2018-09-28 第五章 Java中锁