zoukankan      html  css  js  c++  java
  • 深入理解JVM

    概述JVM

    JVM简介:

    JVM全称是Java VirtualMachineJava虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应一次编译,随处运行的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。

    JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NONOJVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

    JVM组成

     

    从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互

    我们再来看下JVM有哪些组成部分,如下图所示:

     

    Runtime Data Area 运行时数据区

    Stack 

    Heap 

    Mathod Area 方法区

    PC Register 寄存器

    Native Method Stack 本地方法栈

    Execution Engine 执行引擎

    Native Interface 本地接口

    Native Libraies 本地包集合

    Class Loader类加载器

    类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:

    ClassFile{

    u4magic; 

    u2minor_version;

    u2major_version;

    u2constant_pool_count;

    cp_infoconstant_pool[constant_pool_count-1];

    u2access_flags;

    u2this_class;

    u2super_class;

    u2interfaces_count;

    u2interfaces[interfaces_count];

    u2fields_count;

    field_infofields[fields_count];

    u2methods_count;

    method_infomethods[methods_count];

    u2attributes_count;

    attribute_infoattributes[attributes_count];

    }

    需要详细了解的话,可以仔细阅读《JVM Specification》的第四章“The class File Format”,这里不再详细说明。

     Execution Engine执行引擎

    执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。

    Native Interface本地接口

    本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。

    Runtime data area运行数据区

    运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。

     

    整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!

     

    Java运行数据区

    PC程序计数器:是一块较小的内存空间(其实就是指针),它的作用可以看作是当前线程所执行的字节码的行号指示器。每一个线程都有自己私有的程序计数器。如果线程正在执行的是一个JAVA方法,该计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是native方法,则计数器值为空(undefined)。此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    JAVA虚拟机栈:也是线程私有的,生命周期和线程相同。每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈道出栈的过程。

    JAVA堆:JAVA堆是被所有线程共享的一块内存区域,在JVM启动时创建。其作用就是存放对象实例。

    JVM规范规定:所有的对象实例及数组都要在堆上分配。

    JAVA堆也是垃圾回收管理的主要区域。

    由于现在收集器基本采用分代收集算法,所以JAVA堆还可以细分为:新生代和老年代,再细分还有Eden空间、From Survivor空间、To Survivor空间。

    根据JVM规范,JAVA堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,可以是固定大小的,也可以是可扩展的(用-Xmx和-Xms控制),如果堆中没有内存,也无法扩展,抛出OutOfMemoryError

    方法区:也是所有线程共享的内存区域。它用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    在JVM规范中被描述为堆的一部分,但却又有一个non-heap的别名。

    对于HOTSPOT虚拟机,方法区又被称为永久代(Permanent Generation)

    这个区域一样不需要连续的内存,可以选择固定大小或者扩展,还可以选择不实现垃圾回收。确实这个区域的数据一般不参与回收,但这些数据并不一定就是永久存在了,常量池和对类型的卸载也可以成为回收的目标。抛出OutOfMemoryError

    运行时常量池:是方法区的一部分。

    在编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

    Class加载过程

    类加载按加载,连接,初始化这个顺序进行的,其中连接又可以细分为验证,准备,解析三个阶段,部分解析可以在初始化开始之后再开始,这样可以支持java的运行时绑定。虽然部分解析可以在初始化阶段开始以后再开始,但是这部分的初始化还是需要当前的部分解析以后才可以初始化。java虚拟机规范中严格规定了有且之友中情况必须立即对类进行初始化: 

    例:

    public class TestLoad{

    final static String aaa="aaaaaaa";

    int a=add(0);

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    TestLoad test=new TestLoad ();

    try {

    Class bean = Class.forName("synchronize.TestLoad ");

    Field f[] =bean.getDeclaredFields();

    for(int i=0;i<f.length;i++)

    {

    System.out.println("f"+i+":"+f[i].getName());

    }

    } catch (ClassNotFoundException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    System.out.println("a初始值:"+test.a);

    int b=test.add(test.a);

    System.out.println(b);

    }

    int add(int a){

    a=a+10;

    return a;

    }}

     

    下面让我们以TestLoad Class加载过程为例:

     

    1、 加载

    首先ClassLoad加载器加载Class文件。主要在方法区中保存了如下信息

    ① 这个类的完整有效名

    ② 这个类的父类完整有效名

    ③ 这个类的修饰符

    ④ 这个类的接口

    ⑤ 这个类的Method方法信息

    ⑥ 这个类的Field信息

    ⑦ 这个类的常量或static

    ⑧ 等其他信息

    方法的图只画出来方法区中的部分信息。

    创建一个Class对象,对象都在heap区。

    ① 首先根据方法区的Field变量创建一个Field类型的对象

    ② 根据方法区中的方法创建Method类型的对象

    ③ 根据需要创建需要的SoftReference的对象,SoftReference是一个泛型容器存放FieldMethod的对象

    ④ 创建一个Class对象,里面变量引用③中创建的对象

    2、 连接

    我只能自己想个大概,说不出来,请会的朋友告诉我

    3、 初始化

    在加载和连接后,需要对类的全局变量进行初始化。int a=add(0); add方法在main函数开始执行之前就执行了。

    运行过程

    栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?

    栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循先进后出原则。

    那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(LocalVariables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(FrameData),包括类文件、方法等等。

    我的理解:Java运行过程,是PC读取方法区的指令,操作栈中的对象的过程。

  • 相关阅读:
    在springMVC的controller层获取view层的参数的方式
    springMVC创建基础变量
    javascript 继承
    CSS3的新属性的一下总结
    常用js函数整理--common.js
    function与感叹号
    javascript void运算符
    ui组件--弹出层layer的使用
    组件,控件,插件,库都是什么鬼啊
    jsdoc文档
  • 原文地址:https://www.cnblogs.com/chengxuyuanzhilu/p/5181887.html
Copyright © 2011-2022 走看看