深入理解java虚拟机精华总结:性能监控和故障处理工具、类加载机制
- 性能监控和故障处理工具、类加载机制
- jps
- jstat
- jinfo
- jmap
- jhat
- jstack
- VisualVM
- 类加载机制
- 类加载的时机
- 类加载的过程
- 加载
- 验证
- 准备
- 解析
- 初始化
- 类加载器
- 类与类加载器
- 双亲委派模型
- 破坏双亲委派模型
往期内容:
- 深入理解java虚拟机精华总结:jvm内存模型(运行时数据区域)、对象、OOM异常
- 深入理解java虚拟机精华总结:如何判断对象是否可回收、引用、finalize、方法区回收、垃圾收集算法、垃圾收集器、内存分配与回收策略
性能监控和故障处理工具、类加载机制
jps
可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)。
jstat
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程[1]虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,在没有GUI图形界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。
jinfo
是实时查看和调整虚拟机各项参数。
jmap
- 生成堆转储快照(一般称为heapdump或dump文件)。
- 查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前用的是哪种收集器等。
jhat
与jmap搭配使用,来分析jmap生成的堆转储快照。
jstack
用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。
VisualVM
- 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
- 监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)。
- dump以及分析堆转储快照(jmap、jhat)。
- 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法。
- 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快照发送开发者处进行Bug反馈。
- 其他插件带来的无限可能性。
类加载机制
类加载的时机
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。
- 使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。
- 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 虚拟机启动时,main()方法所在类。
- 接口中定义了默认方法时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
类加载的过程
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为连接(Linking)。
加载
在加载阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
为静态变量分配内存并设置初始值。
解析
将常量池内的符号引用替换为直接引用。
- 符号引用(Symbolic References):以一组符号来描述所引用的目标。
- 直接引用(Direct References):直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化
执行类构造器<clinit>()方法。
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的。
类加载器
类与类加载器
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
双亲委派模型
- 启动类加载器(Bootstrap ClassLoader):负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的。
- 扩展类加载器(Extension ClassLoader):它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。
- 应用程序类加载器(Application Class Loader):加载用户类路径(ClassPath)上所有的类库。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载
器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
破坏双亲委派模型
- 子类重写loadClass()方法:双亲委派的具体逻辑就实现在loadClass()方法中。
- 线程上下文类加载器:处理基础类型要调用回用户的代码的情况,增加了线程上下文类加载器。JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为。
- 代码热替换、模块热部署。