5

1、JVM模板

-Xms4096M -Xmx4096M -Xmn3072M -Xss1M  -XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled 
-XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/usr/local/app/oom

年轻代
-XX:MaxTenuringThreshold,默认值是15,多少次垃圾回收进入老年代
动态年龄判断
-XX:PretenureSizeThreshold 大对象,直接进入老年代
S区放不下
空间分配担保机制,老年代可用空间 > 新生代所有存活对象,yong gc,老年代可用空间 > 历史yong gc平均大小,yong gc,否则full gc

老年代
-XX:CMSInitiatingOccupancyFraction,大于该值

碎片整理
-XX:+UseCMSCompactAtFullCollection

执行多少次Full GC进行一次内存碎片整理
-XX:CMSFullGCsBeforeCompaction=0

-XX:+CMSParallelInitialMarkEnabled 初始标记开启多线程并发执行,默认是true

-XX:+CMSScavengeBeforeRemark 在CMS的重新标记阶段之前,先尽量执行一次young gc(避免大量扫描)

-XX:+DisableExplicitGC 是防止System.gc()去随便触发GC,高峰情况下,调用System.gc()会发生Full GC

-XX:MetaspaceSize 默认20M,反射 jdk,cglib动态生成类,推荐512MB

-Xms -> ms是memory start简称,-Xmx mx是memory max的简称

-XX:+PrintTLAB 加这个参数可以看到 TLAB的分配,其中 refills 申请TLAB次数,slow allocs : 慢速分配的次数

XX:+ExplicitGCInvokesConcurrent 和 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 参数来将 System.gc 的触发类型从 Foreground 改为 Background

CMS GC 共分为 Background 和 Foreground 两种模式,前者就是我们常规理解中的并发收集,可以不影响正常的业务线程运行,但 Foreground Collector 却有很大的差异,他会进行一次压缩式 GC。此压缩式 GC 使用的是跟 Serial Old GC 一样的 Lisp2 算法,其使用 Mark-Compact 来做 Full GC,一般称之为 MSC(Mark-Sweep-Compact),它收集的范围是 Java 堆的 Young 区和 Old 区以及 MetaSpace。由上面的算法章节中我们知道 compact 的代价是巨大的,那么使用 Foreground Collector 时将会带来非常长的 STW

Java7 之后常量池等字面量(Literal)、类静态变量(Class Static)、符号引用(Symbols Reference)等几项被移到 Heap 中

2、查看JVM中的参数

java -XX:+PrintFlagsFinal -version
看JVM所有可以设置的参数

3、String.intern()

简单理解就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,
如果有,则返回其引用,如果没有,则在常量池中添加一个Unicode等于str的字符串并返回它的引用(所以注意 s1.intern()是没有用的,需要的是s1 = s1.intern())

jdk1.7之后,字符串常量池已经转移到堆区,常量还是在元数据区中

4、栈内存溢出

栈是线程私有,它的生命周期和线程相同。每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,方法调用
的过程就是栈帧入栈和出栈的过程
在Java虚拟机规范中,对虚拟机栈定义了两种异常:
1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
2、如果虚拟机栈可以动态扩展,并且扩展时无法申请到足够内存,抛出OutOfMemeoryError异常
栈对应线程,栈帧对应方法

5、JVM内存区域

线程共享 : 堆、元数据区
线程私有化 : 虚拟机栈、本地方法栈和程序计数器

元数据空间和永久代类似,都是对JVM规范中方法区的实现。不过元数据空间与永久代之间最大的区别在于 : 元数据空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元数据空间的大小仅受本地内存限制

本地方法栈与虚拟机栈发挥的作用是类似的,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机适应到的Native方法服务

程序计数器 : 当前线程锁执行的字节码的行号指示器,为了线程切换后能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储

6、xx.java文件是怎么加载到JVM中的

xx.java -> xx.class -> 类加载器 -> JVM
一个类从加到到使用,一般会经历下面的过程 :
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
加载 : 全限定查找此类字节码文件,创建一个Class对象
验证 : 是否符合规范
准备 : 静态变量初始化,比如说 public static int flushInterval; 为0,不包含final修饰的static,final在编译的时候已经分配了
类变量会分配在方法区中,实例变量是随着对象一起分配到Java堆中
解析 : 符号引用变为直接引用
初始化 : 静态方法和静态代码块执行(比如MySQL Driver staic方法向DriverManager 注册驱动)
什么时候初始化类呢 ?
1、new 类()
2、Class.forName()
3、classLoader.loadClass

7、类加载器和双亲委派机制

启动类加载器 : lib包下核心类库,C++写
扩展类加载器 : 默认是 jre/lib/ext,可以通过 -Djava.ext.dir 指定
应用类加载器 : ClassPath环境变量所指定的路径中的类,java -cp 指定classpath路径
自定义类加载器
双亲委派机制,从下往上找,防备下层的类加载器把父类的类重写了,比如说String,沙箱安全机制

第三方包加载方式,反向委派机制,说清楚就是核心代码在rt.jar中,实现类用户自已定义,比如SPI,这种情况下我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器(双亲委派的破坏者),就是很好的选择,Thread.currentThread().getContextClassLoader(),也就是
classpath中的jar

Tomcat类加载器 :
BootstarpClassLoader -> ExtClassLoader -> AppClassLoader -> CommonClassLoader

CommonClassLoader 并行分为 CatalinaClassLoader 和 ShareClassLoader
ShareClassLoader 下面 WepAppClassLoader

CatalinaClassLoader 用于隔离 Tomcat本身和Web
ShareClassLoader 本质是两个Web应用之间怎么共享类,并且不能重复加载相同的类

Tomcat的自定义类加载WebAppClassLoader打破了双亲委派机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载Web应用自己定义的类。具体实现就是重新ClassLoader的两个方法:findClass和loadClass,loadClass调用了findClass方法

破坏双亲委派的点在于,findClass中是优先使用 WebAppClassLoader 来加载,而不是一直双亲委派依次向上找,找不到再向上找
loadClass 其实还是遵循 启动类加载器、扩展类加载的,没有,使用 WebAppClassLoader,在没有向上找

8、为什么要分代?以及不同代不同的垃圾回收机制?

分代其实为了关联不同的垃圾回收机制,年轻代大部分被回收,所以复制算法,存活少,复制活的对象到S区,剩余直接清理

老年代存活对象多,所以标记+清除+整理

9、方法区内会不会进行垃圾回收?

首先,该类的所有实例对象都已经从Java堆内存里被回收
其次,加载这个类的ClassLoader已经被回收
最后,对该类的Class对象没有任何引用
只要满足以上条件,方法区类的元数据信息就会被回收

使用 URLClassLoader 进行类加载器,最后进行URLClassLoader close方法,通过重写 finalize 打印方法查看是否被回收
最好的方式就是让所有的不管是加载的clazz,还是ClassLoader,还是实例化的对象都为null,然后close ClassLoader,就肯定不会元数据区溢出

10、谁才能作为GC Root?

局部变量和静态变量,为什么实例变量不行?实例变量是不是属于堆对象中,一定是需要外界引用,外界引用它只能是局部变量和静态变量

11、引用类型

强、软、弱和虚引用
强是什么?就是宁可JVM内存溢出,我也要存在
软是什么?内存不足,壮烈牺牲
弱是什么?只要GC(前提是没有强引用哈),就要销毁
虚是什么?虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制,虚引用必须和引用队列同时使用

12、ParNew + CMS(STW)

ParNew对应新生代,复制算法,不能工作
CMS对应老年代,标记 + 清理,系统一边工作一边清理
1、初始标记,快,从GC Root出发
2、并发标记,慢,相当于是递归追踪,无所谓,程序是可以运行的
3、重新标记,快,增量标记
4、并发清理,慢,垃圾回收
不使用的场景,大机器内存,比如说64G,给JVM 32G,回收需要很长时间

13、为啥老年代的Full GC要比新生代的Yong GC慢很多,一般在10倍以上?

新生代 :
新生代执行速度其实很快,因为直接从GC Roots触发就追踪哪些对象是或的就行了,新生代对象存活是很少的,这个速度是极快的,不需要追踪多少对象
其实年轻代GC几乎没什么好调优的,因为他的运行逻辑非常简单,就是Eden一旦满了无法放心对象就触发一次GC,一般来说,真要对年轻代GC调优,
只要你给系统分配足够的内存即可,核心点点在于堆内存的分配、新生代内存的分配

老年代 :
存活对象很多,并发清理阶段,不是一次性回收一大批内存,而是找零零散散在各个地方的垃圾对象,完事了,还要一次碎片整理

14、什么时候会触发Concurrent Mode Failure ?

说白了其实就是比如说在并发清理的时候,我在清理数据,你比如说新生代发生yong gc,你要往老年代放数据,抱歉,放不了,我正在垃圾回收呢?
所以现在这个情况就会出现 Concurrent Mode Failure,然后CMS切换为Serial Old,直接禁止程序运行

15、G1(Garbage-First,G1)

优先回收垃圾,不会等到空间全部占满,然后进行回收

把Java堆内存拆分为多个大小相等的Region(最多2048个region),G1新生代和老年代是逻辑上的概念,设置一个垃圾回收的预期停顿时间
计算回收价值,比如说1个Region中的垃圾对象有10M,回收需要1s;另外一个Region,垃圾20M,200ms
核心理念 : 最少回收时间和最多回收对象的Region进行垃圾回收

大对象在G1中比较特殊,有特殊的region来保存,在新生代或者老年代发生gc的时候,会顺带大对象回收

-XX:UseG1GC
-XX:G1HeapRegionSize
-XX:G1MaxNewSizePercent 5%~60%
-XX:MaxGCPauseMillis 默认200ms
-XX:InitiatingHeapOccupancyPercent 45%
-XX:G1MixedGCCountTarget 混合回收的过程中,最后一个阶段执行几次混合回收,默认是8次
-XX:G1HeapWastePercent 默认值是5%,混合回收,一旦空闲出来Region数量达到了堆的5%,立即停止混合回收
-XX:G1MixedGCLiveThresholdPercent 默认值是85%,确定要回收的Region的时候,必须是存活对象低于85%的Region才可以进行回收,如果大于不会放到CSet中
-XX:GCTimeRatio GC与应用的耗费时间比,如果G1 GC时间与应用运行的时间占比不超过10%的时候,不需要动态扩展

使用场景 : 大内存的场景
Mixed GC(混合回收),G1特有
新生代分区、自由分区、老年代分区和大对象分区
G1不能手动指定分区个数
停顿预测模型 + 动态调整机制保障我们GC百分之九十能够保持在某个停顿时间内的关键

16、常用命令

jstack -l pid 要在某个用户启动情况下执行,查看线程情况
jstat -gc pid period count 看每个代的容量变化
jmap -histo pid 对象分布

17、有哪些溢出?

栈溢出、堆溢出、元数据溢出、堆外内存溢出

栈溢出 : 递归
堆溢出 : 就是内存不能再创建对象
元数据溢出 : JDK反射或者CGLIB反射,根本原因是ClassLoader一直加载类,最终元数据空间放满
堆外内存溢出 :

场景1 : 瞬时大量请求,DirectByteBuffer,其实会是堆和堆外有一个映射关系,创建大量堆外内存,不能创建了溢出
场景2 : 新生代设置不合理,每次,young gc,一些请求未执行完毕,当然就会进入老年代,而老年代设置的又比较大,这一直创建堆外内存,而且老年代比较大,又不回收
最终就会导致堆外内存溢出
怎么解决呢?每次分配新的堆外内存的时候,都会调用System.gc()去提醒JVM主动执行一下gc去回收掉一些垃圾没人引用的DirectByteBuffer对象,释放堆外内存空间
但是不能设置 -XX:+DisableExplicitGC

18、G1为什么要满足新生代的动态扩展?

动态调整内存分区的占比,来满足回收时间。如果不做动态调整,那么GC时间过长,就没法满足停顿时间。动态增加、减少,才能调整到一个合理的值

扩展机制 : 新生代分区列表 + 自由分区 + 扩展分区

19、停顿预测模型

衰减标准差算法,简单来说其实就是线程加权(距离本地预测越近的GC影响比重就占的越高才行)

20、TLAB

LAB(Thread Local Allocation Buffer,指针碰撞法来分配对象,dummy对象填充碎片空间)
对象分配很复杂,需要锁整个堆,效率低下。所以G1采取的就是本地缓冲区的思想来分配的,每个线程都有一个自己线程的本地分配缓冲区
这个缓冲区,保证了一个线程过来的时候,尽可能的走这个TLAB来分配对象,能够快速分配,并实现了无锁化分配

TLAB是和线程对象的,一个进行启动的线程是有数的,几个线程就锁几次堆,使用CAS来分配TLAB
TLAB太小,会导致TLAB快速被填满,从而导致对象不走TLAB分配,效率变差
如果TLAB过大,造成内存碎片

假如TLAB满了(refill_waste,可以浪费空闲空间,如果剩余小于等于这个值,认为已经满了),无法分片对象了,会怎么处理?
在G1中,就是说,如果说无法分片对象了,就优先去申请一个新的TLAB来存储对象
如果无法申请新的TLAB,才有可能会走堆内存加锁,然后直接在堆上分配对象的逻辑

一边运行一边调整refillwaste和TLAB大小
系统程序 -> 分配对象 -> 对象需要空间大于refill_waste(堆内存直接分配) -> TLAB剩余内存够分配对象吗?(直接TLAB分配)
-> 申请一个新的TLAB ->分配对象

21、快速分配和慢速分配

简说快速分配其实就是走TLAB,慢速分配不走TLAB(慢速分配需要加锁,甚至可能要涉及到GC的过程,所以速度非常慢)

慢速分配
1、去创建新的TLAB
2、扩展分区
3、垃圾回收

ObjSize > regionSize/2的时候,就成为大对象,同时设置TLAB的最大值,限定在最大为regionsize/2,这样子,大对象就一定
大于TLAB的大小,所有可以直接走慢速分配,为什么呢?很简单,一个大对象就占满了TLAB,会造成其他普通的对象进入慢速分配

使用TLAB进行快速分配的过程,第一次进入慢速分配,扩展空间失败的时候,就是ygc活着mixed gc,再次进行慢速分配,有可能还会执行gc,
在分配过程中执行的这个ygc或者mixed gc,慢速分配也失败的时候,就会进入最终的尝试,最终尝试执行两次full gc,一次不回收软引用,
一次回收软引用

22、为了提升GC的效率,G1设置了哪些核心机制?

1、Rset记忆集 Memember Set
G1回收 young gc -> mixed gc -> full gc

新生代对象不一定只有新生代引用,有可能会有老年代的对象引用新生代的对象。那么我们直接在触发新生代gc的时候,我们在老年代的里面有一些对象也在我们的
引用链中。RSet记录了夸代引用的引用关系,在gc的时候,可以快速借助记忆集 + GC Roots搞定同代引用及跨代引用的可达对象分析问题

RSet记忆集的维度是对每一个region,都搞一块内存,存储region里面所有的对象被引用关系

RSet中只保存老年代到新生代的引用(young gc使用,不要在young gc的时候遍历老年代)
老年代到老年代的引用,mixed gc只是选择部分的region进行回收,如果通过RSet是最快的,而不是GC Root一直递归找

2、位图(BitMap),G1采取了位图的方式描述内存是否使用了
3、卡表,G1中,卡表是用一个字节(8位)来描述512字节的空间的使用情况,及内存被谁使用了。JVM使用了RSet+卡表来作为分代模型中,跨带引用关系不好判定,不好追踪问题的解决思路

耗时操作 : 初始标记、并发标记、重新标记和并发清理,最耗时其实就是引用关系不好判定,内存是否使用不好判定

首先,初始标记过程,要从GC Roots触发,标记所有直接被引用的关系是吧?然后再并发标记阶段,追踪所有间接被引用的对象,如果出现跨代引用,比如我新生代对象
被老年代引用了,我肯定不能被回收啊,那么RSet就避免了对老年代的遍历

引用关系怎么找?RSet里肯定不能直接记录哪个对象引用了哪个对象,不然一个系统1000w个对象,引用关系还特别复杂,可能要记录很多遍,那岂不是一个RSet比整个系统
占用的内存还要大?所以,这个时候cardTable就出现了,cardTable里面可以用一个字节来描述512字节内存的引用关系,那么RSet里面直接记录cardTable相关内容这一样可以节省很多内存
比如,我发现老年代有一块512B的空间里的对象引用了新生代的一个对象,RSet直接记录这个512B的空间开标里面的位置就OK了

位图和并发标记是息息相关的,简单来说,就是在并发标记阶段,可以借助位图描述内存使用情况,避免内存使用冲突的问题,也避免GC线程无效遍历一些未使用的内存

新生代引用老年代,一般不需要记录为什么?
我们要知道,gc回收过程是没有老年代单独的回收的,所以如果要回收老年代的时候,肯定是会带着一次新生代回收,或者直接full gc,所以不需要记录

卡表其实就是位图思想的一种实现方式,只是粒度不同罢了。位图用每一位来描述对应数据的装他,卡表,其实就是按照位图的思想,用一个字来描述512字节的内存状态,
引用等相关数据,Rset记录的是卡表地址

23、RSet更新

如果引用关系发生了改变,RSet是否需要更新,应该怎么更新?
要知道每个Region中是由一个RSet的,对象更新,如果加锁的方式更新RSet,会造成性能问题

JVM核心是 : 对象分配和垃圾回收
对象分配是不需要RSet的,因为我们在分配一个对象的时候,值需要看内存够不够,剩余空间是否能够分配一个对象,分配完成以后,直接更新一下内存的使用情况就OK了,并不需要借助RSet
RSet本身就是为了垃圾回收的时候更加方便,不需要遍历所有空间而设计的,所以RSet即使需要更新,而我们没有把他更新,其实也无所谓,因为不影响程序运行,只是在垃圾回收的时候,我一定需要更新RSet,不然就会报错

G1的脏数据队列异步消费,更新RSet。G1设计了一个队列,叫做DCQ(Dirty Card Queue)。在每次引用关系变更的时候,就把这个变更操作,发动一个消息到DCQ里面,然后又一个专门的线程去异步消费(refine线程)

针对DCQ G1是设计了二级缓存来解决并发冲突的
第一层缓存是在线程这一层,也就是说,DCQ其实是属于每一个线程的,也就是说,每一个工作线程都会关联一个DCQ,每个线程在执行了引用更新操作的时候,都会往自己的那个DCQ里面写入变更信息。DCQ默认长度是256,如果写满了,就重新申请一个新的DCQ,并把这个老DCQ提交到第二级缓存,也就是一个DCQ Set里面去,叫做二级缓存为DCQS
refine直接从DCQS取DCQ去消费的,如果DCQS已经不能再放下更多的DCQ了,此时工作线程就会自己处理,自己去处理DCQ,更新RSet

24、G1垃圾回收

如果老年代对象占用达到了某个阈值,就会触发老年代的回收,ParNew + CMS直接Full GC,G1中是触发mixed gc

首先会进行ygc,老年代存活对象越来越多,会进入mixed gc,mixed gc会从老年代中选择部分回收价值比较高的region进行回收,满足用户设置的MaxGCPauseMillis值,当mixed gc之后,对象还是无法分片成功的时候,触发full gc,
full gc会暂停程序运行,对整个堆进行全面的垃圾回收,包括新生代、老年代和大对象等

25、G1 Young GC

并行处理
STW -> 选择需要回收的分区全部新生代region -> GC Roots + RSet -> 把直接引用的对象field放入一个set,-> 放入survior -> 继续遍历field set -> 复制到survior中

串行处理
弱引用、软引用和虚引用追踪 -> 复制到survivor区 -> 一些G1优化,比如字符串去重 -> 重建RSet -> 清理垃圾(尝试回收大对象) -> 尝试扩展分区(GC占用时间达到程序10%) -> 调整新生代数量 -> 要看GC对DCQS的处理,调整DCQS的几个阈值 -> 判断是否开启并发标记,如果判断通过(老年代45%的使用率)就启动并发标记

第一次YGC的时候,应该是 -Xmx * 5%的年轻代使用量的时候,大概是这个值

26、Mixed回收

1、初始标记
一定会伴随一次YGC,在YGC的时候会做一个判断,是否要开启并发标记,如果需要开启并发标记,那么本次YGC的整体过程,会额外做一些事情,
把GC Roots直接引用的老年代的对象也标记起来,作为并发标记的其实对象的一部分

初始标记的时候会发生YGC,YGC结束之后,在survivor区的对象会作为一部分的并发标记的起始对象(跟对象,可以理解为GC Roots对象)
所以,在初始标记阶段中,必然会发生一次YGC

2、并发标记
Survivor区存储的对象 + GC Roots引用的老年代对象集合RSet进行标记,并引入位图 + 三色标记法来做对象是否存活的标记
白色大表死亡、黑色代表存活、灰色代表存活但其子对象尚未标记完全
引用变化,对象变为灰色,重新标记,防止误删除

SATB多次并发标记过程中,出现的引用变化都会记录下来

3、最终标记 STW
主要是对并发标记阶段由于系统运行造成的错标漏表情况修正处理。本质上是把所有SATB队列里面的对象重新做遍历标记处理。最终把对象全表标记为黑色
或者保持原本的白色

4、预回收
存活对象计数的阶段

5、混合回收阶段,会选择一些分区,成为Cset(Collect Set),回收
需要注意的是,在选择CSet的时候,是按照一定的选择算法来选择CSet的(-XX:G1MixedGCLiveThresholdPercent存活小于85%才会放入到CSet中)。我们在上一个阶段,已经统计出来了Region的存活对象的数量,垃圾对象的数量,
那么其实CSet的选择就是根据这个基础来做的

回收时间 约定于 对象的复制时间(从垃圾region转移到空闲region)

混合回收是否要执行的条件 :
-XX:G1HeapWastePercent 默认是5%,即,在并发标记结束之后,如果说我们选择的CSet中可以被回收的垃圾占用堆内存空间的占比大于5%,才会进行混合回收
的回收节点,否则本次是不开启垃圾回收的过程的,即使并发标记,重新标记都完成,也不开启垃圾回收的过程

MixedGC的最后一步,垃圾回收过程,-XX:G1MixedGCCountTarget,默认是8,会最多通过8次对CSet进行分配回收

G1OldCSetRegionThredShouldPercent,参数是10,表示,每次在执行混合回收的时候,回收掉的分区数量不能超过整个堆内存分区数量的10%

27、Full GC

TLAB分配 -> 扩展TLAB进行分配 -> 申请新的TLAB -> 从自由分区新的region给新生代 -> 堆外内存扩展分区给新生代 -> 垃圾回收(YGC、MixedGC) ->
FULL GC(第一次) -> FULL GC(第二次,回收软引用)

FULL GC的一些优化点总结
1、使用多线程进行垃圾回收,CMS一块内存冲突比较多(整理、压缩),所以单线程;G1天生分区多线程(整理、压缩)
2、标记过程采用多线程并行处理
3、采取了任务窃取策略,提升整体的效率
4、采取了单个线程的那些分区整体压缩的处理,提升空闲region的产生的可能性

YGC和FULL GC会进行字符串去重

-Xms10M -Xmx10M -Xss1M  -XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintTLAB 
-XX:+PrintGCDetails -Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/Users/qiaozhanwei/IdeaProjects/jdk

// -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest 这两个选项是打开详情日志
-XX:InitialHeapSize=128M -XX:MaxHeapSize=128M -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintTLAB -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest -XX:MaxGCPauseMillis=20 -Xloggc:gc.log

-Xmx256M -XX:+UseG1GC -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:MaxGCPauseMillis=20 -Xloggc:gc.log

JDK9 中G1被设置为默认的垃圾回收器

如感兴趣,点赞加关注哦!


journey
32 声望22 粉丝

« 上一篇
Netty入门
下一篇 »
Java之基础