一、JDK的命令行工具
-
jps:显示系统内所有HotSpot虚拟机进程。
jps工具主要选项.png
-
jstat:用于收集HotSpot虚拟机各方面的运行数据(类装载、内存、垃圾收集、JIT编译等运行数据)。
jstat工具主要选项.png
-
jinfo:显示虚拟机的配置信息。
-
jmap:生成虚拟机的内存转储快照(heapdump文件)。
jmap工具主要选项.png
-
jhat:用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器查看分析结果。
-
jstatck:生成虚拟机当前时刻的线程快照。生成快照的目的是定位线程出现长时间停顿的原因。
jstack工具主要选项.png
二、虚拟机的类加载机制
-
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机所直接使用的Java类型,这就是虚拟机的类加载机制。
(1)在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,Java天生可动态扩展的特性依赖于动态加载和动态连接这个特点。
(2)每个Class文件(一串二进制的字节流文件,可以以任何形式存在,不一定是文件形式)都带着Java语言中的一个类或者接口。 -
类加载的过程
类的生命周期.png
(1)加载。在加载阶段虚拟机需要完成以下3件事情:
①通过一个类的全限定名来获取定义此类的二进制字节流。
②将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
③在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据结构的访问入口。
(2)验证。确保Class文件的字节流信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。需完成4个阶段的检验过程:
①文件格式验证。
②元数据验证。
③字节码验证。
④符号引用验证。
(3)准备。正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存将在方法区中进行分配。这里的初始值通常指数据类型的0值。如int value=123;在准备阶段后value值为0,在初始化阶段才将该值赋值为123。在final int value的情况下,在初始阶段value为123。
(4)解析。虚拟机将常量池内的符号引用替换为直接引用的过程。
①符号引用:以一组符号来描述引用的目标。
②直接引用:直接指目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
(5)初始化。在这个阶段,开始真正执行类中定义的Java代码。
-
类加载器
(1)虚拟机把类加载过程中"通过一个全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器。
(2)对于任意一个类,都需要由加载它的类加载器和它本身一同确立其在虚拟机中的唯一性。同一个类,若加载它的类加载器不同,则他们不相等。 -
双亲委派模型
工作过程:如果一个类加载器收到了类加载的请求,它首先不会尝试加载这个类,而会把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器无法完成这个请求,子类加载器才会尝试去加载。
类加载器的双亲委派模型.png
三、虚拟机字节码执行引擎
-
Java虚拟机的执行引擎:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。
-
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作栈、动态连接、返回地址等信息。每一个方法从调用开始至执行的过程,都对应这一个栈帧在虚拟机中从入栈到出栈的过程。对于执行引擎来说,在活动线程中只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
(1)局部变量表:用于存放方法参数和方法内部定义的局部变量。局部变量定义了但是没有赋初值是不能使用的。
(2)操作栈:当一个方法刚执行时,它的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是入栈、出栈操作。如算术运算是通过操作数栈来进行的,又如调用其他方法是通过操作数栈来传递参数的。
(3)动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属的方法的引用,持有这个引用是为了支持方法调用过程的动态连接。字节码中的方法调用指令以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用时转化为直接引用。这种转化称为静态解析。另一部分在每一次运行期间转化为直接引用,这部分称为动态连接。
(4)返回地址:一般来说方法正常退出,调用者的PC计数器的值可以作为返回地址,栈帧中可能会保留这个计数器值。异常退出时,返回地址是通过异常处理器表来确定的,栈帧中一般不会保留此部分信息。
①正常完成出口:执行引擎遇到任意一个方法返回的字节码指令。
②异常完成出口:在方法执行过程中遇到了异常,并且在本方法的异常表中没有匹配的异常处理器,导致方法退出。
(5)附加信息 -
方法调用
方法调用不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本,一切方法调用在Class文件中存储的都只是一个常量池中的符号引用,而不是在实际运行时内存布局的入口地址(相当于之前说的直接引用),需要在类加载期间,甚至运行期间才能确定目标方法的直接引用。
(1)解析
解析调用是个静态的过程,在编译期间就完全确定,在类加载阶段会将涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期在去完成。符合“编译器可知,运行期不可变”的方法有静态方法、私有方法、实例构造器、父类方法、final方法。这些方法可称为非虚方法。
(2)分派
方法分配主要有以下四种:静态单分配、静态多分配、动态单分配、动态多分配。
①静态分派
Human man = new Man();
在上面的代码中,Human为静态类型,在编译时可知。Man为实际类型,在运行时才确定。重载是通过参数的静态类型而不是实际类型作为依据的。所有依赖静态类型来定位方法执行版本的分派动作称为静态分配。典型应用是重载。
②动态分派
java重写的本质:invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上。运行时根据实际类型确定方法执行版本的分派过程称为动态分派。应用有重写。
java是一门静态多分派、动态单分派的语言。
③优化
稳定优化手段:为类在方法区中建立一个虚方法表,使用虚方法表索引代替元数据查找以提高性能。方法表一般在类加载的连接阶段进行初始化。
激进优化:内联缓存、守护内联。
(3)动态语言支持
①动态语言特征:类型检查的主体过程实在运行期而不是编译期。变量无类型而变量值才有类型。
②Reflection和MethodHandle
它们的机制都是在模仿方法调用,Reflection是模仿Java代码层次的方法调用,而MethodHandle模仿字节码层次的方法调用。Reflection包含的信息更多(方法签名、描述符、属性表、执行权限等信息),MethodHandle仅包含与执行该方法相关的信息。即Reflection是重量级,MethodHandle是轻量级。MethodHandle可服务于运行于JVM上的所有语言。Reflection只适用于Java。
JDK1.7引入了第五条方法调用的字节码指令invokedynamic。它与MethodHandle机制的作用是一样的,它们的目的都是把如何查找目标方法的决定权由JVM转嫁到具体用户代码中。一个用字节码和Class中的其他属性、常量完成,另一个用上层代码和API实现。 -
基于栈的字节码解释执行引擎
Java语言中,Java编译器完成了程序源码经过词法分析、语法分析到抽象语法数,再遍历语法树生成线性的字节码指令流的过程。这部分在Java虚拟机的外部,而解释器在Java虚拟机的内部,即Java程序的编译是半独立的实现。
编译过程.png
Java编译器输出的指令流基本上是基于栈的指令集结构,大部分是零地址指令,依赖操作数栈进行工作。
优点:可移植、代码更加紧凑(字节码每个字节就对应一条指令)、编译器实现更加简单(不需要考虑空间分配问题)。
缺点:指令数量多、内存访问频繁、执行速度慢。
四、类加载
-
Tomcat5.x:正统的类加载架构
Tomcat5.x服务器的类加载架构.png
-
OSGI:灵活的类加载器架构
示例:
①BundleA:发布了packageA,依赖java.*
①BundleB:依赖packageA、依赖packageC和依赖java.*
①BundleC:发布了packageC,依赖packageA
OSGI类加载器架构.png
OSGI每个模块(Bundle)可以声明它所依赖的Java Package,也可以声明它允许导出发布的Java Package。
OSGI的Bundle只有规则,没有固定的委派关系。加载器之间的关系不再是双亲委派模型的树型结构,它发展成一种更为复杂、运行时才能确定的网状结构。
优点:提供了更精细的模块划分和可见性控制,并且还可能实现模块级的热插拔功能。
缺点:引入了额外的复杂度,带来了线程死锁和内存泄漏的风险。
JDK1.7,为非树状继承关系下的类加载器架构进行了一次专门升级,目的是从底层避免死锁出现的可能性。
- Retrotranslator:跨越JDK版本
JDKmeic
网友评论