JVM中的内存分配及GC回收过程

JVM简介
      JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
      Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。
      以上内容来自百度百科。。。

JVM的内存分配

这是JVM的体系结构图:在这里插入图片描述

类装载器(ClassLoader)(用来装载.class文件);执行引擎(执行字节码,或者执行本地方法)

运行时数据区(方法区、堆、虚拟机栈、程序计数器、本地方法栈)
我们主要了解下运行数据区各个模块的主要作用:

  1. 程序计数器
          程序计数器叫做Program Counter Register,占用内存小,线程私有,当我们的字节码解释器就是通过改变程序计数器的值来选定下一步的字节码指令。
           这个计数器记录的是正在执行的虚拟机字节码指令的地址,而且这一区域是java这几个区域中唯一一个没有被规定 OutOfMemoryError 的区域。

  2. 虚拟机栈
           虚拟机栈叫做VM Stack,线程私有,主要存储的是局部变量表、操作数栈、动态链接、方法出口等信息,当方法结束的时候,这一块区域自动释放。

  3. 本地方法栈
           本地方法栈叫做Native Method Stack,类似于虚拟机栈,不同点在于虚拟机栈是为虚拟机执行字节码来服务,而本地方法栈是为虚拟机执行本地方法,也就是Native方法来服务的。


  4.        堆就是Heap,线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。JVM管理内存的最大的一块。我们后边说的GC主要发生在这一区域。

  5. 方法区
           方法区叫做Method Area,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

GC回收

      上边已经提到过,GC主要说的是堆。在堆中,找到已经无用的对象,并把这些对象占用的空间收回使其可以重新利用.大多数垃圾回收的 算法思路都是一致的:把所有对象组成一个集合,或可以理解为树状结构,从树根开始找,只要可以找到的都是活动对象,如果找不到,这个对象应该被回收了。
JVM回收的对象,是那些已经不再被使用的对象。而判断是否不再被使用的原则,可以从两个方面来描述。简单来说,第一是不可到达,第二是引用计数为0。
JVM为了更有效地管理和回收堆内存,以HotSpot虚拟机为例,其基于以下假设,将堆内存从物理上划分为两个部分,即年轻代(Young Generation)和年老代(Old Generation)。这两个假设便是:

大多数对象的生命周期都不会很长。也就意味着这些对象的引用会很快变得不可达;
只有很少的由老对象(创建时间较长的对象)指向新生对象的引用
对于年轻代,绝大多数新分配对象会在这块区域被创建;
年老代,占用空间会比年轻代多,年轻代中进行minor GC时存活下来的对象最终会进入这里。年老代中发生GC(即Full GC)的次数要少得多;
      而年轻代又会被划分为三个区域,通常是一个较大的区域主要用作对象分配的,叫Eden区;以及两个Survivor区。Eden区无需多说,对象分配时首先从这里分配空间。而两个Survivor的作用在于,Eden中经过一次minor GC后存活下来的对象,会进入其中一个Survivor区,而另一个Survivor区域则留作当前Survivor区的备份空间。当Survivor A区域中的空间饱和的时候,此时发生的Minor GC会将Survivor A中依然存活的对象,以及Eden中存活的对象,都复制到Survivor B,然后清空Survivor A。就这样循环往复,两个Survivor之间互相复制。
       GC采用分代收集思想,于是就有了对象年龄计数这一概念。出生在Eden的对象,经过一次Minor GC仍存活,年龄+1。如果其能被Survivor区容纳,将进入Survivor区域。也就是说只要其活过一次minorGC,就可进入survivor区了。随后每一次minorGC, 活下来的对象年龄都+1,达到一定年龄的对象将进入老年代(默认是15岁)。该阈值可通过 -XX:MaxTenuringThreshold设置。
       同时,如果survivor区内很多年龄不太大的对象怎么办呢,大家年龄都不足以进入老年代,但数量太多,survivor也吃不消啊。于是还有一条规则,就是survivor区内所有年龄相同的对象大小总和如果超过survivor区空间的一半,年龄大于等于该年龄的对象都直接进入老年代,不受参数MaxTenuringThreshold参数的限制了。

a) Minor GC: 新生代GC。发生相对频繁,回收速度也较快。

b) Major GC/Full GC: 老年代GC。通常会伴随一次Minor GC,速度较慢,通常比Minor GC耗时多10倍以上。

  1. GC回收策略
           JVM回收的对象,是那些已经不再被使用的对象。而判断是否不再被使用的原则,可以从两个方面来描述。简单来说,第一是不可到达,第二是引用计数为0。这两点,其实说的是一回事,从不同的方面来描述罢了。
           详细来说,不可到达,就是说从GC Root出发,对象之间的引用链没有指向的对象,我们称之为不可到达。引用计数为0其实说的就是从GC Root开始的对象引用链到达该对象的引用计数为0,即没有可到达的引用路径指向它了。
           可作为GC Root的对象包括:*方法区中加载的类的静态字段引用的对象,常量引用的对象;*每个线程对象的虚拟机栈中引用的对象;*本地方法栈中引用的本地对象或常量。
           既然说了可到达,那么提一句弱引用及软引用。弱引用即不影响可到达性的引用,如果没有强引用只有弱引用的对象,GC照样回收,也就是说弱引用是被GC直接忽略的。软引用则比弱引用略强硬一些,通常用来描述有用但非必须的对象。在系统将要发生内存溢出时会把软引用对象进行一次回收。
          至于具体的对象回收算法,其实现就要取决于垃圾收集器了。
          不同的垃圾收集器有其不同的回收算法。详见:GC算法简述:https://blog.csdn.net/xiaozhaoshigedasb/article/details/85559872

猜你喜欢

转载自blog.csdn.net/xiaozhaoshigedasb/article/details/85568596