Java类加载机制及关于时序数据库IoTDB排查
排查时序数据库 [IOTDB-4899] [UDF] develop UDF class with Enum, return 500 when querying - ASF JIRA 时,涉及的 Java 类加载机制知识如下:
一、类加载概述
类加载是指将类的.class
文件中的二进制数据读入内存,并创建java.lang.Class
对象的过程。该对象封装了类在方法区的数据结构,并提供访问接口。类加载器在预料类将被使用时,可预先加载,若.class文件缺失或错误,则在首次主动使用时报告LinkageError
错误。
加载阶段,虚拟机需完成以下任务:
- 通过类的全限定名获取二进制字节流。
- 将字节流转换为方法区的运行时数据结构。
- 在Java堆中创建代表该类的
java.lang.Class
对象。
二、类加载机制
1. 双亲委派机制
双亲委派机制是指类加载器收到加载请求时,先委托给父加载器处理。请求依次向上传递,直至启动类加载器。若父加载器无法加载,子加载器才尝试加载。类加载器层次关系如下:
- 启动类加载器:加载
$JAVA_HOME\jre\lib
下或-Xbootclasspath
指定的类库(如rt.jar
)。 - 扩展类加载器:加载
$JAVA_HOME\jre\lib\ext
目录或由java.ext.dirs
指定的类库。 - 应用程序类加载器:加载用户类路径(ClassPath)指定的类。
- 自定义类加载器:用于加载非标准Java类文件。
2. 缓存机制
缓存机制确保所有加载过的类被缓存。当需要使用类时,类加载器先从缓存区查找。修改类后,需重启JVM才能使修改生效。
三、问题排查
1. 问题背景
创建UDF(用户定义函数)成功,但执行时报错,找不到org.apache.iotdb.udf.MySum$1
类。
2. 问题分析
2.1 org.apache.iotdb.udf.MySum$1
类解析
该类名似匿名类。检查创建UDF的jar包,发现包含MySum$1
类,但实际项目中只有MySum
类。结合日志,报错位置在switch代码块,经查证,JVM在switch enum中case数量过多时,会编译出匿名类。
2.2 类加载器分析
结合类加载的全盘负责机制,匿名类由加载其依赖的org.apache.iotdb.MySum
的类加载器A加载。A能成功加载org.apache.iotdb.udf.MySum
,但无法加载org.apache.iotdb.udf.MySum$1
。原因可能是A被关闭。
排查代码发现,使用try-with-resource
语句自动关闭类加载器A,导致后续无法加载匿名类。
2.3 ClassLoader.close()
方法分析
URLClassLoader在被close后无法加载新类或资源,但已加载的类和资源仍可访问。
2.4 setContextClassLoader
方法分析
setContextClassLoader
用于打破双亲委派机制。初步误以为是未正确设置ContextClassLoader导致问题,但经实验和查阅资料,确认应按全盘负责机制理解,setContextClassLoader
不起作用。
3. 解决方案
- 避免关闭类加载器:若可能,避免关闭用于加载UDF的类加载器,或确保在需要加载所有相关类后再关闭。
- 优化代码结构:减少不必要的类加载器使用,优化代码结构,确保资源有效管理。
4. 类卸载时机
类满足以下条件时可能被卸载:
- 类所有实例被回收。
- 加载类的ClassLoader被回收。
- 类对应的
java.lang.Class
对象无引用。
注意:ClassLoader不提供卸载类的接口,需等待JVM自动卸载。