如何在WildFly的WAR包中使用OpenJDK JOL测量对象大小?
我们基于WildFly 26和OpenJDK 1.8开发Java EE应用,打包为EAR包(内含JSF的WAR模块),需要测量一组HttpSession对象的深度内存大小,但尝试多种方案均失败:
尝试的方案及错误信息
使用Java Object Layout (JOL)
代码:GraphLayout.parseInstance(sessions).totalSize());抛出异常:
java.lang.NoClassDefFoundError: Lorg/jboss/vfs/VirtualFile; at java.lang.Class.getDeclaredFields0(Native Method) [rt.jar:1.8.0_345] at java.lang.Class.privateGetDeclaredFields(Class.java:2583) [rt.jar:1.8.0_345] at java.lang.Class.getDeclaredFields(Class.java:1916) [rt.jar:1.8.0_345] at org.openjdk.jol.info.GraphWalker.getAllReferences(GraphWalker.java:128) at org.openjdk.jol.info.GraphWalker.walk(GraphWalker.java:96) at org.openjdk.jol.info.GraphLayout.parseInstance(GraphLayout.java:63) at <myClass> [..] Caused by: java.lang.ClassNotFoundException: org.jboss.vfs.VirtualFile from [Module "org.jboss.as.weld.ejb" version 26.1.1.Final from local module loader [..]添加容器模块依赖
尝试将org.jboss.vfs添加到org/jboss/as/weld/ejb模块后,又出现Infinispan相关类缺失的新异常。使用Heinz Kabutz的内存测量方案
同样抛出相同的类加载异常:Caused by: java.lang.NoClassDefFoundError: Lorg/jboss/vfs/VirtualFile; at java.lang.Class.getDeclaredFields0(Native Method) [rt.jar:1.8.0_345] at java.lang.Class.privateGetDeclaredFields(Class.java:2583) [rt.jar:1.8.0_345] at java.lang.Class.getDeclaredFields(Class.java:1916) [rt.jar:1.8.0_345] at MemoryCounter._estimate( [..]
1. 只测量Session中的业务属性(推荐)
WildFly的HttpSession实现包含大量容器内部对象(如VFS引用、Infinispan缓存实例),这些对象属于容器模块,应用类加载器无法访问。因此应避开容器内部对象,仅测量Session中存储的业务数据:
// 提取Session中的所有业务属性 Map<String, Object> sessionBusinessData = new HashMap<>(); Enumeration<String> attributeNames = session.getAttributeNames(); while (attributeNames.hasMoreElements()) { String name = attributeNames.nextElement(); sessionBusinessData.put(name, session.getAttribute(name)); } // 使用JOL测量业务数据的总内存大小 long totalSize = GraphLayout.parseInstance(sessionBusinessData).totalSize();
2. 利用WildFly内置JMX监控获取Session内存数据
无需编写代码,直接通过WildFly的JMX控制台获取Session内存统计:
- 启动WildFly后,使用
jconsole或其他JMX工具连接服务器 - 定位到
jboss.web域下的host=default-host,path=/<你的应用上下文路径>,subsystem=undertowMBean - 查看
sessionAverageSize、sessionMaxSize等属性,直接获取Session内存占用的统计值
3. 自定义反射逻辑,过滤容器内部类
如果必须测量完整Session对象,可修改反射遍历逻辑,过滤掉WildFly容器相关的类(如org.jboss.*、org.wildfly.*、org.infinispan.*)。以Heinz Kabutz的方案为例,修改核心测量方法:
private static long calculateSize(Object obj, Map<Object, Object> visited) { if (obj == null || visited.containsKey(obj)) { return 0; } Class<?> cls = obj.getClass(); // 过滤容器内部类,避免加载不可访问的类 if (cls.getName().matches("org\\.(jboss|wildfly|infinispan)\\..*")) { return 0; } visited.put(obj, null); long size = getClassSize(cls); // 遍历对象字段时跳过静态字段,并过滤容器类字段 for (Field field : cls.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; } field.setAccessible(true); Object fieldValue; try { fieldValue = field.get(obj); } catch (IllegalAccessException e) { continue; } if (field.getType().isArray()) { int length = Array.getLength(fieldValue); for (int i = 0; i < length; i++) { size += calculateSize(Array.get(fieldValue, i), visited); } } else { size += calculateSize(fieldValue, visited); } } return size; } // 原方案中的sizeOf方法,用于计算单个类的内存大小 private static long getClassSize(Class<?> cls) { // 保留原实现逻辑 }
4. 声明容器模块依赖(完整遍历Session时使用)
若需要完整遍历Session对象,可在应用的jboss-deployment-structure.xml中声明对所需容器模块的依赖,无需修改WildFly核心模块:
在EAR包的META-INF目录下创建jboss-deployment-structure.xml,内容如下:
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.vfs" export="true"/> <module name="org.infinispan.core" export="true"/> <module name="org.wildfly.clustering.web" export="true"/> <module name="org.jboss.as.web" export="true"/> </dependencies> </deployment> </jboss-deployment-structure>
该配置会让应用类加载器能够访问这些容器模块,解决类加载异常问题。
内容的提问来源于stack exchange,提问作者Alexander Rühl

