一、前言

      这篇博客起,小编会向一个更加深层次、逼格满满的区域进发——JVM。

      可以说JVM不是一个新鲜的东西,但是做java的都会了解JVM,都听过JVM。有的时候我们写的代码运行跟JVM也有关系。

二、JVM介绍

      在java诞生的时候,就说“一处编译,到处运行”,是什么来保障“到处运行”的呢?

      答案就是JVM。

宏观把控

      要想学习jvm,就要对jvm有一个全局把控。

      下面的图中是小编画的简单的jvm的重要的内容,也是小编在后面的博客中重点学习的,本节中,就先向大家介绍java运行时的区域。

【JVM】程序员进阶JVM(一)——Java内存区域_局部变量

JVM的位置和作用

      我们在使用java之前呢,一开始就要安装jdk,jdk包含了java运行环境JRE、java工具包和java基础类库。其中JRE又包括了JVM、Java核心类库和支持文件。

【JVM】程序员进阶JVM(一)——Java内存区域_java_02

      从上面的图可以看出,我们写好的java文件,经过编译为class文件(字节码文件)。class文件就运行在JVM上,经过java解释执行。

JVM发展历史

【JVM】程序员进阶JVM(一)——Java内存区域_java_03

三、java内存区域

      针对Java的内存区域,我推荐大家可以用四象限的方式来学习。如下图:

【JVM】程序员进阶JVM(一)——Java内存区域_jvm_04

      在图中,把整个内存区域分层了四块:栈、方法区、堆、程序计数器。

      有过一两年的编程经验的工程师都会听过过这个。学过数据结构的就更不在话下。栈的最大的特点就是:“先进后出”。

      在Jvm中,栈存储的是局部变量、操作数栈和动态链接方法。

      2018年7月26日补充:

      栈是线程私有的

      栈分为了两个部分:

  1. Java虚拟机栈,线程私有。生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
  2. 本地方法栈,线程私有。和虚拟机栈的区别就在于一个是为了执行Java方法服务,一个是为了虚拟机使用到的Native方法服务。

      什么是本地方法?带有native的方法就是本地方法,一般是底层的方法,

【JVM】程序员进阶JVM(一)——Java内存区域_jvm_05

2018年7月26日 补充:

每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

我们知道了栈中存储是局部变量和操作数栈等。这里就要引出一个概念——栈帧。

现在理解为栈帧包括了局部变量表 + 操作数栈 + 动态链接 + 出口 四个部分:

  • 局部变量表:存储局部变量,固定长度的数据集合,定宽的, 32位,一个int的。
  • 操作数栈:存储sum中间结果的
  • 动态链接:有点抽象,指向的是 方法区中 运行时常量池 的 方法引用。
    动态链接是什么?
  • 返回地址:方法执行完成后,返回。1.正常出口,2.异常出口(上抛?,catch?)

【JVM】程序员进阶JVM(一)——Java内存区域_jvm_06

【JVM】程序员进阶JVM(一)——Java内存区域_开发语言_07

      如下图:

      a 和 b为局部变量,初始值分别为100 和98 ,局部变量初始化在局部变量表中,分别位于0和1的位置,然后 int c = 0;,也是局部变量,存储在局部变量表2的位置。然后是c=a+b,把a压栈到操作数栈,把b压栈到操作数栈,完成a+b后,把结果压栈到操作数栈。因为c=结果,所以要把结果放入到局部变量表的2的位置。这样c就变成了结果。

【JVM】程序员进阶JVM(一)——Java内存区域_jvm_08

      对于更多的栈帧操作,可以参看小编的这篇博客:

从内存层面理解,为什么 int i = 0; i = i++;

方法区

class static

      方法区主要就是存储类、常量、静态变量 和 即时编译器编译后的代码。

      方法区中没有垃圾收集。

      方法区也叫做静态区,是线程共享的。

      有人会问,方法区跟方法有关系吗?

      我的回答是:没有关系,老婆跟老婆饼有关系吗?蚂蚁跟蚂蚁上树有关系吗?

new 的对象

      堆中主要存储的是对象实例和数组实例。

      堆是主要的内存回收区域。

一般我们把堆分为新生代和老年代。更新细致一点的分配是Eden、From Survivor 和To Survivor。

      我们这样分配是跟要使用的内存分配和回收算法有关系,也是为了更好的分配和回收内存。

      堆中的数据线程共享

程序计数器

      主要是计数的。

      2018年7月26日补充

      可以看做当前线程要执行的代码(字节码)的行号指示器。字节码解释器工作的时候,就是要通过改变整个计数器的值,来找下一行要执行的代码,无论是循环、选择、顺序等都要按照计数器的指示来执行下一行代码。

      重要的是,程序计数器是线程私有的,所有线程之间不会共享,独立存储。 这个是因为JVM的多线程是通过线程切换并分配处理器执行时间的方式来实现的。在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以线程私有。

      程序计数器是唯一一个在Java虚拟机规范中没有规定任何OOM的区域。

四、小结

      通过这次的学习,我们可以了解,我们写的代码在PC中运行的时候,代码中的各个部分是存储在内存中的什么位置,进一步我们可以分析分析,如何写代码可以让程序运行的更快。