JVM内存模型和内存分配

原文:https://www.cnblogs.com/fubaizhaizhuren/p/4976839.html

一、什么是JVM?
  1. JVM是一种用于计算设备的规范,它是一个虚构出来的机器,是通过在实际的计算机上仿真模拟各种功能实现的。
  2. JVM包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。
  3. JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
二、JDK、JRE、JVM是什么关系?
  1. JRE(Java Runtime Environment),也就是java平台。所有的java程序都要在JRE环境下才能运行。
  2. JDK(Java Development Kit),是开发者用来编译、调试程序用的开发包。JDK也是Java程序需要在JRE上运行。
  3. JVM(Java Virtual Machine),是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
    Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
3.JVM原理
  1. JVM是Java的核心和基础,在Java编译器和OS平台之间的虚拟处理器,可在上面执行字节码程序。
  2. Java编译器只要面向JVM,生成JVM能理解的字节码文件。Java源文件经编译成字节码程序,通过JVM将每条指令翻译成不同的机器码,通过特定平台运行。
    JVM内存模型和内存分配
四、JVM执行程序的过程
  1. 加载.class文件
  2. 管理并分配内存
  3. 执行垃圾收集

JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。
JVM在整个JDK中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机。
操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境:

  1. 创建JVM装载环境和配置
  2. 装载JVM.dll
  3. 初始化JVM.dll并挂界到JNIEnv(JNI调用接口)实例
  4. 调用JNIEnv实例装载并处理class类。
五、JVM的生命周期
  1. JVM实例对应了一个独立运行的java程序它是进程级别

    a. 启动
    启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点

    b. 运行
    main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程

    c. 消亡
    当程序的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序可以使用Runtime类或者System.exit()来退出

  2. JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的

六、JVM内存模型
  1. Java代码具体执行过程如下图
    JVM内存模型和内存分配

  2. 运行时数据区,即JVM内存结构图如下图
    JVM内存模型和内存分配

  3. 运行时数据区存储了哪些数据?

    a. 程序计数器(PC寄存器)
    由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的。因此在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置。

    每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。

    因此可以这么说,程序计数器是每个线程所私有的。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

    b. Java栈
    Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。

    当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。JVM内存模型和内存分配
    c. 本地方法栈
    本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的

    d. 堆
    Java中的堆是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。

    e. 方法区
    与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

    在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

七、JVM内存溢出的情况

JVM内存模型和内存分配
a. 程序计数器(Program Counter Register)
每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。

b. Java虚拟机栈(Java Virtual Machine Stacks)
在Java虚拟机规范中,对这个区域规定了两种异常情况:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
  2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

这两种情况存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。

在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。而在多线程环境下,则会抛出OutOfMemoryError异常。

c. 堆Java Heap
Java Heap是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。

根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常

d. 方法区域
又被称为“永久代”,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。


参考资料:http://www.cnblogs.com/dolphin0520/p/3613043.html
参考资料:http://www.cnblogs.com/sunada2005/p/3577799.html
参考资料:http://blog.****.net/ns_code/article/details/17565503