一.如何判断对象已死
垃圾回收器并不是java独有的,垃圾回收器的作用就是回收对象释放内存空间,那么如何判断哪些对象应该被回收呢?
在Java语言中是采用GC Roots来解决这个问题。如果一个对象和GC Roots之间没有链接,那么这个对象也可以被视作是一个可回收的对象。
Java中可以被作为GC Roots中的对象有:
虚拟机栈中的引用的对象。
方法区中的类静态属性引用的对象。
方法区中的常量引用的对象。
本地方法栈(jni)即一般说的Native的引用对象。
二.垃圾回收器
Serial收集器
Serial收集器是最古老的收集器,它的缺点是当Serial收集器想进行垃圾回收的时候,必须暂停用户的所有进程,即stop the world。到现在为止,它依然是虚拟机运行在client模式下的默认新生代收集器,与其他收集器相比,对于限定在单个CPU的运行环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾回收自然可以获得最高的单线程收集效率
ParNew收集器
ParNew收集器是Serial收集器新生代的多线程实现,注意在进行垃圾回收的时候依然会stop the world,只是相比较Serial收集器而言它会运行多条进程进行垃圾回收
Parallel Scavenge收集器
Parallel是采用复制算法的多线程新生代垃圾回收器,似乎和ParNew收集器有很多的相似的地方。但是Parallel Scanvenge收集器的一个特点是它所关注的目标是吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务
CMS收集器
CMS(Concurrent Mark Swep)收集器是一个比较重要的回收器,现在应用非常广泛,我们重点来看一下,CMS一种获取最短回收停顿时间为目标的收集器,这使得它很适合用于和用户交互的业务。从名字(Mark Swep)就可以看出,CMS收集器是基于标记清除算法实现的。它的收集过程分为四个步骤:
1. 初始标记(initial mark)
2. 并发标记(concurrent mark)
3. 重新标记(remark)
4. 并发清除(concurrent sweep)
注意初始标记和重新标记还是会stop the world,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作
G1收集器
G1收集器是一款面向服务端应用的垃圾收集器。HotSpot团队赋予它的使命是在未来替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:
1. 并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。
2. 分代收集:和其他收集器一样,分代的概念在G1中依然存在,不过G1不需要其他的垃圾回收器的配合就可以独自管理整个GC堆。
3. 空间整合:G1收集器有利于程序长时间运行,分配大对象时不会无法得到连续的空间而提前触发一次GC。
4. 可预测的非停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
在使用G1收集器时,Java堆的内存布局和其他收集器有很大的差别,它将这个Java堆分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
虽然G1看起来有很多优点,实际上CMS还是主流
三.垃圾回收算法
标记清除法
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。
在标记阶段首先通过根节点(GC Roots),标记所有从根节点开始的对象,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
适用于存活对象较多的情况,适用于老年代
缺点:容易产生内存碎片,再来一个大对象时会提取触发垃圾回收,效率不高(扫描了两次空间)
复制算法
从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存(图中下边的那一块儿内存)上去,之后将原来的那一块儿内存(图中上边的那一块儿内存)全部回收掉。
适用对象存活空间的较少的新生代,解决了内存碎片的问题,提高了回收效率(相对于标记清除算法)但是需要一块空闲的空间,需要复制移动对象
标记整理算法
复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。
这种情况在新生代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高
标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。
首先也需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高
四.引用类型
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中
五.与GC相关的常用参数
· -Xmx: 设置堆内存的最大值。
· -Xms: 设置堆内存的初始值。
· -Xmn: 设置新生代的大小。
· -Xss: 设置栈的大小。
· -PretenureSizeThreshold: 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
· -MaxTenuringThrehold: 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就会加1,当超过这个参数值时就进入老年代。
· -UseAdaptiveSizePolicy: 在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作。
· -SurvivorRattio: 新生代Eden区域与Survivor区域的容量比值,默认为8,代表Eden: Suvivor= 8: 1。
· -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
· -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。
· -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集
转自: https://www.toutiao.com/i6658035031186866696/