-
前言
JAVA GC(Garbage Collection,垃圾回收)机制是区别C++的一个重要特征,c++的开发人员需要自己管理每一个对象的创建和销毁.而对于java的开发人员来说,他们不需要了解对象的创建和销毁,因为jvm已经帮我们把这件事-"垃圾回收"代劳了.但这并不意味着我们不用去理解GC机制的原理,因为如果不了解其原理,可能会引发内存泄漏、频繁GC导致应用卡顿,甚至出现OOM等问题,因此我们需要深入理解其原理,才能编写出高性能的应用程序,解决性能瓶颈。
想要理解GC的原理,我们必须先理解JVM内存管理机制,因为这样我们才能知道回收哪些对象、什么时候回收以及怎么回收。
-
JVM内存管理
JVM把它所管理的内存划分为以下5个不同的数据区域
- 程序计数器(Program Counter Register)
- 虚拟机栈(VM Stack)
- 本地方法栈(Native Method Stack)
- 堆区(Heap)
- 方法区(Method Area)
-
程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器.程序中的分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成的.
每一条线程都有一个独立的程序计算器,各个线程之间的计数器互不影响,独立存储,所以程序计数器是"线程私有"的.
如果线程执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器则为空.并且该内存区域是唯一一个不会出现OutOfMemiryError异常的区域。
-
虚拟机栈(VM Stack)
每个线程都有一个虚拟机栈,它的生命周期与线程相同,也是线程私有的。
每一方法在执行的时候都会在虚拟机栈中压入一个栈帧(Stack Frame),它用于存储局部变量表,操作数栈,动态链接,方法出口等信息.而当方法结束时,其对应的栈帧就会从虚拟机栈中出栈.
在虚拟机栈中有两种异常情况:
StackOverflowError异常:当现场请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常.一般该异常会出现在虚拟机栈的长度是固定的时候,而现在大部分的虚拟机栈都是可以动态扩展的.
OutOfMemoryError异常:如果虚拟机栈可以动态扩展,并且扩展时无法申请到足够的内存就会抛出OutOfMemoryError异常
-
本地方法栈
本地方法栈的功能和虚拟机栈的功能差不多,两者不同之处在与:虚拟机栈执行的是java方法(也就是字节码),而本地方法栈执行的是Native方法(本地方法).
在虚拟机规范并没强制规定本地方法栈中使用的语言,使用方式与数据结构.因此在有的虚拟机中(如hotspot虚拟)会把本地方法栈和虚拟机栈合二为一.
本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常
-
堆区(Heap)
java堆是java虚拟机所管理的内存中最大的一块.同时它并不是线程私有的,它被所有线程所共享.堆区的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存.
堆区是垃圾收集器管理的注意区域,因此也被称为"GC"堆
-
方法区(Method Area)
方法区用于存储已被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码等数据.它是非线程私有的区域,能被各个线程所共享.当方法区超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。
在Hotspot虚拟机中,这块区域对应持久代(Permanent Generation),一般来说,方法区上执行GC的情况很少,因此方法区被称为持久代的原因之一,但这并不代表方法区上完全没有GC,其上的GC主要针对常量池的回收和已加载类的卸载。在方法区上进行GC,条件相当苛刻而且困难。
运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译器生成的常量和引用。一般来说,常量的分配在编译时就能确定,但也不全是,也可以存储在运行时期产生的常量。比如String类的intern()方法,作用是String类维护了一个常量池,如果调用的字符”hello”已经在常量池中,则直接返回常量池中的地址,否则新建一个常量加入池中,并返回地址。