概述
想要进一步掌握Java语言,必须要深入了解一下Java程序的运行环境。本文会对JVM的内存模型、Java内存自动管理机制、以及Oracle官方虚拟机HotSpot在GC方面的实现策略进行大概的梳理。
什么是Java的内存模型?
众所周知,Java程序是运行在JVM上面的,但是不具体指定是哪一款JVM,只要是符合一定的规范的JVM,都可以正确的运行Java字节码。该规范由Oracle官方提供,旨在描述一个抽象的JVM,它包含很多部分,包括class文件结构、运行时内存状态、指令集等。
而上述所说的运行时内存状态这一部分,就是常说的JVM(Java) 的内存模型。它主要描述的是运行时,各类型数据在内存中的呈现方式。JVM内存模型并没有指定该呈现方式的具体实现方式,而是指定了一些逻辑上应该拥有的模块,以及每个模块的作用域、大小、存放的数据类型等信息。
Java语言规定的内存模型是什么样子?
JVM规范给出了运行时的内存模型,应该包含的一些模块,以及每个模块的特性,如下图所示:
什么是GC?
通常所说的GC,可以叫做Garbage Collection 或者 Garbage Collector,GC既是表明一种内存管理机制,也可以看做是内存管理程序,这里不做详细区分。Java跟C++的很大的一个区别就是,内存自动管理机制。既然是内存自动管理,那么就涉及到内存的分配与回收问题。而且是自动的,所以在Java程序运行时,JVM中肯定还有一个负责做这件事的程序,这就是GC。
GC有自己的责任,主要有下面几个职责:
分配内存 - 为新的对象分配请求寻找足够的内存空间。
回收垃圾内存 - 当内存满了,不能为新对象分配内存时、或者内存使用达到一定的百分比时,回收掉那些不再被使用的对象(垃圾对象)。
整理内存碎片 - 回收完成后,可能会有很多不连续的内存空间,叫做内存碎片。整理碎片,有助于下次更快的为新对象分配内存。
什么是内存垃圾?
Java是面向对象语言,正常都是操作对象,那么当一个对象用完后,不再需要使用的时候,它就变成了内存垃圾。在JVM中,主要是通过GC Roots来实现垃圾对象的识别。当一个对象,从任何的GC Roots开始,都不能访问到该对象时,该对象就是垃圾对象。
GC Roots的定义如下:
Class - 系统ClassLoader加载的class,这些类不能被卸载。他们可以通过static变量来持有对象。自定义的ClassLoader不是GC Roots。
Thread - 还存活的Thread。
JVM Stack - 本地变量或者方法的参数。
JNI Local - Native方法的本地变量或者参数。
JNI Global - 全局的JNI引用。
Held By JVM - 系统的ClassLoader、一小部分JVM知道的重要的Exception、一些为处理异常而预先分配的对象、正在loading class的自定义ClassLoader。
如何衡量一个GC的性能?
生产能力 - 应用程序运行的时间与总运行时间的比例。
GC运行的额外消耗 - GC程序运行的时间与总运行时间的比例。
暂停时间 - GC运行时,应用程序暂停的时间。
GC频率 - GC的执行频率
覆盖的区域大小 - GC工作的区域大小。
垃圾回收的及时性 - 一个对象成为垃圾对象到被回收的时间。
HotSpot是怎么实现GC策略的?
HotSpot采用了分代管理的方式。不同的内存区域的对象,存活的年龄不一样。
1、新生代(young generational)
大多数对象直接分配在新生代的Eden区域(一些大对象直接分配在老年代),Survivors 的 From 区域保存那些在至少一次新生代GC之后活下来,但是还不能被认为是“足够老”的对象,它们有更多死亡的机会。而这时 To 区域是空的。这两个区域交替变换身份(下一次GC,现在的 To 区域就变为 From 区域)。
2、老年代(old generational)
包含从新生代存活下来的对象,以及一些直接分配在老年代的大对象,
3、持久代(permanent generational)
包含那些JVM的 gc collector 方便管理的对象,比如类或者方法的描述对象,以及类和对象本身。Method Area 和 Run-Time Constant Pool。
有哪些GC类型?
1、minor collection(young generational collection)
触发条件:当新生代区域满的时候,会触发。
回收区域:只对新生代进行回收。
2、full collection(major collection)
触发条件:老年代或者持久代区域满的时候,会触发。
回收区域:三个区域都会被回收。
HotSpot中有哪些Garbage Collector?
1、Serial Collector
在Serial Collector中,不管是 新生代 还是 老年代/永久代,都是串行执行,使用一个CPU。当执行GC时,应用程序就会被暂停,不被执行。
young generational 的回收方式:
Eden区域存活的对象,被复制到Survivor中为空的To区域。如果有To区域容纳不下的大对象,则直接复制到old generational。
From区域中存活的相对年轻的对象,被复制到To区域中。存活的相对老的对象,被复制到old generational。
当To区域不够容纳存活对象时,这些存活对象直接被复制到old generational,不管它们经历了多少次young generational collection。
清空Eden和From,现在From变为了To,而To变为了From。
回收前:
回收后:
old generational(permanent generational) 的回收方式
Serial Collector 对 old generational区域实行Mark-Sweep-Compact的算法。该算法分为三个步骤,如下所示:
Mark - 标记出还存活的对象。
Sweep - 清理掉垃圾对象。
Compact - 执行移动压缩,把所有存活对象平移到开始趋区域。让尾部留出连续的空间,这样后面为对象可以快速分配空间。
回收前后:
2、parallel collector
young generational 的回收方式:
跟Serial collector的算法一样,只是利用了多核的优势,并行执行。蓝色为并行的应用程序,黄色为GC程序。
old generational (permanent generational)的回收方式:
跟Serial collector的算法一样,只是利用了多核的优势,并行执行。