GC Garbage Collector
- 引用计数、引用跟踪、内存泄漏、内存溢出
- GC 的一般原理
- GC类型
- JVM中的串行 GC(Serial GC)
- JVM中的并行 GC(Parallel GC)
- JVM中的CMS GC(Mostly Concurrent Mark and Sweep Garbage Collector)
- ParNew 与 Parallel Scavenge的区别
- JVM中的 G1 GC
- ZGC
- ShennandoahGC
- 各垃圾回收器对比汇总
- GC总结
- 四种Java引用与GC的影响
- GC的特点
引用计数、引用跟踪、内存泄漏、内存溢出
引用计数:是指将对象被引用的次数相加,当对该对象的引用减少则将该对象的引用次数随之减少,当被引用次数变为零时就将其释放的过程
引用计数容易因为对象之间的循环依赖导致所涉及的内存无法被使用,而导致内存泄漏,当这样的对象增多,内存泄漏最终会导致内存溢出,即内存空间不足
引用跟踪:从根(GC Roots)出发,将所有的正在使用的对象串联起来,直至完毕,不被串联的对象即为“不可达”
GC Roots
可以作为 GC Roots 的对象
- 当前正在执行的方法里的局部变量和输入参数
- 当前活动线程的对象(Active threads)
- 所有类的静态字段(static field)
- JNI 引用
GC 的一般原理
标记清除算法(Mark and Sweep)与Full GC
标记清除算法(Mark and Sweep)简称清除算法
- Marking(标记): 遍历所有的可达对象,并在本地内存(native)中分门别类记下
- Sweeping(清除): 这一步保证了,不可达对象所占用的内存,在之后进行内存分配时可以重用
优势:可以处理循环依赖,只扫描部分对象
缺点:标记和清除对象需要 STW(Stop The World),即让全世界停止下来,此时只有GC线程在运行,而应用的其它业务线程都会停止
如果不对JVM堆内存做划分,那么每一次GC都是对整个堆内存的GC(Full GC),此时STW将会对应用的使用体验造成不好的影响,随着堆内存越大,STW的时间就越长,此时客户端等待应用的响应时间就越长
需要注意的是:标记阶段,暂停的时间,与堆内存大小,对象的总数没有直接关系,而是由存活对象(alive objects)的数量来决定,所以堆内存的大小并不会直接影响标记阶段占用的时间
分代假设和堆内存池的划分
分代假设:大部分新生对象很快无用,存活较长时间的对象,可能存活更长时间
对于存活较长并且可能更长的对象可以继续保留,对于很快无用或存活时间有限长的对象可以被清理,做了这种区分,无疑将缩短更多的STW时间,由此产生了不同类型对象在不同区域,有不同策略处理
标记-复制算法(Mark-Copy/Minor GC)
标记-复制算法(Mark-Copy)简称复制算法也叫Minor GC,主要用于对堆内存中的young区的对象GC处理
对象分配在新生代的 Eden 区,标记阶段 Eden 区以及非空存活区中存活的对象就会复制到空的存活区,两个存活区互换 from 和 to 的角色,对象存活到经过了一定数量的来回周期会提升到老年代
标记-清除-整理算法(Mark-Sweep-Compact/Major GC)
标记-清除-整理算法(Mark-Sweep-Compact)简称标记-整理、整理算法也叫Major GC,主要用在老年代堆区的GC处理
老年代默认都是存活对象,采用移动方式:
- 标记所有通过 GC roots 可达的对象
- 删除所有不可达对象
- 整理老年代空间中的内容,方法是将所有的存活对象复制,从老年代空间开始的地方依次存
放,相当于做了一次移动
为什么老年代不用复制算法,因为老年代存活的对象多,空间相对不足够
为什么要做最后一步的整理,因为堆内存空间总是一片连续的内存空间,如果不做整理导致内存碎片化,则当存储大对象时就会容易触发FullGC,进而产生STW时间
关于触发Full GC
GC类型
JVM中的串行 GC(Serial GC)
- 启动Java进程时通过 -XX:+UseSerialGC 配置串行 GC
- 串行 GC 对年轻代使用 单线程 mark-copy(标记-复制)算法 = Serial young
- 对老年代使用 单线程 mark-sweep-compact(标记-清除-整理)算法 = Serial old
- Serial GC = Serial young + Serial old
- Serial young、Serial old 两者都是单线程的垃圾收集器,不能进行并行处理,都会触发全线暂停(STW),停止所有的应用线程
- Serial GC 不能充分利用多核 CPU,不管有多少 CPU 内核,JVM 在垃圾收集时都只能使用单个核心
- Serial GC对 单核CPU 利用率高,暂停时间长。简单粗暴,就像老式的电脑,动不动就卡死。该选项只适合几百 MB 以下堆内存的 JVM
JVM中的并行 GC(Parallel GC)
- 并行GC即为多个GC线程一起并行的进行垃圾回收,因为此时没有应用线程抢占CPU资源
- 对年轻代使用 多线程并行 的 mark-copy(标记-复制)算法 = Parallel Scavenge
- 对老年代使用 多线程并行 的 mark-sweep-compact(标记-清除-整理)算法 = Parallel Old
- Parallel GC = Parallel Scavenge + Parallel Old
- Parallel Scavenge、Parallel Old都会有STW,暂停所有应用线程
- JDK6、7、8都是以并行GC为默认GC
- -XX:+UseParallelGC 指定启动JVM使用并行GC
- -XX:ParallelGCThreads=N 通过该参数来指定 GC 线程数, 其默认值为 CPU 核心数
- 并行垃圾收集器适用于多核服务器,主要目标是增加吞吐量。因为对系统资源的更有效使用,所以能达到更高的吞吐量:
- 在 GC 期间,所有 CPU 内核都在并行清理垃圾,所以相同背景和条件下总暂停时间(STW)更短
- 在两次 GC 周期的间隔期,没有 GC 线程在运行,不会消耗任何系统资源
- 使用Parallel GC young区大小一般为-Xmx的1/3
JVM中的CMS GC(Mostly Concurrent Mark and Sweep Garbage Collector)
- CMS GC又叫并发GC,在这之后出现的所有GC如G1 GC、ZGC等都属于并发GC
- 并发是因为多个GC线程和应用线程在抢占CPU资源,所以不是并行
- 通过启动参数 -XX:+UseConcMarkSweepGC 可以指定当前Java进程使用CMS GC
- 对年轻代采用 多线程并发 mark-copy (标记-复制)算法 = ParNew
- 对老年代主要使用 多线程并发 分为六个阶段的 mark-sweep (标记-清除)算法 = CMS
- CMS GC 的设计目标是避免在老年代垃圾收集时出现长时间的卡顿,主要通过以下手段来达成此目标:
1.不对老年代进行整理,而是使用空闲列表(free-lists)来管理内存空间的回收
2.在老年代并发 mark-and-sweep (标记-清除) 阶段分为6个阶段,多数阶段GC线程和应用线程一起并发执行,这些阶段虽然没有暂停应用线程,但值得注意的是,GC线程会和应用线程争抢CPU
3.总是在young区对象比较少时开始对old区的清理 - 默认情况下,CMS 使用的并发线程数等于 CPU 核心数的 1/4
- 如果服务器是多核 CPU,并且主要调优目标是降低 GC 停顿导致的系统延迟,那么使用 CMS GC是个很明智的选择
- 进行老年代的并发回收时,可能会伴随着多次年轻代的 minor GC(ParNew)
- JVM使用CMS GC时 young区大小一般为64M * GC线程数 * 13/10(JDK8才如此,JDK9及以后和Parallel GC相同)
三色标记算法
- CMS 在并发标记阶段使用三色标记算法,三色标记算法是所有并发GC都用的标记算法
- 三色标记算法会产生漏标,CMS对此采取的办法是Increament Update,依然会产生漏标,所以后来在Java14被弃用
- CMS GC在G1出现前是惟一的并发标记算法,是并发GC的开创者,在JDK8(不包括JDK8)之前只能选CMS GC
对老年代的清理-CMS
Initial Mark(初始标记)
CMS GC六阶段之第一阶段是Initial Mark(初始标记)
- 这个阶段伴随着 STW 暂停
- 初始标记的目的是标记所有的根对象(GC ROOT),包括根对象直接引用的老年代对象,以及被年轻代中所有可达的存活对象所引用的老年代对象(通过JVM内的Remember Set记录跨代对象之间的引用关系,所以可以通过RSet找到这些引用了老年代对象的年轻代对象)
Concurrent Mark(并发标记)
CMS GC六阶段之第二阶段是Concurrent Mark(并发标记)
- 在此阶段,CMS GC 遍历老年代,标记所有的可达的老年代存活对象,从前一阶段 “Initial Mark” 找到的根对象开始算起
- “并发标记”阶段,就是GC线程与应用程序线程同时运行,不用暂停的阶段,所以如图所示会存在正在处理的对象,因此这个阶段可能会存在没有标记的可达存活对象(这块空间在标记后来了新对象)
- 使用了三色标记算法
Concurrent Preclean(并发预清理)
CMS GC六阶段之第三阶段是Concurrent Preclean(并发预清理)
- 此阶段同样是GC线程与应用线程并发执行的
- 因为前一阶段【并发标记】与程序并发运行,可能有一些引用关系已经发生了改变。如果在并发标记过程中引用关系发生了变化,JVM 会通过“Card(卡片)”的方式将发生了改变的区域标记为“脏”区,这就是所谓的 卡片标记(Card Marking)
Final Remark(最终标记)
CMS GC六阶段之第四阶段是Final Remark(最终标记)
- 最终标记阶段是此次 GC 事件中的第二次(也是最后一次)STW 停顿
- 本阶段的目的是完成老年代中所有存活对象的标记,所以通常 CMS 会尝试在年轻代尽可能空的情况下执行 Final Remark 阶段,以免连续触发多次该阶段的 STW 事件(young区存活对象越多,old区的对象数量也会有更大可能发生改变)
- 因为之前的预清理阶段是并发执行的,有可能 GC 线程跟不上应用程序的修改速度,所以需要一次 STW 暂停来处理各种复杂的情况
Concurrent Sweep(并发清除)
CMS GC六阶段之第五阶段是Concurrent Sweep(并发清除)
- 此阶段GC线程与应用程序线程并发执行,不需要 STW 停顿
- JVM 在此阶段删除不再使用的对象,并回收他们占用的内存空间
Concurrent Reset(并发重置)
此阶段GC线程与应用程序线程并发执行,重置 CMS 算法相关的内部数据,为下一次 GC 循环做准备
ParNew 与 Parallel Scavenge的区别
- ParNew 与 Parallel Scavenge大体相同,但是ParNew是在Parallel Scavenge基础上为了配合CMS做了增强、改动后的GC,与CMS共同组成CMS GC
JVM中的 G1 GC
- G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它
- G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的
- G1 GC 是一款软实时垃圾收集器,可以为其设置预期停顿时间的性能指标,为了达成可的指标,G1 GC 有一些独特的实现用来尽可能的实现该停顿时间指标
- JDK7开始出现但不成熟,JDK9及之后的默认GC都是G1 GC
- G1 GC采用三色标记算法进行并发标记,通过SATB解决漏标问题
- G1 GC物理上不分代,逻辑上分代,在G1 GC之后出现的GC都是不分代的GC
G1 GC堆内存的划分与回收集
- 堆不再分成年轻代和老年代,而是划分为多个(通常是2048个)可以存放对象的小块堆区域(smaller heap regions)。每个小块,可能一会被定义成 Eden 区,一会被指定为 Survivor区或者Old 区。在逻辑上,所有的 Eden 区和 Survivor 区合起来就是年轻代,所有的 Old 区拼在一起那就是老年代
- 这样划分之后,使得 G1 不必每次都去收集整个堆空间,而是以增量的方式来进行处理: 每次只处理一部分内存块(包含多个小块),称为此次 GC 的回收集(collection set),但是每次 GC 暂停都会收集其中所有年轻代的内存块,但一般只包含部分老年代的内存块
- G1 的另一项创新是,在并发阶段估算每个小堆块存活对象的总数。构建回收集的原则是: 垃圾最多的小块会被优先收集。这也是 G1 名称的由来
G1 GC–配置参数
-XX:+UseG1GC
:启用 G1 GC-XX:G1NewSizePercent
:初始年轻代占整个 Java Heap 的大小,默认值为 5%-XX:G1MaxNewSizePercent
:最大年轻代占整个 Java Heap 的大小,默认值为 60%- -XX:G1HeapRegionSize:设置每个 Region 的大小,单位 MB,需要为 1、2、4、8、16、32 中的某个值,默认是堆内存的1/2000。如果这个值设置比较大,那么大对象就可以进入 Region 了
-XX:ConcGCThreads
:与 Java 应用一起执行的 GC 线程数量,默认是 Java 线程的 1/4,减少这个参数的数值可能会提升并行回收的效率,提高系统内部吞吐量。如果这个数值过低,参与回收垃圾的线程不足,也会导致并行回收机制耗时加长-XX:+InitiatingHeapOccupancyPercent
(简称 IHOP):G1 内部并行回收循环启动的阈值,默认为 Java Heap的 45%。这个可以理解为老年代使用大于等于 45% 的时候,JVM 会启动垃圾回收。这个值非常重要,它决定了在什么时间启动老年代的并行回收-XX:G1HeapWastePercent
:G1停止回收的最小内存大小,默认是堆大小的 5%。GC 会收集所有的 Region 中的对象,但是如果下降到了 5%,就会停下来不再收集了。就是说,不必每次回收就把所有的垃圾都处理完,可以遗留少量的下次处理,这样也降低了单次消耗的时间-XX:G1MixedGCCountTarget
:设置并行循环之后需要有多少个混合 GC 启动,默认值是 8 个。老年代 Regions的回收时间通常比年轻代的收集时间要长一些。所以如果混合收集器比较多,可以允许 G1 延长老年代的收集时间-XX:+G1PrintRegionLivenessInfo
:这个参数需要和-XX:+UnlockDiagnosticVMOptions
配合启动,打印 JVM 的调试信息,每个Region 里的对象存活信息-XX:G1ReservePercent
:G1 为了保留一些空间用于年代之间的提升,默认值是堆空间的 10%。因为大量执行回收的地方在年轻代(存活时间较短),所以如果你的应用里面有比较大的堆内存空间、比较多的大对象存活,这里需要保留一些内存-XX:+G1SummarizeRSetStats
:这也是一个 VM 的调试信息。如果启用,会在 VM 退出的时候打印出 Rsets 的详细总结信息。如果启用-XX:G1SummaryRSetStatsPeriod
参数,就会阶段性地打印 Rsets 信息-XX:+G1TraceConcRefinement
:这个也是一个 VM 的调试信息,如果启用,并行回收阶段的日志就会被详细打印出来-XX:+GCTimeRatio
:这个参数就是计算花在 Java 应用线程上和花在 GC 线程上的时间比率,默认是 9,跟新生代内存的分配比例一致。这个参数主要的目的是让用户可以控制花在应用上的时间,G1 的计算公式是 100/(1+GCTimeRatio)。这样如果参数设置为9,则最多 10% 的时间会花在 GC 工作上面。Parallel GC 的默认值是 99,表示 1% 的时间被用在 GC 上面,这是因为 Parallel GC 贯穿整个 GC,而 G1 则根据 Region 来进行划分,不需要全局性扫描整个内存堆-XX:+UseStringDeduplication
:手动开启 Java String 对象的去重工作,这个是 JDK8u20 版本之后新增的参数,主要用于相同String 避免重复申请内存,节约 Region 的使用-XX:MaxGCPauseMills
:预期 G1 每次执行 GC 操作的暂停时间,单位是毫秒,默认值是 200 毫秒,G1 会尽量保证控制在这个范围内
G1 GC 的处理步骤 1-年轻代模式转移暂停(Evacuation Pause)
- GC 会通过前面一段时间的运行情况来不断的调整自己的回收策略和行为,以此来比较稳定地控制暂停时间。在应用程序刚启动时,G1 还没有采集到什么足够的信息,这时候就处于初始的 fully-young 模式
- 当年轻代空间用满后,应用线程会被暂停,年轻代内存块中的存活对象被拷贝到存活区。如果还没有存活区,则任意选择一部分空闲的内存块作为存活区
- 拷贝的过程称为转移(Evacuation),这和前面介绍的其他年轻代收集器是一样的工作原理(标记-复制算法)
G1 GC 的处理步骤 2-并发标记(Concurrent Marking)
- G1 GC 的很多概念建立在 CMS 的基础上,G1 并发标记的过程与 CMS 基本上是一样的
- G1 的并发标记通过 Snapshot-At-The-Beginning(起始快照)的方式,在标记阶段开始时记下所有的存活对象
- 即使在标记的同时又有一些变成了垃圾。通过对象的存活信息,可以构建出每个小堆块的存活状态,以便回收集能高效地进行选择,用来执行老年代区域的垃圾收集
有两种情况是可以完全并发执行的:
一、如果在标记阶段确定某个小堆块中没有存活对象,只包含垃圾,对于该小堆块会进入“阶段5”
二、在 STW 转移暂停期间,同时包含垃圾和存活对象的老年代小堆块会进行并发标记
并发标记阶段 1: Initial Mark(初始标记)
此阶段标记所有从 GC 根对象直接可达的对象(STW)
并发标记阶段 2: Root Region Scan(Root区扫描)
此阶段标记所有从 “根区域” 可达的存活对象。根区域包括:非空的区域,以及在标记过程中不得不收集的区域
并发标记阶段 3: Concurrent Mark(并发标记)
此阶段和 CMS 的并发标记阶段非常类似:只遍历对象图,并在一个特殊的位图中标记能访问到的对象
并发标记阶段 4: Remark(再次标记)
和 CMS 类似,这是一次 STW 停顿(因为不是并发的阶段),以完成标记过程。 G1 收集器会短暂地停止应用线程,停止并发更新信息的写入,处理其中的少量信息,并标记所有在并发标记开始时未被标记的存活对象
并发标记阶段 5: Cleanup(清理)
最后这个清理阶段为即将到来的转移阶段做准备,统计小堆块中所有存活的对象,并将小堆块进行排序,以提升GC的效率,维护并发标记的内部状态。 所有不包含存活对象的小堆块在此阶段都被回收了。有一部分任务是并发的:例如空堆区的回收,还有大部分的存活率计算。此阶段也需要一个短暂的 STW 暂停
G1 GC 的处理步骤 3- 混合模式转移暂停(Evacuation Pause (mixed))
- 并发标记完成之后,G1将执行一次混合收集(mixed collection),就是不只清理年轻代,还将一部分老年代区域也加入到回收集中
- 混合模式的转移暂停不一定紧跟并发标记阶段。有很多规则和历史数据会影响混合模式的启动时机。比如,假若在老年代中可以并发地腾出很多的小堆块,就没有必要启动混合模式,因此,在并发标记与混合转移暂停之间,很可能会存在多次 young 模式的转移暂停
- 具体添加到回收集的老年代小堆块的大小及顺序,也是基于许多规则来判定的。其中包括指定的软实时性能指标,存活性,以及在并发标记期间收集的 GC 效率等数据,外加一些可配置的 JVM 选项
- 混合收集的过程,很大程度上和前面的 fully-young gc 是一样的
G1 GC 的注意事项
特别需要注意的是,某些情况下 G1 触发了 Full GC,这时 G1 会退化使用 Serial 收集器来完成垃圾的清理工作,它仅仅使用单线程来完成 GC 工作,GC 暂停时间将达到秒级别的
- 并发模式失败
G1 启动标记周期,但在 Mix GC 之前,老年代就被填满,这时候 G1 会放弃标记周期
解决办法:增加堆大小,或者调整周期(例如增加线程数
-XX:ConcGCThreads
等)
- 晋升失败
没有足够的内存供存活对象或晋升对象使用,由此触发了 Full GC(to-space exhausted/to-space overflow)
解决办法:
a) 增加–XX:G1ReservePercent
选项的值(并相应增加总的堆大小)增加预留内存量
b) 通过减少–XX:InitiatingHeapOccupancyPercent
提前启动标记周期
c) 也可以通过增加–XX:ConcGCThreads
选项的值来增加并行标记线程的数目
- 巨型对象分配失败
当巨型对象找不到合适的空间进行分配时,就会启动 Full GC,来释放空间
解决办法:增加内存或者增大
-XX:G1HeapRegionSize
ZGC
ZGC 最主要的特点包括:
- GC 最大停顿时间不超过 10ms
- 堆内存支持范围广,小至几百 MB 的堆空间,大至 4TB 的超大堆内存(JDK13 升至 16TB)
- 与 G1 相比,应用吞吐量下降不超过 15%
- 当前只支持 Linux/x64 位平台,JDK15 后支持 MacOS 和Windows 系统
打开指令参数:如
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g
ShennandoahGC
- Shenandoah GC 立项比 ZGC 更早,设计为GC 线程与应用线程并发执行的方式,通过实现垃圾回收过程的并发处理,改善停顿时间,使得 GC 执行线程能够在业务处理线程运行过程中进行堆压缩、标记和整理,从而消除了绝大部分的暂停时间
- Shenandoah 团队对外宣称 Shenandoah GC 的暂停时间与堆大小无关,无论是 200 MB 还是 200 GB的堆内存,都可以保障具有很低的暂停时间(注意:并不像 ZGC 那样保证暂停时间在 10ms 以内)
各垃圾回收器对比汇总
串行:单个GC线程工作
并行:多个GC线程共同工作
并发:GC线程和应用程序业务线程同时工作
常用的垃圾回收器组合
常用的组合为:
(1)Serial GC = Serial+Serial Old 实现单线程的低延迟垃圾回收机制
(2)CMS GC = ParNew+CMS,实现多线程的低延迟垃圾回收机制
(3)Parallel GC = Parallel Scavenge + Parallel Old,实现多线程的高吞吐量垃圾回收机制,是JDK6、7、8的默认GC
垃圾回收器的选择
- 选择正确的 GC 算法,唯一可行的方式就是去尝试,一般性的指导原则:
- 如果系统考虑吞吐优先,CPU 资源都用来最大程度处理业务,用 Parallel GC
- 如果系统考虑低延迟有限,每次 GC 时间尽量短,用 CMS GC
- 如果系统内存堆较大,同时希望整体来看平均 GC 时间可控,使用 G1 GC
- 对于内存大小的考量:
- 一般 4G 以上,算是比较大,用 G1 的性价比较高
- 一般超过 8G,比如 16G-64G 内存,非常推荐使用 G1 GC
GC总结
总共是十种垃圾回收器
不同类型的GC
- 串行 GC(Serial GC): 单线程执行,应用需要暂停
- 并行 GC(ParNew、Parallel Scavenge、Parallel Old): 多线程并行地执行垃圾回收,关注与高吞吐
- CMS(Concurrent Mark-Sweep): 多线程并发标记和清除,关注与降低延迟
- G1(G First): 通过划分多个内存区域做增量整理和回收,进一步降低延迟
- ZGC(Z Garbage Collector): 通过着色指针和读屏障,实现几乎全部的并发执行,几毫秒级别的延迟,线性可扩展
- Epsilon: 实验性的 GC,供性能分析使用
- Shenandoah: G1 的改进版本,跟 ZGC 类似
GC的演变
- 串行 -> 并行: 重复利用多核 CPU 的优势,大幅降低 GC 暂停时间,提升吞吐量
- 并行 -> 并发: 不只开多个 GC 线程并行回收,还将 GC 操作拆分为多个步骤,让很多繁重的任务和应用线程一起并发执行,减少了单次 GC 暂停持续的时间,这能有效降低业务系统的延迟
- CMS -> G1: G1 可以说是在 CMS 基础上进行迭代和优化开发出来的,划分为多个小堆块进行增量回收,这样就更进一步地降低了单次 GC 暂停的时间
- G1 -> ZGC::ZGC 号称无停顿垃圾收集器,这又是一次极大的改进。ZGC 和 G1 有一些相似的地方,但是底层的算法 和思想又有了全新的突破
- 目前绝大部分 Java 应用系统,堆内存并不大比如 2G-4G 以内,而且对 10ms 这种低延迟的 GC 暂停不敏感,也就是说处理一个业务步骤,大概几百毫秒都是可以接受的,GC 暂停 100ms 还是 10ms 没多大区别。另一方面,系统的吞吐量反而往往是我们追求的重点,这时候就需要考虑采用并行 GC
- 如果堆内存再大一些,可以考虑 G1 GC。如果内存非常大(比如超过 16G,甚至是 64G、128G),或者是对延迟非常敏感(比如高频量化交易系统),就需要考虑使用新 GC(ZGC/Shenandoah)
四种Java引用与GC的影响
GC的特点
1.垃圾回收机制只回收JVM堆内存里的对象空间
2.对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
3.垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行
4.可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
5.程序中可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定
6.垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象),永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用