JVM-GC
# JVM-GC
# GC的作用域----方法区和堆

# 常见的垃圾回收算法
- 引用计数
缺点:
- 每次对象赋值后都要维护计数器,且计数器有一定的消耗
- 较难处理循环引用
- 复制
- 标记清除
- 标记整理(压缩)
引用计数

复制

标记清除

标记整理(压缩)

# GCRoot
# JVM垃圾回收的时候是怎么确定垃圾?知道什么是GCRoot?
- 什么是垃圾?
- 简单说就是内存中已经不再被使用到的空间就是垃圾
- 如何判断一个对象是否可以被回收?
引用计数

- Java中,引用和对象是有关联的。如果要操作对象则必须引用进行。因此,很显然一个简单的方法是通过引用计数来判断一个对象是否可以回收。
- 简单说,给对象添加一个计数器,每当有一个地方引用它,计数器值加1,每当有一个引用失效时,计数器值减1。
- 任何时刻计数器值为零时的对象就是不可能再被使用的,那么这个对象就是可回收对象。
- 主流的Java虚拟机没有选用这种算法,原因是它很难解决对象中间的循环引用的问题。
枚举根节点做可达性分析(根搜索路径)--GCRoot(复制,标记清除,标记压缩)

- 所谓GCRoot或者说tracing GC的"根集合"就是一组必须活跃的引用。
- 基本思路就是一系列的GCRoot的对象作为起点,从这个被称为GCRoot的对象开始向下搜索,如果一个对象到GCRoot没有任何引用链相连时,则说明此对象不可用。
- 即给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就判定为存活;没有遍历到的就自然判定为死亡。
- case

- Java中可以作为GCRoot的对象
- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中的引用对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
- GCRootDemo
public class GCRootDemo{ private byte[] byteArray = new byte[1024 * 1024 * 1024]; private static GCRootDemo2 t2; private static final GCRootDemo3 t3 = new GCRootDemo3(8; public static void m1(){ GCRootDemo t1 = new GCRootDemo(); System.gc(); System.out.println("第一次GC完成"); } public static void main(String[] args){ m1(); } }1
2
3
4
5
6
7
8
9
10
11
12
13
# 强,软,弱,虚引用分别是什么
整体架构

强引用(默认支持模式)
- 当内存不足,JVM开始进行垃圾回收,对于强引用对象,就算是出现了OOM也不会对该对象进行回收,死都不收。
- 强引用是最常见的普通对象引用,只要还有强引用还指向一个对象,就表明该对象还"活着",垃圾收集器就不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个变量,这个变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即时该对象永远也不会被用到,也不会被JVM回收。因此强引用是内存泄露的一个主要原因之一。
- 对于一个普通对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体的回收时机还是看垃圾收集策略)。
- Demo
public class StrongReferenceDemo { public static void main(String[] args) { //这样定义默认是强引用 Object obj1 = new Object(); //obj2引用赋值 Object obj2 = obj1; obj1 = null; System.gc(); //java.lang.Object@49476842 System.out.println(obj2); } }1
2
3
4
5
6
7
8
9
10
11
12软引用
- 软引用是相对强引用弱化了一些的引用,需要调用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
- 对于只有软引用的对象来说: 当系统内存充足它 不会 被回收 当系统内存不足它 会 被回收
- 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用就保留,不够用就回收。
- Demo
public class SoftReferenceDemo { public static void main(String[] args) { // systemMemoryEnough(); systemMemoryNotEnough(); } public static void systemMemoryEnough(){ Object obj = new Object(); SoftReference<Object> softReference = new SoftReference<>(obj); obj = null; System.gc(); System.out.println(obj); //内存够用,不会被回收 System.out.println(softReference.get()); } /** * 1.小内存,大对象,创建出内存不够的场景 * 2.-Xms5m -Xmx5m -XX:+PrintGCDetails */ public static void systemMemoryNotEnough(){ Object obj = new Object(); SoftReference<Object> softReference = new SoftReference<>(obj); System.out.println(obj); System.out.println(softReference); obj = null; try{ Byte[] bytes = new Byte[30 * 1024 *1024]; }catch(Exception e){ e.printStackTrace(); }finally{ System.out.println(obj); System.out.println(softReference); } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34弱引用
- 弱引用需要用java.lang.ref.WeakReferenge类来实现,它比软引用的生存期更短
- 对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
- Demo
public class WeakReferenceDemo { public static void main(String[] args) { Object obj = new Object(); WeakReference<Object> weakReference = new WeakReference<>(obj); System.out.println(obj); System.out.println(weakReference); obj = null; System.gc(); System.out.println(obj); //不管内存够不够,一律回收 System.out.println(weakReference.get()); } }1
2
3
4
5
6
7
8
9
10
11
12
13软引用和弱引用的应用
- 假如有一个应用需要读取大量的本地图片:如果每次读取图片都从硬盘读取则会严重影响性能,如果一次性全部加载到内存中又可能造成内存溢出。此时使用软引用可以解决这个问题。
- 设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
- Demo
Map<String,SoftReference<Bitmap>>imageCache =new HashMap<String,SoftReference<Bitmap>>();1WeakHashMap--key置为空,GC之后,WeakHashMap的记录也为空
- Demo
public class WeakHashMapDemo { public static void main(String[] args) { myHashMap(); System.out.println("================="); myWeakHashMap(); } public static void myHashMap(){ HashMap<Integer,String> hashMap = new HashMap<>(); Integer key = new Integer(1); String value = "HashMap"; hashMap.put(key,value); key = null; System.out.println(hashMap); System.gc(); System.out.println(hashMap); } public static void myWeakHashMap(){ WeakHashMap<Integer,String> weakHashMap = new WeakHashMap<>(); Integer key = new Integer(1); String value = "HashMap"; weakHashMap.put(key,value); key = null; System.out.println(weakHashMap); System.gc(); System.out.println(weakHashMap); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
虚引用(幽灵引用)
- 虚引用需要java.lang.ref.PhantomReference类来实现。
- 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。
- 如果一个对象仅持有虚引用,那么它就和没面任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
- 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。
- PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
- 换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。
- Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
- Demo
public class PhantomReferenceDemo { public static void main(String[] args) { Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(obj,referenceQueue); System.out.println(obj); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("=============================="); obj = null; System.gc(); try{ TimeUnit.SECONDS.sleep(1); }catch(Exception e){e.printStackTrace();} System.out.println(obj); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ReferenceQueue -相当于一种通知,将某个引用被添加到这个队列,就可以在被回收之前采取必要的行动
- Demo
public class ReferenceQueueDemo { public static void main(String[] args) { Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); WeakReference<Object> weakReference = new WeakReference<>(obj,referenceQueue); System.out.println(obj); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); obj = null; System.gc(); try{ TimeUnit.SECONDS.sleep(1); }catch(Exception e){e.printStackTrace();} System.out.println("============================="); System.out.println(obj); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# GCRoot和四大引用总结

# 垃圾收集器
- GC垃圾回收算法和垃圾收集器的关系?
- 垃圾回收算法是方法论,垃圾收集器是方法论的落地实现----相当于接口和实现类的关系
- 目前为止还没有完美的收集器实现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分区收集。
# 垃圾回收的方式有哪些(Java8)

- 串行垃圾回收器(Serial)
- 它为单线程环境设计并且只用一个线程进行垃圾回收,会暂停所有的用户线程,不适合服务器环境。
- 并行垃圾回收器(Parallel)
- 多个垃圾收集器并行工作,会暂停所有的用户线程,适用于科学计算/大数据首台处理等弱交互场景。
- 并发垃圾回收器(CMS)
- 用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行),不需要暂停用户线程,互联网公司多用它,适用于对响应时间有要求的场景。
- 小结
- G1垃圾回收器
- G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
# 如何查看默认的垃圾收集器
- java -XX:+PrintCommandLinesFlags -version
- java -XX:+PrintFlagsInitial
- java -XX:+PrintFlagsFinal
- 进程号
- jps -l
- jinfo -flag UseSerialGC/UseParallelGC/... 进程号
- jinfo -flags 进程号
# JVM默认的垃圾收集器有哪些(七大垃圾收集器)

- UseSerialGC
- UseSerialOldGC(Java8废弃)
- UseParallelGC
- UseConcMarkSweepGC
- UseParNewGC
- UseParallelOldGC
- UseG1GC
# 垃圾收集器配置
- 部分参数
- DefNew - Default New Generation
- Tenured - Old
- ParNew - Parallel New Generation
- PSYoungGen - Parallel Scavenge
- ParOldGen - Parallel Old Generation
- Server/Client模式分别是什么?

- 一般只用Server,Client基本不用
- 操作系统:
- 32位Window操作系统,不论硬件如何都默认使用Client的JVM模式
- 32位其它操作系统,2G内存同时有2个cpu以上用Server模式,低于该配置还是Client模式
- 64位only server模式
- 新生代
- 串行GC(Serial)/(Serial Copying)
- 一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
- 串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集过程中可能会产生较长的停顿(Stop-The-World状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
- 对应JVM参数是:-XX:+UseSerialGC开启后会使用:Serial(Young区)+Serial Old(Old区)的收集器组合
- 表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
- Demo -- -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC
- 并行GC(ParNew)
- 使用多线程进行垃圾回收,在垃圾收集时,会Stop-the-World暂停其他所有的工作线程直到它收集结束。
- ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。它是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。
- 常用对应JVM参数:-XX:+UseParNewGC启用ParNew收集器,只影响新生代的收集,不影响老年代开启上述参数后,会使用:ParNew(Young区用)+Serial Old的收集器组合,新生代使用复制算法,老年代采用标记-整理算法
- 但是,ParNew+Tenured这样的搭配,java8已经不再被推荐。
- Java HotSpot(TM)64-Bit Server VM warning:Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release
- -XX:ParallelGCThreads 限制线程数量,默认开启和CPU数目相同的线程数
- Demo -- -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParNewGC
- 并行回收GC(Parallel)/(Parallel Scavenge)
- Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。一句话:串行收集器在新生代和老年代的并行化
- 可控制的吞吐量(Thoughput=运行用户代码间/(运行用户代码时间+垃圾收集时间),也即比如程序运行100分钟,垃圾收集时间1分钟,吞吐量就是99%)。高吞吐量意味着高效利用CPU的时间,它多用于在后台运算而不需要太多交互的任务
- 自适应调节策略也是ParallelScavenge 收集器与ParNew收集器的一个重要区别。(自适应调节策略:虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:MaxGCPauseMils)或最大的吞吐量。
- 常用JVM参数:-XX+UseParallelGC或-XX:+UseParallelOldGC(可互相激活)使用Parallel Scanvenge收集器 -XX:ParallelGCThreads=数字N 表示启动多少个GC线程 cpu>8 N=5/8 cpu<8 N=实际个数
- 串行GC(Serial)/(Serial Copying)
- 老年代
- 串行GC(Serial Old)/(Serial MSC)
- Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。
- 在Server模式下,主要有两个用途(版本已经到8及以后):
- 1.在JDK1.5之前版本中与新生代的Parallel Scavenge 收集器搭配使用。(Parallel Scavenge+Serial Old)
- 2.作为老年代版中使用CMS收集器的后备垃圾收集方案。
- 并行GC(Parallel Old)/(Parallel MSC)
- Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Paralel Old收集器在JDK1.6才开始提供。
- JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。在JDK1.6之前(Parallel Scavenge+Serial Old)
- Parallel Old正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,JDK1.8后可以优先考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略。在JDK1.8及后(Parallel Scavenge+Parallel Old)
- VM常用参数:-XX:+UseParallelOldGC 使用Parallel Old收集器,设置该参数后,新生代Parallel+老年代Parallel Old
- 并发标记清除GC(CMS)
- CMS收集器(Concurrent Mark Sweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器
- 适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。
- CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
- Concurrent Mark Sweep并发标记清除,并发收集低停顿,并发指的是与用户线程一起执行。
- 开启该收集器的JVM参数:-XX:+UseConcMarkSweepGC开启该参数后会自动将-XX:+UseParNewGC打开开启该参数后,使用ParNew(Young区用)+CMS(Old区用)+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
- -Xms10m -Xm×10m -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
- 4步过程
- 初始标记(CMS initial mark)
- 只是标记一下GCRoots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
- 并发标记(CMS concurrent mark)和用户线程一起
- 进行GCRoots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。
- 重新标记(CMS remark)
- 为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
- 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正。
- 并发清除(CMS concurrent sweep)和用户线程一起
- 清除GCRoots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。
- 由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的存回收和用户线程是一起并发地执行。
- 初始标记(CMS initial mark)
- 优缺点
- 优点
- 并发收集低停顿
- 缺点
- 并发执行,cpu压力大
- 由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间。
- 采用的标记清除算法会导致大量碎片
- 标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
- 并发执行,cpu压力大
- 优点
- 串行GC(Serial Old)/(Serial MSC)
- Demo
/**
* 1.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseSerialGC (DefNew+Tenured)
* 2.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseParNewGC (ParNew+Tenured)
* 备注情况:Java Hotspot(TM)64-Bit Server M warning:
* Using the Parwew young collector with the serial old collector is deprecated
* and will likely be removed in a future release
* 3.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseParallelGC (PSYoungGen+ParOldGen)
* 4.
* 4.1
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseParallelOldGC (PSYoungGen+ParOldGen)
* 4.2 不加就是默认-XX:+UseParallelGC
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags (PSYoungGen+ParOldGen)
* 5.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseConcMarkSweepGC (par new generation+concurrent mark-sweep)
* 6.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseG1GC
* 7.-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseSerialOldGC Java8之后没有了
*
* PS:
* 配多个参数有时候是多此一举
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseParallelGC -XX:+UseParallelOldGC (PSYoungGen+ParOldGen)
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandlineFlags -XX:+UseParNewGC -XX:+UseConcMarkSweepGC (par new generation+concurrent mark-sweep)
*/
public class GCDemo {
public static void main(String[] args) {
System.out.println("=======================Hello GC");
try{
String str = "test GC collector";
while(true){
str += str + new Random().nextInt(11111111) + new Random().nextInt(22222222);
str.intern();
}
}catch(Throwable e){
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 如何选择垃圾收集器

- 组合的选择
- 单cpu或小内存,单机程序
- -XX:+UseSerialGC
- 多cpu,需最大吞吐量,如后台计算应用
- -XX:+UseParallelGC或者-XX:+UseParallelOldGC
- 多cpu,追求低停顿时间,需快速响应如互联网应用
- -XX:+UseConcMarkSweepGC
- -XX:ParNewGC
- 单cpu或小内存,单机程序
# G1(garbage-first) GC
- 以前收集器的特点
- 年轻代和老年代是各自独立且连续的内存块;
- 年轻代收集使用单eden+S0+S进行复制算法;
- 老年代收集必须扫描整个老年代区域;
- 都是以尽可能少而快速地执行GC为设计原则。
- G1是什么
- G1(Garbage-First)收集器,是一款面向服务端应用的收集器
- 从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。另外,它还具有以下特性:
- 像CMS收集器一样,能与应用程序线程并发执行。
- 整理空闲空间更快。
- 需要更多的时间来预测GC停顿时间。
- 不希望牺牲大量的吞吐性能。
- 不需要更大的Java Heap。
- G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
- G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
- G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
- CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又保留CMS垃圾收集器低暂停时间的优点,JAVA7发布了一个新的垃圾收集器-G1垃圾收集器。
- G1是在2012年才在jdk1.7u4中可用。oracle官方计划在jdk9中将G1变成默认的垃圾收集器以替代CMS。它是一款面向服务端应用的收集器,主要应用在多CPU和大内存服务器环境下,极大的减少垃圾收集的停顿时间,全面提升服务器的性能,逐步替换java8以前的CMS收集器。
- 主要改变是Eden,Survivor和Tenured等内存区域不再是连续的了,而是变成了一个个大小一样的region,每个region从1M到32M不等。一个region有可能属于Eden,Survivor或者Tenured内存区域。
- 特点:
- 1:G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
- 2:G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
- 3:宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘。
- 4:G1收集器里面讲整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
- 5:G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换。
- 底层原理
- Region区域化垃圾收集器 - 最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
- 区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。
- 核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,在堆的使用上,G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
- 大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为:32MB*2048=65536MB=64G内存


- G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
- 这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。
- 回收步骤
- 针对Eden区进行收集,Eden区耗尽后会被触发,主要是小区域收集+形成连续的内存块,避免内存碎片
- *Eden区的数据移动到Survivor区,假如出现Survivor区空间不够,Eden区数据会部会晋升到Old区
- *Survivor区的数据移动到新的Survivor区,部会数据晋升到Old区
- *最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。


- 4步过程
- 初始标记:只标记GC Roots能直接关联到的对象
- 并发标记:进行GC Roots Tracing的过程
- 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
- 筛选回收:根据时间来进行价值最大化的回收
- Region区域化垃圾收集器 - 最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
- Case案例-同七大垃圾回收器Case
- 常用配置参数
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=n
- 设置的G1区域的大小。值是2的幂,范围是1MB到32MB。目标是根据最小的Java堆大小划分出约2048个区域。
- -XX:MaxGCPauseMillis=n
- 最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
- -XX:InitiatingHeapOccupancyPercent=n
- 堆占用了多少的时候就触发GC,默认为45
- -XX:ConcGCThreads=n
- 并发GC使用的线程数
- -XX:G1ReservePercent=n
- 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认10%
- 开发常用
- -XX:+UseG1GC -Xmx32g -XX:MaxGCPauselMilis=100 -XX:MaxGCPauseMilis=n:最大GC停顿时间单位毫秒,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间
- 和CMS相比的优势
- G1不会产生内存碎片。
- 是可以精确控制停顿。该收集器是把整个堆(新生代、老生代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
编辑 (opens new window)
上次更新: 2024/05/24, 11:36:46

