JVM

2020-05-07  本文已影响0人  鸡龙

未完待续……

帧数据区:JVM

一、JVM与java体系结构

使用HosSpot VM

1. jvm整体结构

jvm整体结构 image-20200428191541153

2. 架构图

1) jvm整体结构详细图

jvm整体结构详细图 image-20200428192104210

2) jvm架构模型

栈:

跨平台性、指令集小、指令多;执行性能比寄存器差

3. jvm生命周期

1) 虚拟机启动

java虚拟机的启动是通过引导类加载器bootstrap class loader创建一个初始类Initial class来完成的,这个类是由虚拟机的具体实现指定的。

object由引导类加载器加载,main由系统类加载器加载的,

2) 虚拟机的执行

3)虚拟机的退出

有如下的集中情况:

4. 虚拟机

二、类加载子系统

image-20200428193301852 image-20200428195615429

1.类的加载过程

加载=>验证=>准备=>解析=>初始化

1)加载loading

  1. 通过一个类的全限定名获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

补充:加载.class文件的方式

2)链接linking

① 验证 Verify

JClassLib和Binary Viewer

以CAFEBABE开头

主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

② 准备 Prepare
③ 解析 Resolve

3)初始化init

语句顺序执行示例

private static int num = 1;

static{
    num = 2;
    number = 20;
}

private static int number = 10;

public static void main(String[]args){
    System.out.println(ClassInitTest.num);//2
    System.out.println(ClassInitTest.number);//10
}

对应字节码文件

执行顺序

2.类加载器的分类

加载器 加载器继承关系

jvm支持的两种加载器

1)引导类加载器(Bootstrap ClassLoader)

2)自定义类加载器(User-Defined ClassLoader)

Java代码编写

将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

① Extension Class Loader 扩展加载器
② App Class Loader 应用加载器
③ System Class Loader 系统加载器

3.用户自定义类加载器

1)为什么要自定义类加载器

2)用户自定义加载器实现步骤

4.关于ClassLoader

ClassLoader是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)。

sun.misc.Launcher它是一个java虚拟机的入口应用。

获取ClassLoader的途径

操作 描述
clazz.getClassLoader() 获取当前类的ClassLoader
Thread.currentThread().getContextClassLoader() 获取当前线程上下文的ClassLoader
ClassLoader.getSystemClassLoader() 获取系统的ClassLoader
DriverManager.getCallerClassLoader() 获取调用者的ClassLoader

5.双亲委派机制

java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

1)工作原理

双亲委派

2)例子解释

我们创建一个java.lang包,在里面创建一个String类,并在里面添加一个静态代码块。

package java.lang;
public class String {
    static {
        System.out.println("我是自定义的String类的静态代码块");
    }
}

创建一个主方法类

public class StringTest {
    public static void main(String[] args) {
        String str = new String();
    }
}

这个时候,因为我创建了一个java.lang.String,而java类库本身就有一个java.lang.String,这个时候运行,猜一下结果是什么?

是输出静态代码块,还是什么都不输出?

答案是:什么都不输出。

为什么这里不会出错呢,为什么这里加载了String,为什么不执行静态代码块呢,看下图。

启动类加载器可加载的jar 扩展加载类可加载的jar

第一张图是启动类加载器可加载的java类库,第二张图是扩展类加载器可加载类库。再看下图的因为使用了双亲委派机制的流程图。

加载String类的流程

可以看到,当java.lang.String加载时,一直往上委派,直到委派到启动类加载器,因为启动类加载器中可以寻找到java.lang.String,这个时候,启动类加载器就会帮他的下层去完成这个类加载。因为他会到java类库中寻找java.lang.String,所以这个时候加载出来的java.lang.String就不是我们所写的,而是系统类库自带的。

那么我们做实验足以验证这个说法,在String添加main方法并尝试启动

public class String {
    static {
        System.out.println("我是自定义的String类的静态代码块");
    }

    public static void main(String[] args) {

    }
}
无法找到main方法

这足以证明,向上委派后,加载的类不是我们所编写的java.lang.String类。

可以看到双亲委派机制的优势

6.沙箱安全机制

7.其他

在JVM中表示两个cllass对象是否为同一个类存在两个必要条件

JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。

类的主动使用和被动使用

Java程序对类的使用方式分为:主动使用和被动使用。

三、运行时数据区

image 运行时数据区

红色区域:进程共享

灰色区域:线程共享

1.程序计数器

又叫PC寄存器,用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

image

下列的指令地址就是PC寄存器中需要存放的值。

反编译后的指令地址

使用PC寄存器的作用:因为CPU需要不停的切换线程,切换回来之后,JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

PC寄存器为什么设定为线程私有:cpu时间片轮换并发,需要线程切换,在切换其他线程时必然导致中断和恢复线程,每个线程拥有自己的PC寄存器能起到保护执行现场的作用。

2.虚拟机栈

栈解决程序的运行问题,堆解决数据存储。

作用:保存方法的局部变量(8种基本类型数据、对象的引用地址)、部分结果、并参与方法的调用和返回。

1)栈的特点

2)栈异常

java虚拟机规范允许java栈的大小是动态的或者是固定不变的。

各系统默认java栈内存大小

默认栈大小

设置栈内存大小-Xss:-Xss1m=-Xss1024k=-Xss1048576

3)栈中的存储

4)栈运行原理

5)栈帧的内部结构

image
① 局部变量表(Local Variables)
image

slot

image
public void test4() {
    int a = 0;
    {
        int b = 0;
        b = a + 1;
    }
    //c会重用b的slot槽位
    int c = a + 1;
}
c重用b的slot位
② 操作数栈(Operand Stack)

使用数组实现。也称为表达式栈(Expression Stack)

代码追踪

java代码

public void testAddOperation() {
    //byte、short、char、boolean:都以int型来保存
    byte i = 15;
    int j = 8;
    int k = i + j;
}

编译后,javap获取字节码

0: bipush        15
2: istore_1
3: bipush        8
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return

bipush 15执行后入栈15,istore_1出栈值赋于局部变量表索引为1的位置。执行完这两次命令后栈为空

bipush 15 istore_1

bipush 8执行后入栈15,istore_2出栈值赋于局部变量表索引为2的位置。执行完这两次命令后栈为空

bipush 8 istore_2

iload_1取出局部变量表中索引为1的值入栈,此时栈顶的值为15。

iload_2取出局部变量表中索引为2的值入栈,此时栈顶的值为8.

iload_1 iload_2

由执行引擎执行iadd从栈中出栈两个两值进行相加后入栈。istore_3从栈顶中取出一个值赋给索引为3的局部变量。此时栈为空。

iadd istore_3

最后return结束。

整个过程中,局部变量表长度为4(加上this),操作数栈深度为2。=>stack=2, locals=4

③ 动态链接(Dynamic Linking)#x

又称指向运行时常量池的方法引用

image

方法的调用

将符号引用转换为调用方法的直接引用与方法的绑定机制相关。

对应的方法绑定机制为:早期绑定晚期绑定绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。

非虚方法:

方法调用指令:

前四条指另固化在虚拟机内部,方法调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespeacial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。

重写本质:

  1. 找到操作数栈顶的第一个元素所执行的对象实际类型,记作C。
  2. 如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则这个方法的直接引用,查找过程结束:如果不通过,则返回java.lang.IllegalAccessError异常。
  3. 否则,按照继承关系从下往依次对C的各个父类进行第2步的搜索和验证过程。
  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

在上面写到的重写本质中其实就是虚方法的查找,为了提高性能,jvm采用在类的方法区建立一个虚方法表使用索引表来代替查找

举例一:在Son与Father都继承Object类,Son继承Father时,他们两个都会继承Object的方法。Son重写了从Father里继承的两个方法。重写方法之后指向Son类自身的方法,如果Son并未重写方法,直接引用就会指向Father的方法。

虚方法表

举例二:

interface Friendly{
    void sayHello();
    void sayGoodbye();
}
class Dog{
    
    public void sayHello(){}
    
    @Override
    public String toString(){
        return "Dog";
    }
}
class Cat implements Friendly{
    
    public void eat(){}
    
    @Override
    public void sayHello() {}
    
    @Override
    public void sayGoodbye() {}
    
    @Override
    protected void finalize(){}
    
    @Override
    public String toString() {
        return super.toString();
    }
}
class CockerSpaniel extends Dog implements Friendly{
    public void sayHello(){
        super.sayHello();
    }
    @Override
    public void sayGoodbye() {}
}

Dog类的虚方法表

Dog类的虚方法表

CockerSpaniel的虚方法表

CockerSpaniel的虚方法表

cat的虚方法表

cat的虚方法表
④ 方法返回地址(Return Address)

正常完成出口:

异常处理表:

  Exception table:
     from    to  target type
         4     8    11   Class java/io/IOException
⑤ 一些附加信息

栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。

6)栈的相关面试题

上一篇下一篇

猜你喜欢

热点阅读