JVM 深入理解(一)

2020-06-13  本文已影响0人  only_one
简介:
Java Virtual Machine JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。
翻译:Java 程序不一样,使用 javac 编译成 .class 文件之后,还需要使用 Java 命令去主动执行它,操作系统并不认识这些 .class 文件。所以JVM就是一个翻译。
image.png
从图中可以看到,有了 JVM 这个抽象层之后,Java 就可以实现跨平台了。JVM 只需要保证能够正确执行 .class 文件,就可以运行在诸如 Linux、Windows、MacOS 等平台上了,JVM讲.class和.jar进行翻译成操作系统能识别的机器码,从而调用操作系统的方法。
JVM、JDK,JRE之间的关系:
JVM:只是一个翻译,把Class翻译成机器识别的代码,JVM 不会自己生成代码,需要大家编写代码,同时需要很多依赖类库,这个时候就需要用到JRE。(翻译)
JRE:它除了包含JVM之外,提供了很多的类库(就是我们说的jar包,它可以提供一些即插即用的功能,比如读取或者操作文件,连接网络,使用I/O等等之类的)这些东西就是JRE提供的基础类库。JVM 标准加上实现的一大堆基础类库,就组成了 Java 的运行时环境,也就是我们常说的 JRE(Java Runtime Environment)。(提供类库)
JDK:提供了一些非常好用的小工具,比如 javac(编译代码)、java、jar (打包代码)、javap(反编译<反汇编>)等。这个就是JDK(提供工具)
JVM整体流程图:
流程图.png
图中的运行时数据区:jvm所管理的内存。
执行引擎:将放在运行时数据区的中的方法,和操作进行解释执行。
解释执行与JIT之间的区别:
以HelloWord.java为例,通过javac进行编译之后得到.class文件,通过ClassLoader进行类加载到运行时数据区中(jvm所管理的内存),jvm再通过执行引擎将运行时数据区中的数据进行翻译机器码,从而执行调用操作系统的方法。那么还是没有说到解释执行和JIT 呢?
翻译:
(1)、将所有的类都全部翻译成机器码之后才执行,(速度慢,效率低)
(2)、一遍翻译一遍执行,(相比第一种速度上有了很大的提升)-----解释执行。
那什么又是JIT呢?
在hotspot中,可能会有热点方法,如我们同一个方法执行很多次,jvm会将这部分代码直接编程成本地代码,提高运行速度,(前提是热点方法,热点类)
运行时数据区:
运行时数据区的定义:
Java虚拟机在执行Java程序 的过程中会把它所管理的内 存划分为若干个不同的数据 区域。
运行时数据区的类型:程序计数器、虚拟机栈、本 地方法栈、Java堆、方法区 (运行时常量池)、直接内存。
如图所示:
image.png
1、程序计数器:当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。
举个例子:现有一个Person类,Person中提供了对应的方法,通过javac编译之后得到对应的.class文件,现在通过javap -c 对.class 文件进行反编译,如图所示:
image.png
上图所示是通过反编译得到.class文件的对应的字节码,每行都有对应的编号,在多线程中,cpu调度线程的,时间轮转时线程通过程序计数器记录当前的对应的行号,直到线程下次拥有cpu调度时接着执行,从而确保程序的正常执行。
2、虚拟机栈:存储当前线程运行方法所需的数据,指令,返回地址,栈(先进后出)
2.1、栈帧(方法)
2.1.1、局部变量表:用来存储局部变量 只能 存储8大基本类型,对象引用
2.1.2、操作数栈:存放方法的操作、执行。
2.1.3、动态链表::Java语言特性多态(需要类运行时才能确定具体的方法)。
2.1.4、完成出口:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)
3、方法区:
方法区主要存储:类信息,常量,静态变量,即时编译期编译后的代码。
4、堆:
堆主要存储:几乎所有对象实例,数组。
方法区和堆为啥不用一个区域,要用两个区域?堆可以进行频繁的回收,方法区回收比较难,动静分明。
从底层深入理解运行时数据区:
image.png
1、申请内存,分别给堆,栈,方法区分配内存。
2、类加载,class进入方法区。
3、常量,静态变量 入方法区。
4、虚拟机栈帧--入栈帧
下面先介绍一下HSDB工具,查看内存的工具:
1)、首先找到jdk的安装目录,找到sawindbg.dll文件,将改文件复制到对应目录的jre下。
image.png
2)、现在采用JVMObject.java为例:
public class JVMObject {
    public final static String MAN_TYPE = "man"; // 常量
    public static String WOMAN_TYPE = "woman";  // 静态变量

    public static void  main(String[] args)throws Exception {//栈帧
        Teacher T1 = new Teacher();//堆中   T1 是局部变量
        T1.setName("Mark");
        T1.setSexType(MAN_TYPE);
        T1.setAge(36);
        for (int i=0;i<15;i++){//进行15次垃圾回收
            System.gc();//垃圾回收
        }
        Teacher T2 = new Teacher();
        T2.setName("King");
        T2.setSexType(MAN_TYPE);
        T2.setAge(18);
        Thread.sleep(Integer.MAX_VALUE);//线程休眠很久很久
    }
}

class Teacher{
    String name;
    String sexType;
    int age;//堆

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getSexType() {
        return sexType;
    }
    public void setSexType(String sexType) {
        this.sexType = sexType;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
3)、在jdk下面的lib目录找到a-jdi.jar文件,输入命令如下:
image.png
4)、让JVMObject运行,再将环境切换到JVMObject对应的目录下输入命令如下所示:
image.png
5)、在HSDB输入框中输入JVMObject对应的10756,并点击main方法
image.png image.png
如图所示为jvm虚拟机的栈区图,左边对应的是内存的地址。
image.png
main方法对应栈帧的内存地址,图上怎么还会有一个栈帧?原因是在代码中还调用了sleep方法,查看 sleep源码属于本地方法,验证了本地方法栈和hotspot栈合并。
再看class在内存中所在的区域
image.png image.png
通过点击Teacher可以看到有两个地址,在回看JVMObject代码,在代码中创建了两个对象,mark和king对象。为了验证随便点击一个进行查看:
image.png
接下来看看堆空间
image.png
从图上可以看出,分为两个空间,新生代,和老年代,并且内存地址还是连续的,JVMOject代码中mark对象通过调用gc进行回收15次,则mark对象进入了老年代,而king在新生代。
image.png
总结:
以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以 及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放; 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
上一篇下一篇

猜你喜欢

热点阅读