JVM内存区域划分

目录

          • 一、Java程序执行过程
          • 二、Java内存区域
          • 三、拓展

一、Java程序执行过程

JVM内存区域划分_第1张图片
首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。

二、Java内存区域

JVM内存区域划分_第2张图片

  • 程序计数器:是一块比较小的内存空间,用来指示程序执行哪条指令,线程私有。( 由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。)

    1. 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)
    2. 在JVM中多线程是通过线程轮流切换来获得CPU执行时间的,在任一具体时刻,一个 CPU的内核只会执行一条线程中的指令,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的.
    3. 程序计数器是jvm内存环境中唯一一个不会发生OOM异常的区域
  • Java虚拟机栈: 早期也叫Java栈。描述的是Java方法执行的内存模型,执行每个方法的时候都会创建一个栈帧,用来存储局部变量表、操作数栈、方法出口等信息。局部变量表中存放了各种基本数据类型,对象引用类型,局部变量表在编译期间完成分配,线程私有

    这个区域有两种异常情况:如果线程请求的栈深度大于虚拟机容许的最大深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,但是扩展时无法申请到足够的内存,抛出OutOfMemoryError异常

  • 本地方法栈:它和Java虚拟机栈的内存机制相同,本地方法栈则是为执行本地方法(Native Method)服务的,线程私有

  • Java 堆:Java堆是被所有线程共享的一块内存区域,在JVM启动时创建,随着JVM的结束而消亡。此内存区域所占的面积最大,他主要是用于存放对象实例,几乎所有的对象实例都在这里分配内存。因此他也是垃圾回收器主要关注的区域,当创建的对象实例特别多导致顿内存空间不够时,此区域会发上OOM异常。堆内空间还会被不同的垃圾收集器进行进一步的细分,最有名的就是新生代、老年代的划分

  • 方法区:这也是所有线程共享的一块内存区域,用于存储对象的类型数据,例如类结构信息,静态变量,以及常量池、字段、方法代码等。由于早期的Hotspot JVM实现,很多人习惯于将方法区称为永久代。注意:Oracle JDK 8中将永久代移除,同时增加了元数据区(Metaspace)。JVM对永久代垃圾回收(如,常量池回收、对象类型的卸载)非常不积极,所以当我们不断添加新类型的时候,永久代会出现OutOfMemoryError,尤其是在运行时存在大量动态类型生成的场合;类似Intern字符串缓存占用太多空间,也会导致OOM问题

三、拓展

新生代、老生代、永久代(1.8后为元空间)
JVM内存区域划分_第3张图片

1、新生代
新创建的对象都会存放到新生代中。新生代中对象的特点:很快会被GC回收掉或者不是特别大的对象
为了方便垃圾收集,新生代又分出了一个Eden区,两个 Survivor区

使用策略:
JVM 每次只会使用 Eden区 和其中的一块 Survivor 区域来为对象服务,另一块Survivor区域是空的,用于垃圾回收。
举个例子,第一次回收的时候,虚拟机会将 Eden区+Survivor(from)区域的存活对象复制到Survivor(to)上(存活对象小于Survivor(to)的空间),清空Survivor(from),虚拟机使用Eden区+Survivor(to);
第二次回收的时候,虚拟机再将Eden区+Survivor(to)存活的对象复制到Survivor(from)。
JVM内存区域划分_第4张图片
2、老生代
在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代(默认是15,可以通过-XX:MaxTenuringThreshold来设置)。
另外,比较大的对象也会进入老年代,可以-XX:PretenureSizeThreshold进行设置。
因此,老年代中存放的都是一些生命周期较长的对象或者特别大的对象

3、永久代
即JVM的方法区。在这里存放着一些被虚拟机加载的类信息(别忘了还有动态生成的类)的静态文件,JVM对永久代垃圾回收(如,常量池回收、对象类型的卸载)非常不积极。
4、元空间
从jdk1.8开始,Java使用元空间代替了永久代,元空间并不在虚拟机中,而是直接使用本地内存。 那么,默认情况下,元空间的大小仅受本地内存限制。当然,也可以对元空间的大小手动的配置。

采用元空间而不用永久代的几点原因:
1. 字符串存在永久代中,容易出现性能问题和内存溢出。
2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因为堆空间有限,此消彼长)。
3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
4. Oracle 可能会将HotSpot 与 JRockit 合二为一。

你可能感兴趣的:(JVM)