Java虚拟机——JVM分区

Java虚拟机——JVM分区


目录

前言

Java开发人员与C,C++开发人员的区别之一是把内存控制的权力交给了Java虚拟机。毫无疑问,这种模式很大程度上简化了Java开发中的内存管理环节。不过,也造成了在发生内存泄漏和溢出问题时,Java开发人员在不了解Java虚拟机运作的情况下很难去排查错误。因此,了解Java虚拟机的原理就显得尤为重要。
本篇博文是对JVM的初步理解下介绍JVM的分区情况。
由于更多的是理论学习,所以可能存在一些谬误,欢迎指正交流。


JVM分区

通常,在JDK1.7之前,Java虚拟机在执行Java程序的过程中包括5个区域,这五个区域分别是虚拟机栈、本地方法栈、程序计数器、方法区和堆。
其中,按线程公有或私有可以分为两类。
线程公有的包括方法区和堆,
线程私有的则是虚拟机栈,本地方法栈和程序计数器。
下面分别讲述一下我对他们的理解。
书上的分区图

《深入理解Java虚拟机》中Java虚拟机运行时数据区配图


1.堆

JVM中的堆这个分区是JVM存放Java对象实例和数组的地方(对象引用呢?它被放置在栈中的局部变量表中了),
堆还会被细分为新生代和老年代,通常默认的内存比例上新生代:老年代=1:2。我们可以通过参数设置进行修改,主要包括以下几个参数。
—Xmx 最大堆大小
—Xms 初始堆大小
—Xmn 年轻代大小
—XxSurvivorRatio eden:survivor的内存比例

新生代由一个Eden区和两个Survivor区(包括一个FromSurvivor去和一个ToSurvivor区)构成,存放的是新生和存活时间较短的对象。默认情况下Eden:From Survivor:To Survivor=8:1:1,这样的分配是为了在新生代进行GC的过程中,对新生代应用复制算法进行GC。

而老年代则只由一片区域构成,老年代中存放的是以下几种类型的对象:

  • 存活时间超过进入老年代的阈值的对象
  • 占用内存大的对象,会在创建时直接进入老年代
  • Minor GC后,Survivor去存放不下的对象
  • 动态年龄判断,当大于等于某个年龄的对象超过Survivor空间的一半时,符合该条件的对象进入老年代。

2.方法区

方法区在JDK1.7和JDK1.8中都有非常大的变动,因此需要分开来说,我就先以JDK1.7之前的方法区为例讲一下自己的理解,然后再补充JDK1.7和JDK1.8对它的改动。
方法区也常常被人们称为永久代或者非堆,它不是堆的一部分,和堆是完全独立的。称为永久代的原因是方法区中的内容通常只有很少需要进行回收的。
在方法区中发生的回收通常只有两类:

  • 常量池中常量的回收
  • 类的卸载

方法区在JDK1.7之前包含的主要有以下三部分内容

  • 已经被加载的类的信息
  • 常量池
  • 静态变量

其中比较被人们熟知的应该是常量池,提到常量池我们通常想到的是在它之中存储的各种常量,尤其具有代表性的是字符串和基本数据类型,这也是我们经常相对较难理解的地方,之前的博文中有提到字符串的new操作或者基本数据类型的赋值时曾对常量池和堆之间的关系有过初步的介绍。
常量池中包含的内容如下:

  • 字面量。 又包括final修饰的变量(常量)和文本字符串。
  • 符号引用。 又包括类或接口的全限定名,字段名称和描述符或方法名称和描述符。

字面量就是我们通常所关心的常量池中的内容。通常包括数值、字符串和其它常量。

JDK1.7中对方法区进行了改动,将常量池放置在了Java堆中。


JDK1.8则直接取消了方法区,将静态变量放在Java堆中,而类的元数据放在了Native Memory(本地内存)中。
方法区也就正式成为了过去式。


3.虚拟机栈

虚拟机栈是线程私有的,每一个线程都有自己的虚拟机栈,通常情况下,虚拟机栈是作为一个线程的运行过程中方法的调用的执行者。
每当线程调用一个方法时,该方法就会作为一个栈帧进入虚拟机栈。
这个栈帧包括以下四种内容:

  1. 局部变量表。
    这也是我们通常所说的”堆栈“中的栈,在编译期就完成分配。它存放了编译期可知的各种基本数据类型(byte,short,int,long,float,double,char,boolean)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
  2. 操作数栈。
    通常是我们在方法中用到的操作数的记录。
  3. 动态链接。
  4. 方法出口。
    当方法的调用结束后,方法就出栈,也就相当于方法的生命周期结束。
    值得注意的是,基本数据类型的变量是被存放在栈中的,因为它们不是对象,不存放在堆中。

4.本地方法栈

本地方法栈是线程私有的。
Native Method Stack和虚拟机栈的作用相似,不过它是服务于本地方法(Native Method)的,要知道的是本地方法通常都是非Java语言编写的。通常会是C或C++。


5.程序计数器

程序计数器是线程私有的,通常是用来记录当前线程运行到的字节码指令的地址的。
可以简单的把它理解为一个程序的行号计数器,记录当前的程序运行到了哪一位置。
值得注意的是当线程在执行本地方法时,程序计数器的值会是underfound。


以上就是我对JVM分区的一些个人理解。是学习书籍《深入理解Java虚拟机》和浏览他人博客所做的整理,可能有所纰漏,希望能够交流补充,共勉。