JVM初识

作者: 奔向学霸的路上 | 来源:发表于2020-07-27 11:20 被阅读0次

    一、JVM的基本介绍

    JVM(Java Virtual Machine)又被分为三大子系统,类加载子系统,运行时数据区,执行引擎。


    image.png

    JVM是Java Virtual Machine的缩写,它是一个虚构出来的计算机,一种规范。
    JVM类似于一台小电脑运行在操作系统环境上,它和操作系统交互,与硬件不直接交互。

    image.png

    Java文件是如何被运行的

    比如我们现在写了一个HelloWorld.java,JVM并不认识它,所以它需要被编译,让它成为一个能被JVM读懂的二进制文件HelloWorld.class

    类加载器

    如果JVM想要执行这个.class文件,我们需要将其装进一个类加载器中,类加载器会把所以的.class文件全部搬到JVM里面来。


    image.png

    方法区

    方法区 是用于存放类似于元数据信息方法的数据的,比如类信息、常量、静态变量、编译后代码等
    类加载器将.class文件搬过来就是先丢到这一块上

    主要放了一些存储的数据,比如对象实例、数组等,它和方法区都同属于线程共享区域,也就是说他们都是线程不安全

    是代码运行空间,我们编写的每一个方法都会放到栈里面运行
    我们会听说 本地方法栈 或 本地方法接口,它俩底层是使用C来进行工作的,和Java没有太大的关系。

    程序计数器

    主要就是完成一个加载工作,类似于一个指针,指向下一行我们需要执行的代码。和栈一一样,是线程独享的,也就是说每个线程都独有一块区域,不会存在并发和多线程的问题。

    image.png

    小总结

    1.Java文件经过编译后变成.class字节码文件
    2.字节码文件通过类加载器搬运到JVM虚拟机中
    3.虚拟机主要的5大块:方法区、堆都是线程共享,的,由线程安全问题;栈和本地方法栈以及程序计数器都是独享区域,不存在线程安全问题,而JVM的调优是围绕堆,栈两大块进行

    简单的例子说明

    public class Student {
        public String name;
        public Student(String name){
            this.name = name;
        }
        public void sayName(){
            System.out.println("student's name is " + name);
        }
    }
    
    public class App {
        public static void main(String[] args) {
            Student student = new Student("lili");
            student.sayName();
        }
    }
    

    执行main方法的步骤如下:

    1. 编译App.java后得到App.class,执行App.class, 系统会启动一个JVM进程,从classpath路径种找到一个名为App.class的二进制文件,将App的类信息加载到运行时数据区的方法区内,这个过程叫App类加载
    2. JVM找到App的主程序入口,执行main方法
    3. 这个main中的第一条语句为Student student = new Student("lili"),是让JVM创建一个Student对象,这个时候方法区里没有Student类的信息,所以JVM会马上加载Student类,把Student类的信息放到方法区中
    4. 加载完Student类后,JVM堆中为一个新的Student实例分配内存,然后调用构造函数初始化Student实例,这个Student实例持有指向方法区中的Student类的类型信息的引用
    5. 执行student.sayName()时,JVM根据student的引用找到student对象,然后根据student对象持有的引用定位到方法区中student类的类型信息的方法表,获取sayName()的字节码地址
    6. 执行sayName()

    其实也不用管太多,只需要知道对象实例初始化时会去方法区中找类信息,完成后再到栈那里去运行方法。找方法就在方法表中找。

    二、类加载器的介绍

    之前也提到了它是负责加载.class文件的,它们在文件开头会有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,而是否能够运行则由 Execution Engine来决定

    类加载器的流程

    从类加载到虚拟机内存中开始,到释放总共有7个步骤:加载,验证,准备,解析,初始化,使用,卸载。其中验证,准备,解析三部分统称链接

    加载

    1. 将class文件加载到内存
    2. 将静态数据结构(数据存在于class文件的结构)转化成方法区中运行时的数据结构(数据存在于JVM时的数据结构)
    3. 在堆中生成一个代表这个类的 java.lang.Class对象作为数据访问的入口

    链接

    链接就是将Java类的二进制代码合并到java的运行状态中的过程。

    1. 验证:确保加载的类符合JVM规范和安全,保证被校验类的方法在运行时不会做出危害虚拟机的时间,其实就是一个安全检查
    2. 准备: 为static变量在方法区中分配内存空间,设置变量的初始值,例如: static int a = 3(注意:准备阶段只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量时对象初始化时赋值的)
    3. 解析:虚拟机将常量池内的符号引用替换为直接引用的过程符号引用,比如我现在import java.util.ArrayList这就算符号引用,直接引用就是指针或者对象地址,注意引用对象一定是在内存进行)

    初始化

    初始化其实就是一个赋值的操作,它会执行一个类构造器的<clinit>()方法。由编译器自动收集类中所以变量的赋值动作,此时准备阶段时的那个static int a = 3的例子,在这个时候旧正式赋值为3

    卸载

    GC将无用对象从内存中卸载

    类加载器的加载顺序

    加载一个class类的顺序也是优先级的,类加载器从最底层开始往上的顺序是这样的

    1. BootStrap ClassLoader:rt.jar(采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.、java.uti.等; 这些类位于$JAVA_HOME/jre/lib/rt.jar)
    2. Extention ClassLoader:加载扩展的jar包(扩展的class loader,加载位于$JAVA_HOME/jre/lib/ext目录下的扩展jar)
    3. App ClassLoader: 指定的classpath下面的jar包(系统class loader,父类是ExtClassLoader,加载$CLASSPATH下的目录和jar;它负责加载应用程序主函数类。)
    4. Custom ClassLoader:自定义的类加载器


      image.png

    双亲委派机制

    当一个类收到了加载请求时,它时不会先自己去尝试加载的,而是委派给父类去完成,比如我现在要new一个Person,这个Person是我们自定义的类,如果我们要加载它,就会先委派App ClassLoader,只有当父类加载器都反馈自己无法完成这个请求(也就是父类加载器都没有找到加载所需的class)时,子类加载器才会自行尝试加载

    这样做的好处是,加载位于rt.jar包中的类时不管时哪个加载器加载,最终都会委托到BootStrap ClassLoader进行加载,这样保证了使用不同的类加载器得到的是同一结果。

    其实这个也是一个隔离的作用,避免了我们的代码影响了JDK的代码,例如:

    public class String{
    }
    

    这种时候,我们的代码肯定会报错,因为在加载的时候其实是找到了rt.jar中的String.class

    原文:Java识堂 大白话带你认识JVM

    相关文章

      网友评论

          本文标题:JVM初识

          本文链接:https://www.haomeiwen.com/subject/kjumuhtx.html