JVM学习篇(2)之类相关内容

来源:互联网 发布:南京软件 外包公司 编辑:程序博客网 时间:2024/04/30 08:49

类文件结构

Class文件结构

1.        魔数与Class文件的版本

魔数:确定这个文件是否为虚拟机所能接受的Class文件。

2.        常量池:

1)        主要存放字面变量和符号引用

2)        字面变量:常量

3)        符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

3.        访问标志

1)        作用:用于识别一些类和接口层次的访问信息

2)        内容:

a)        是否是接口

b)        是否定义为public类型

c)        是否定义为abstract类型

d)        如果是类的话,是否被声明为final

4.        类索引、父类索引与接口索引集合

5.        字段表集合

用于描述接口或者类中声明的变量。变量指的是成员变量和静态变量。

6.        方法表集合

7.        属性表集合

虚拟机字节码执行引擎


运行时栈帧结构

1.        组成:

1)        局部变量表:用来存放方法参数和方法内部定义的局部变量。

2)        操作数栈

3)        动态链接

4)        方法返回值

2.        特点:在编译时确定大小,故栈帧需要分配多少内存不会受到程序运行期变量数据的影响,仅仅取决于具体的虚拟机实现。

3.        局部变量表

Ø  局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指出一个slot应占用的内存空间大小

Ø  JVM通过索引定位的方式使用局部变量表,索引值的范围从0开始到局部变量表最大的SLOT数量

Ø  JVM使用局部变量表完成参数值到参数变量列表的传递过程

Ø  果是实例方法,那么局部变量表的第0位索引的SLOT默认是用于传递方法所属对象实例的引用,在方法中可以通过“this"访问到这个隐含的参数。

Ø  局部变量表中的SLOT是可以重用的,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那么这个变量对应的SLOT就可以交给其他变量使用

4.        操作数栈

Ø  当一个方法开始执行时,这个方法的操作数栈是空的,在方法执行过程中,会有各种字节码指令向操作数栈中写入和提取内容。

Ø  在编译器和校验阶段的保证下,操作数栈中元素的数据类型必须与字节码指令的序列严格匹配

5.        动态链接:(符号引用转换为直接引用)

1)        静态解析:在类加载阶段或者第一次使用的时候转化为直接引用。

2)        动态链接:在每一次运行期间转化为直接引用。

6.        方法返回地址:

1)        方法返回的方式:

Ø  正常完成出口:执行引擎执行一个方法的返回字节码指令。

Ø  异常完成出口:方法执行时遇到异常,并且在本方法的异常表中没有搜索到匹配的异常处理器。

2)        退出时执行的操作:

Ø  恢复上层方法的局部变量和操作数栈

Ø  把返回值压入栈中

Ø  调用PC计数器的值以指向下一条字节码指令

方法的调用

1.        作用:唯一作用(目标)就是确定被调用方法的版本,不涉及内部具体运行过程。(在Class文件里一切方法的调用存储的都只是符号引用)即将符号应用转变为直接引用

2.        字节码调用指令

1)        Invokestatic:调用静态方法

2)        Invokespecial:调用构造器方法、私有方法和父类方法

3)        Invokevirtual:调用所有的虚方法

4)        Invockeinterface:调用接口方法

5)        Invockedynamic:现在运行时动态解析出调用点父所引用的方法,然后在执行该方法。

3.        解析调用:

1)        转换的时机:解析调用是一个静态的过程,在编译期间完全确定。即在类装载的解析阶段就会把涉及的引用转化为直接引用。

2)        条件:编译期可知,运行期不变。

3)        涉及的方法:

Ø  虚方法:被invokestatic和Invokespecial指令调用的方法,包括静态方法、私有方法、实例构造器和父类方法

Ø  非虚方法:被final修饰的方法。由于无法被覆盖,所以没有其他版本。

4.        分派调用

Human man = new Man();

Human称为变量的静态类型,Man称为变量的实际类型。

区别:

静态类型不会被改变,并且最终的静态类型是在编译期可知。

实际类型变化的结果在运行期才可确定,即在编译器编译阶段并不知道一个对象的实际类型是什么。

1)        静态分派:

Ø  依赖静态类型来定位方法执行版本的分派动作成为静态分派

Ø  典型应用:方法的重载

Ø  发生的阶段:编译阶段

小结:虚拟机在重载时时通过参数的静态类型而不是实际类型作为判断依据的。在编译阶段Javac编译器会根据参数的静态类型决定使用哪个重载版本。

2)        动态分派:

Ø  在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

Ø  典型应用:方法的重写

Ø  发生阶段:运行阶段

Ø  确定过程:Invokevirtual指令的多态查找过程:

a)        找到操作数栈顶的第一个元素所指向对象的实际类型,记作C。

b)        如果类型C的常量池中找到与常量中的描述符和简单名称都相符的方法,则进行权限校验

Ø  通过:返回这个方法的直接引用

Ø  不通过:返回IllegalAccessError异常

c)        未找到,按照继承关系从上到下依次对C的各个父类进行b)的搜索和校验过程。

d)        若始终没有找到合适的方法,则抛出异常。

小结:由于Invokevirtual指令执行的第一步就是在运行期确定接受者的实际类型,故Invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上。

3)        单分派和多分派

静态分派:

Ø  在编译时发生,根据变量的静态类型确定。

Ø  通过方法调用者的静态类型、方法参数的静态类型这两个来确定使用的方法

Ø  由上可知,静态分派属于多分派

动态分派:

Ø  在运行时发生

Ø  所使用的方法已经在静态分派时确定,所以在运行时(动态分派)需要确定方法调用者的实际类型。

Ø  动态分派属于 类型。

4)        虚拟机动态方法的实现


Ø  使用方法表的索引来代替元数据查找以提高性能,虚方法表中存放着各个方法的实际入口地址

Ø  若某个方法在子类中没有被重新,那子类的虚方法表里面的地址入口和父类方法的地址入口一致。即子类方法表的索引指向父类方法。

Ø  若某个方法在子类中被重新,那子类的虚方法表里面的地址入口指向自己的方法入口。

Ø  具有相同签名的方法,在父类、子类的虚方法表中具有一样的索引号。这样当类型变换时,仅需要变更查找的方法即可。

基于栈的字节码解释执行引擎

如何执行方法中的字节码指令


基于栈的Hotspot的执行过程如下:


类的加载

类的加载、连接与初始化

1.        加载:负责将Class文件加载到内存中,并返回与之对应的Class对象。

2.        连接:

1)        验证:确保被加载的类的正确性

2)        准备:为【静态变量】分配内存,并将其初始化为默认值

3)        解析:把类中的符号引用转为直接引用

3.        初始化:为类中【静态变量】赋予正确的初值


加载:

1.        加载方式:主动加载和被动加载

2.        类的加载:指的是

1)        将类的.Class文件中的二进制数据读入到内存中,并将其放到运行时数据区的方法区内。

在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构

 

注:类加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口

2)         


类加载器

1.    Java虚拟机自带3种加载器:

1)    根类加载器:用来加载Java核心类,如:java.lang.*

2)    扩展类加载器:用来加载jre路径中的class文件

3)        系统类加载器:又来加载用户自定义的类,加载路径是Classpath中的class文件。

2.        用户自定义的类加载器:要求继承java.lang.ClassLoader类

MyClassLoad load = new MyClassLoad();

load.loadClass(“class-name”)

至此只是将类加载到内存中,并没用主动调用这个类

类的验证:连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。

类验证的内容:

1.        类文件的结构检查

2.        语义检查

3.        字节码验证

4.        二进制兼容性的验证

注:在这个阶段java虚拟机为类的静态变量分配内存,并设置默认初始值。

类的初始化

1.        初始化的步骤:

1)        假如这个类没有被加载和连接,那就先进行加载和连接

2)        假如类存在直接父类,并且这个类还没有初始化,借先初始化直接的父类

3)        假如类中存在初始化语句,那就一次执行这些初始化语句

2.        类初始化的时机

1)        创建类的实例

2)        类或接口的【静态变量】,访问或赋值时

3)        类的【静态方法】被调用

4)        反射(Class.forName(“class-name”))

5)        初始化一个类的子类时,这个类也会被初始化

6)        被标明为启动类的类

3.        注意事项:

1)        当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口

Ø  在初始化一个类时,并不会初始化它所实现的接口

Ø  在初始化一个接口时,并不会先初始化它的父类接口

总之只有当程序首先使用特定接口的静态变量时,才会导致该接口的初始化

2)  调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化

类加载的机制

1.  父类委托机制

1)  在父委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器以外,其余的类加载器都有且只有一个父加载器。

2)  父亲委托机制能更好的保证java平台的安全性。因为在此机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠类,从而防止不可靠甚至恶意代码代替父加载

3)  当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父类加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才有加载器loader1本身加载Sample。


4)  若有一个类加载器能成功加载Sample类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)称为初始类加载器

5)  加载器之间的父子关系实际上指的是加载器对象之间的包装关系,不是继承关系。

2.  命名空间

1)  每个类加载器都有自己的命名空间,命名空间有该加载器所有加载的类组成。即与加载器和类名有关。

2)  只有属于统一运行时包的类才能相互访问,包可见的类和类成员。这样的限制可以避免用户自定义的类冒充核心类库中的类,去访问核心类库的包可见成员。


3)  子加载器的命名空间包含所有父加载器的命名空间。

4)  父加载器加载的类不能看见子加载器加载的类。

5)  当两个不同的命名空间内的类互相不可见,可采用Java反射机制来访问对方实例的属性和方法。

自定义类的加载器

步骤:

1.        继承ClassLoad类

2.        定义 private byte[] loadClassData(String name){}的方法

1)        Name为目标源,通过目标源获得一个字节输入流。

2)        创建一个内存字节数组输出流,完成将name所表示的字节码文件加载到内存中。

3)        并且返回含有字节码文件内容的一个字节数组。

3.        重写Class<?> findClass(String name) 方法

1)        调用刚刚完成的loadClassData(name)方法,得到一个字节数组

2)        Returnthis.defindClass(name,data,0,data.length),将字节数组转换为一个Class对象

加载器调用loadClass(name)来加载指定名字的类。在loadClass(name)中

1.        调用findClass(name)在内从中找到name类,并返回Class对象

2.        若没有找到,查看有没有父加载器,有则调用父加载器来加载的findClass(name),没有则掉根类加载器。

3.        若找到了这个类则依次返回,否则抛出 ClassNotFoundException异常

类的卸载

1.        由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。

2.        由用户自定义的类加载器所加载的类是可以被卸载的。

3.        类的Class实例与加载器之间是相互关联的。

1)        在类加载器的内部实现中,用一个Java集合来存放锁加载类的引用

2)        Class对象通过调用getClassLoader()方法获得它的类加载器

4.        一个类的实例总是引用代表这个类的Class对象

1)        在Object类中定义了getClass()方法,获得Class对象的引用。

2)        Java静态方法class,同样可获得Class对象的引用。

1 0