深入理解类加载机制
HSDB工具的使用
Hotspot Debugger(HSDB):JDK原生自带
以Windows系统为例,jdk8的环境,在jdk的lib目录下,启动之前,你需要确保你进入的lib目录和你当前的JAVA_HOME配置的JDK是相同的,否则可能会出现无法加载libarary的异常,进而无法使用HSDB,命令如下
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
调节字体大小的方法,添加环境变量JAVA_TOOL_OPTIONS
-Dswing.plaf.metal.controlFont=Dialog-22 -Dswing.plaf.metal.systemFont=Dialog-22 -Dswing.plaf.metal.userFont=SansSerif-22
这里需要用attach到一个java进程,
利用jps可以查看到相关指令
将进程号输入进去,我这里换了一个程序,进程号不同
会看到运行的Java Thread
查看main线程的调用栈
静态字段如何存储
instanceKlass
instanceMirrorKlass
Test_1_A
静态变量str的值存放在StringTable中,镜像类中存放的是字符串的指针
Test_1_B
str是类Test_1_A的静态属性,可以看到不会存储子类Test_1_B的镜像类中。
可以猜得到,通过子类Test_1_B访问父类Test_1_A的静态字段有两种实现方式:
- 1.先去Test_1_B的镜像类中去取,如果有直接返回;如果没有,会沿着继承链将请求上跑。很明显,这种算法的性能随继承链的depth而上升,算法复杂度为O(n).
- 2.借助另外的数据结构实现,使用K-V的格式存储,查询性能为O(1)
Hotspot就是使用的第二种方式,借助另外的数据结构ConstantPoolCache,常量池类ConstantPool中有个属性_cache指向了这个结构。每一条数据对应一个类ConstantPoolCacheEntry.
ConstantPoolCacheEntry在哪儿?在CosntantPoolCache对象后面,代码位置/openjdk/hotspot/src/share/vm/oops/cpCache.hpp,代码如图所示。这个公式的意思是ConstantPoolCache对象的地址加上ConstantPoolCache对象的内存大小
ConstantPoolCache
常量池缓存是为常量池预留的运行时数据结构。保证所有字段访问和调用字节码的解释器运行时信息。缓存是在类被积极使用之前创建和初始化的。每个缓存项在解析时被填充。从图中的代码可以看出,是直接去获取ConstantPoolCacheEntry
类解析的过程ClassFileParser.cpp中的parseClassFile方法
1.魔数验证
2.版本号验证
3.解析常量池、访问权限等等
4.解析类的接口信息、方法信息、属性信息、父类信息
5.创建位于方法区的InstanceKlass对象
6.创建位于堆中的InstanceMirrorClass对象
疑惑
为什么HotSpot将BootstrapClassLoader使用C++语言编写而ExtClassLoader和AppClassLoader用java语言编写
HotSpot JVM将BootstrapClassLoader用C++语言比那些,而ExtClassLoader和AppClassLoader用Java语言编写,主要是处于以下几个原因:
1.引导类加载器(BootstrapClassLoader)
核心系统组件:BootstrapClassLoader是JVM的核心部分,负责加载Java核心库(如java.lang.*、java.util.*等)。这些核心库在JVM启动时必须
被加载,并且它们的加载过程必须在任何Java代码执行之前完成。
依赖关系:由于BootstrapClassLoader加载的是最基础的Java类库,它不能已离开于任何Java嘞,否则,会陷入加载循环(依赖的类还未加载,需要先加载依赖类)
性能和控制:使用C++编写可以更直接地控制内存和资源,提高性能和启动速度
2.扩展类加载器(ExtClassLoader)和应用类加载器(AppClassLoader)
实现简便:ExtClassLoader和AppClassLoader主要是加载扩展库和应用程序嘞,它们不需要像BootstrapClassLoader那样处理JVM启动的核心部分。因此可以用
Java语言编写,利用java自身的特性,使得代码更简洁和易维护
继承和扩展:用Java编写可以更方便地利用Java的面向对象特性,继承和扩展ClassLoader嘞,从而更容易地实现自定义类加载器。
运行时环境:在JVM启动之后,ExtClassLoader和AppClassLoader运行已经初始化的Java环境中,不需要担心加载器本身的类是否已经加载
总结:
BootStrapClassLoader使用C++编写,确保JVM在启动时能够加载最核心的类库,并且不依赖于任何Java类,避免循环依赖,同事提高性能和控制性
ExtClassLoader和AppClassLoader使用Java语言编写,方便实现和扩展,同时在JVM启动后利用现有的Java环境和面向对象特性,更易于维护和扩展。
这种设计确保了JVM的启动和运行时类加载机制既高效又灵活,能够满足不同阶段的需求
Tomcat为什么要打破双亲委派机制
我们知道,Java默认的类加载机制是通过双亲委派模型来实现的,而Tomcat实现的方式又和双亲委派模型有所区别。原因在于一个Tomcat容器允许同时运行多个Web程序,每个Web程序依赖的类又必须是相互隔离的。因此,如果Tomcat使用双亲委派模式来加载类的话,将导致Web程序依赖的类变为共享的。
举个例子,假如我们有两个Web程序,一个依赖A库的1.0版本,另一个依赖A库的2.0版本,它们都使用了类xxx,其实现的逻辑因类库版本的不同而结构完全不同。那么这两个Web程序的其中一个必然因为加载的Class不是所使用的Class而出现问题!而这对于开发来说是非常致命的
完整的的Tomcat类加载图。我们在这张图中看到很多类加载器,除了JDK自带的类加载器,我们尤其关心Tomcat自身持有的类加载器。仔细一点我们很容易发现:Catalina类加载器和Shared类加载器,它们并不是父子关系。为啥这样设计,我们得分析一下每个类加载的用途,才能知晓。
1.Common类加载器,负责加载Tomcat和Web应用都服用哦个的累
1.1 Catalina类加载器,负责加载Tomcat专用的类,而这些被加载的类在Web应用中将不可见
1.2 Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见
1.2.1 WebApp类加载器,负责加载具体的某个Web应用程序所使用到的类,而这些被加载的类在Tomcat和其他的Web应用程序都将不可见(每一个Web应用程序对应一个WebApp类加载器)
1.2.2 Jsp类加载器,每个jsp页面一个类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热拔插