JVM基本结构和工作过程

一.JVM的基本结构

首先要说明的,jvm是运行在操作系统之上的,与硬件系统没有直接的交互。

1.1 程序计数器


在CPU的寄存器中只有一个pc寄存器,存放下一条指令地址。每一条线程都有一个独立的程序计数器,Java虚拟机中的程序计数器指向正在执行的字节码地址,而不是下一条。

1.2 虚拟机栈


虚拟机栈是线程私有的,每个方法执行的时候都会创建一个栈帧,用于存放局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法调用从调用到执行完成的过程都对应着一个栈帧在虚拟机中的入栈到出栈的过程。局部变量表存放了编译期可以知道的基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置),和返回后所指向的字节码的地址。其中64 位长度的long 和double 类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个 。

1.3 本地方法栈


​ 在HotSpot虚拟机将本地方法栈和虚拟机栈合二为一,它们的区别在于,虚拟机栈为执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

1.4 Java堆


​ Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。这个区域是用来存放对象实例的,几乎所有对象实例都会在这里分配内存。堆是Java垃圾收集器管理的主要区域(GC堆),垃圾收集器实现了对象的自动销毁。Java堆可以细分为:新生代和老年代;再细致一点的有Eden空间,From Survivor空间,To Survivor空间等。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。可以通过-Xmx和-Xms控制 。

1.5 方法区


​ 方法区也叫永久代。在过去,类大多是“static”的,很少被卸载或收集,因此被称为“永久的”。由于类class是JVM实现的一部分,并不是由应用创建的,所以又被认为是“非堆(non-heap)”内存。

​ 永久代也是各个线程共享的区域,它用于存储已经被虚拟机加载过的类信息,常量,静态变量(JDK7中被移到Java堆),即时编译期编译后的代码(类方法)等数据。这里要讲一下运行时常量池,它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用(其实就是八大基本类型的包装类型和String类型数据(JDK7中被移到Java堆)) 。

在JVM中共享数据空间划分如下图所示:

对上图的解释如下:

1.JVM中共享数据空间可以分成三个大区,新生代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation),其中JVM堆分为新生代和老年代

2.新生代可以划分为三个区,Eden区(存放新生对象),两个幸存区(From Survivor和To Survivor)(存放每次垃圾回收后存活的对象)

3.永久代管理class文件、静态对象、属性等(JVM uses a separate region of memory, called the Permanent Generation (orPermGen for short), to hold internal representations of java classes. PermGen is also used to store more information )

4.JVM垃圾回收机制采用“分代收集”:新生代采用复制算法,老年代采用标记清理算法。

作为操作系统进程,Java 运行时面临着与其他进程完全相同的内存限制:操作系统架构提供的可寻址地址空间和用户空间。

 

二.一般性的工作过程

       一般来说,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程, 就为其分配一个Program Counter Register(程序计数器), VM Stack(虚拟机栈)和Native Method Stack(本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。 
       这也是为什么我把内存区域分为线程共享 | 非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

  •  第 1 步 、向操作系统申请空闲内存。内存写上“Java 占用”标签

  •  第 2 步,分配内存内存。JVM 分配内存,首先给 heap 分个内存,然后给栈内存也分配好。

  •  第 3 步,文件检查和分析class 文件。若发现有错误即返回错误。

  •  第 4 步,加载类,JVM 默认使用 bootstrap 加载器,加载过程下面详细聊一下。

  •  第 5 步、执行方法,执行启动一个线程,开始执行 main 方法。

  •  第 6 步,释放内存,运行结束,JVM 向操作系统发送消息,说“内存用完了,我还给你”,运行结束。

2.1JVM类的加载过程

    加载-->验证-->准备-->解析-->初始化,之后类就可以被使用了。绝大部分情况下是按这样的顺序来完成类的加载全过程的。但是是有例外的地方,解析也是可以在初始化之后进行的,这是为了支持java的运行时绑定,并且在一个阶段进行过程中也可能会激活后一个阶段,而不是等待一个阶段结束再进行后一个阶段。

1.加载

   加载时jvm做了这三件事:

     1)通过一个类的全限定名来获取该类的二进制字节流

     2)将这个字节流的静态存储结构转化为方法区运行时数据结构

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

2.验证

   验证、准备、解析这三步可以看做是一个连接的过程,将类的字节码连接到JVM的运行状态之中

   验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全

   验证主要包括以下几个方面的验证:

  1.   文件格式的验证,验证字节流是否符合Class文件的规范,是否能被当前版本的虚拟机处理
  2.        元数据验证,对字节码描述的信息进行语义分析,确保符合java语言规范
  3.   字节码验证 通过数据流和控制流分析,确定语义是合法的,符合逻辑的
  4.   符号引用验证 这个校验在解析阶段发生

3.准备  为类的静态变量分配内存,初始化为系统的初始值。对于final static修饰的变量,直接赋值为用户的定义值。如下面的例子:这里在准备阶段过后的初始值为0,而不是7。

public static int a=7

4.解析

  解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)

5.初始化    

 到了初始化阶段,jvm才真正开始执行类中定义的java代码

      1)初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集

           类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。

      2)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。

      3)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值