JVM相关知识

1、JVM基础

JVM即:Java Virtual MachineJava虚拟机);jvm直接与操作系统进行交互;不会直接与服务器硬件进行交互。如图:

 

 

2、java文件执行过程

 

 

 

1、 java文件经过javac编译之后变成class字节码文件;

 

2、 字节码文件被classLoader类加载器搬运到jvm虚拟机中;

 

3、 虚拟机当中主要有五大块:  

a) 方法区:线程共享区域,存在多线程安全问题,主要存放全局变量,常量,静态变量等;

 

b) 堆:线程共享区域,存在多线程安全问题,主要存放对象实例和数组等;

 

c) 虚拟机栈:线程不同享区域,不存在多线程安全问题,主要存放局部变量等;

 

d) 本地方法栈:线程共享区域,不存在多线程安全问题主要负责去调用一些底层的C程序实现一般不做研究;

 

e) 程序计数器:线程不共享区域,不存在多线程安全问题,一般不做研究;

 

Animal.java

 

public class Animal {

 

    public String name;

 

    public Animal(String name) {

 

        this.name = name;

 

    }

 

    public void printName() {

 

        System.out.println("Animal ["+name+"]");

 

    }

 

}

 

MainApp.java

 

public class MainApp {

 

    public static void main(String[] args) {

 

        Animal animal = new Animal("Puppy");

 

        animal.printName();

 

    }

 

}

该程序运行的详细步骤:

1. 在编译好java程序得到MainApp.class文件后,执行MainApp

系统就会启动一个jvm进程,jvm进程从classpath路径中找到一个名为MainApp.class的二进制文件,将MainApp的类信息加载到运行时数据区的方法区内,这个过程叫做MainApp类的加载。

2. JVM找到AppMain主函数入口,开始执行main函数。

3. main函数的第一条命令是Animal  animal = new Animal("Puppy");

就是让JVM创建一个Animal对象,但是这时候方法区中没有Animal类的信息,所以JVM马上加载Animal,把Animal类的类型信息放到方法区中。

4. 加载完Animal类之后,Java虚拟机做的第一件事情就是在堆区中为一个新的Animal实例分配内存, 然后调用构造函数初始化Animal实例,这个Animal实例持有着指向方法区的Animal类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用。

5. 当使用animal.printName()的时候,JVM根据animal引用找到Animal对象,然后根据Animal对象持有的引用定位到方法区中Animal类的类型信息的方法表,获得printName()函数的字节码的地址。

6. 开始运行printName()函数。

 

3、类加载器

 

3.1、类加载器的过程

从类被加载到虚拟机内存中开始,到卸御出内存为止,它的整个生命周期分为7个阶段:

加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸御(Unloading)

其中验证、准备、解析三个部分统称为链接。 7个阶段发生的顺序如下:

 

 

 

3.1.1、加载:

 

(1)、将class文件加载在内存中。

 

(2)、将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构。

 

注意:方法区中如果出现OOM,那么多半是因为加载的依赖太多。

 

(3)、 在堆中生成一个代表这个类的java.lang.Class对象,作为数据访问的入口。

3.1.2、链接

 

private static int a = 3 ; 

 

a = 0 ; 

 

1、验证:确保加载的类符合JVM规范与安全。保证被校验类的方法在运行时不会做出危害虚拟机安全的事件

 

2、准备:为static变量在方法区中分配空间,设置变量的初始值。例如static int a=3,在此阶段会a被初始化为0

 

注意:准备阶段,只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是在对象初始化的时候分配值的

 

3、解析:虚拟机常量池内的符号引用替换为直接引用

 

符号引用:简单的理解就是字符串,比如引用一个类,java.util.ArrayList 这就是一个符号引用;

 

直接引用:指针或者地址偏移量。引用对象一定在内存(已经加载)。

3.1.3初始化

初始化是类加载的最后阶段,初始化阶段是执行类构造器<clinit>()方法。

在类构造器方法中,它将由编译器自动收集类中的所有类变量的赋值动作(<!--准备阶段的a正式被赋值3-->)和静态变量与静态语句块

static{}合并初始化,为类的静态变量赋予正确的初始值。

3.1.4使用,卸载

使用:正常使用

卸载:GC把无用的对象从内存中卸载。

 

3.2类加载器的加载顺序

 

 

 

1Bootstrap ClassLoader

 

负责加载$JAVA_HOMEjre/lib/rt.jar里所有的 class

 

2Extension ClassLoader

 

负责加载Java平台中扩展功能的一些 jar ,包括$JAVA_HOMEjre/lib/*.jar-Djava.ext.dirs指定目录下的 jar 包。

 

3App ClassLoader

 

负责加载 classpath 中指定的 jar 包及目录中 class。

 

4Custom ClassLoader

 

属于应用程序根据自身需要自定义的 ClassLoader加载过程中会先检查类是否被已加载,检查顺序是自底向上,从

Custom ClassLoader BootStrap ClassLoader 逐层检查,只要某个Classloader 加载就视为已加载此类,保证此类

只所有ClassLoader 加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

 

 

 

3.3双亲委派机制

 

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,

因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到

所需加载的Class),子类加载器才会尝试自己去加载。采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管

是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个

Object对象。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-04-17 22:06  明月清风306  阅读(114)  评论(0编辑  收藏  举报