关于JVM的垃圾回收器,很多同学也提出了很多问题,本期内容将把大家的疑惑问题整理出来,给大家一起学习,欢迎大家一起来交流!
本次内容概要:
1、JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots
2、如何盘点查看JVM系统默认值
3、你平时⼯作⽤过的JVM常⽤基本配置参数有哪些
4、强引⽤,软引⽤,弱引⽤,虚引⽤作⽤分别是什么
5、请你谈谈对OOM的认识
6、==GC垃圾回收算法和垃圾收集器的关系?分别是什么==
7、==怎么查看服务器默认的垃圾收集器是哪个?⽣产上如何配置垃圾收集器?对垃圾收集器的理解?==
8、==垃圾收集器==
9、==G1垃圾收集器==
10、⽣产环境服务器变慢,诊断思路和性能评估谈谈?
11、加⼊⽣产环境CPU占⽤过⾼,谈谈分析思路和定位?
12、对于JDK⾃带的JVM监控和性能分析⼯具你⽤过那些?⼀般怎么⽤?
下篇文章分享:9-12系列课程内容
一、JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots
1. 什么是垃圾?
内存中已经不再使⽤到的空间就是垃圾
2. 要进⾏垃圾回收,如何判断⼀个对象是否可以被回收
引⽤计数法:
java中,引⽤和对象是由关联的。如果要操作对象则必须⽤引⽤进⾏。
因此很显然⼀个简单的办法是通过引⽤计数来判断⼀个对象是否可以回收,简单说,给对象中添加⼀个引⽤计数器,每当有⼀个地⽅引⽤它,计数器加1,每当有⼀个引⽤失效时,计数器减1,任何时刻计数器数值为零的对象就是不可能再被使⽤的,那么这个对象就是可回收对象。
但是它很难解决对象之间相互循环引⽤的问题JVM⼀般不采⽤这种实现⽅式。
==枚举根节点做可达性分析(跟搜索路径)==
为了解决引⽤计数法的循环引⽤问题,java使⽤了可达性分析的⽅法。
所谓GC ROOT或者说Tracing GC的“根集合”就是⼀组⽐较活跃的引⽤。
基本思路就是通过⼀系列“GC Roots”的对象作为起始点,从这个被称为GC Roots 的对象开始向下搜索,如果⼀个对象到GC Roots没有任何引⽤链相连时,则说明此对象不可⽤。也即给定⼀个集合的引⽤作为根出发,通过引⽤关系遍历对象图,能被便利到的对象就被判定为存活;
没有被便利到的就被判定为死亡
哪些对象可以作为GC Roots对象
虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引⽤的对象。
⽅法区中的类静态属性引⽤的对象
⽅法区中常量引⽤的对象
本地⽅法栈中JNI(native⽅法)引⽤的对象
3.如何盘点查看JVM系统默认值
如何查看运⾏中程序的JVM信息
jps查看进程信息
jinfo-flflag 配置项 进程号
jinfo-flflags 进程号====查看所有配置
1)JVM参数类型
1. 标配参
-version-
help各个版本之间稳定,很少有很⼤的变化
2. x参数
-Xint-Xcomp-Xmixed
-Xint :解释执⾏
-Xcomp:第⼀次使⽤就编译成本地代码
-Xmixed:混合模式
3.==xx参数==
Boolean类型
公式:-XX+或者-某个属性值 +表示开启,-表示关闭
是否打印GC收集细节-XX:+PrintGCDetails 开启-XX:-PrintGCDetails 关闭
是否使⽤串⾏垃圾回收器:-XX:-UseSerialGC
KV设值类型公式:-XX:属性key=属性值value
case:-XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
-Xms---->-XX:InitialHeapSize
-Xmx---->-XX:MaxHeapSize
2)查看参数
-XX:+PrintFlagsInitial
查看初始默认
==java-XX:+PrintFlagsInitial-version==
-XX:+PrintFlagsFinal
查看修改后的 :=说明是修改过的
-XX:+PrintCommandLineFlags
查看使⽤的垃圾回收器
三、你平时⼯作⽤过的JVM常⽤基本配置参数有哪些
-Xms-Xmx-Xmn
-Xms128m-Xmx4096m-Xss1024K-XX:MetaspaceSize=512m-XX:+PrintCommandLineFlags-XX:+PrintGCDetails-XX:+UseSerialGC
1、-Xms
初始⼤⼩内存,默认为物理内存1/64,等价于-XX:InitialHeapSize
2、-Xmx
最⼤分配内存,默认物理内存1/4,等价于-XX:MaxHeapSize
3、-Xss
设置单个线程栈的⼤⼩,默认542K~1024K ,等价于-XX:ThreadStackSize
4.-Xmn
设置年轻代的⼤⼩
5.-XX:MetaspaceSize
设置元空间⼤⼩元空间的本质和永久代类似,都是对JVM规范中⽅法区的实现,不过元空间与永久代最⼤的区别在于:==元空间并不在虚拟机中,⽽是在本地内存中。==因此,默认元空间的⼤⼩仅受本地内存限制
6.-XX:+PrintGCDetails
输出详细GC收集⽇志信息[名称:GC前内存占⽤->GC后内存占⽤(该区内存总⼤⼩)]
7.-XX:SurvivorRatio
设置新⽣代中Eden和S0/S1空间的⽐例
默认-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
8.-XX:NewRatio
设置年轻代与⽼年代在堆结构的占⽐
默认-XX:NewRatio=2 新⽣代在1,⽼年代2,年轻代占整个堆的1/3
NewRatio值⼏句诗设置⽼年代的占⽐,剩下的1给新⽣代
9.-XX:MaxTenuringThreshold
设置垃圾的最⼤年龄默认-XX:MaxTenuringThreshold=15
如果设置为0,年轻代对象不经过Survivor区,直接进⼊年⽼代。对于年⽼代⽐较多的应⽤,可以提⾼效率。如果将此值设置为⼀个较⼤的值,则年轻代对象回在Survivor区进⾏多次复制,这样可以增加对对象在年轻代的存活时间,增加在年轻代即被回收的概率。
10.-XX:+UseSerialGC
串⾏垃圾回收器11.-XX:+UseParallelGC并⾏垃圾回收器
四、强引⽤、软引⽤、弱引⽤、虚引⽤作⽤分别是什么
1 、强引⽤ Reference
当内存不⾜,JVM开始垃圾回收,对于强引⽤对象,就算出现了OOM也不会对该对象进⾏回收。
强引⽤是我们最常⻅的普通对象引⽤,只要还有强引⽤指向⼀个对象,就能表明对象还“存活”,垃圾收集器不会碰这种对象。在Java中最常⻅的就是强引⽤,把⼀个对象赋给⼀个引⽤变量,这个引⽤变量就是⼀个强引⽤。当⼀个对象被强引⽤变量引⽤时,它处于可达状态,他是不可能被垃圾回收机制回收的,既是该对象以后永远都不会被⽤到 JVM也不会回收。因此强引⽤时造成java内存泄漏的主要原因之⼀。
对于⼀个普通对象,如果没有其他的引⽤关系,只要超过了引⽤的作⽤域或者显式的将应⽤(强)引⽤复制为null,⼀般认为就是可以被垃圾收集的了
2、 软引⽤ SoftReference
软引⽤是⼀种相对强引⽤弱化了⼀些作⽤,需要⽤ java.lang.ref.SOftReference 类来实现,可以让对象豁免⼀些垃圾收集。
当系统内存充⾜时他不会被回收,当内存不⾜时会被回收。
软引⽤通常在对内存敏感的程序中,⽐如⾼速缓存就有⽤到软引⽤,内存⾜够的时候就保留,不够就回收。
3、弱引⽤ WeakReference
弱引⽤需要⽤java.lang.ref.WeakReference类来实现,⽐软引⽤的⽣存期更短只要垃圾回收机制⼀运⾏,不管JVM的内存空间是否⾜够,都会回收。
谈谈WeakHashMapkey是弱引⽤
4 、虚引⽤PhantomReference
顾名思义,就是形同虚设,与其他集中不同,虚引⽤并不会决定对象的⽣命周期
如果⼀个对象持有虚引⽤,那么他就和没有任何引⽤⼀样,在任何时候都可能被垃圾回收器回收,他不能单独使⽤也不能通过它访问对象,虚引⽤和引⽤队列(ReferenceQueeu)联合使⽤。
需应⽤的主要作⽤是跟踪对象被垃圾回收的状态。仅仅是提供了⼀种确保ui想被fifinalize以后,做某些事情的机制。
PhantomReference的get⽅法总是返回null,因此⽆法访问对应的引⽤对象。其意义在于说明⼀个对象已经进⼊fifinalization阶段,可以被gc回收,⽤来实现⽐fifinalization机制更灵活的回收操作。
换句话说,设置虚引⽤关联的唯⼀⽬的,就是这个对象被收集器回收的时候收到⼀个系统通知或者后续添加进⼀步的处理。
java允许使⽤fifinalize()⽅法在垃圾收集器将对象从内存中清除出去之前做必要的清理⼯作。
引⽤队列Reference
创建引⽤的时候可以指定关联的队列,当gc释放对象内存的时候,会把引⽤加⼊到引⽤队列,如果程序发现某个虚引⽤已经被加⼊到引⽤队列,那么就可以在所引⽤的对象的内存被回收之前采取必要的⾏动,相当于通知机制。
当关联的引⽤队列中有数据的时候,意味着引⽤指向的对内存中的对象被回收。通过这种⽅式,jvm允许我们在对象被⼩回收,做⼀些我们⾃⼰想做的事情。
5、适⽤场景
加⼊⼀个应⽤需要读取⼤量的本地图⽚,如果每次读取图⽚都从硬盘读取会严重影响性能,如果⼀次性全部加载到内存⼜可能造成内存溢出,这时可以⽤软引⽤解决这个问题。
设计思路:⽤⼀个HashMap来保存图⽚路径和相应图⽚对象关联的软引⽤之间的映射关系,在内 存不⾜时,JVM会⾃动共回收这些缓存图⽚对象所占的空间,避免OOM
Map> imageCache = new HashMap<>();
五、请你谈谈对OOM的认识
java.lang.StackOverflowError
栈空间溢出 ,递归调⽤卡死
java.lang.OutOfMemoryError:Java heap space
堆内存溢出 , 对象过⼤
java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间过⻓
过⻓的定义是超过98%的时间⽤来做GC并且回收了⽽不倒2%的堆内存
连续多次GC,都回收了不到2%的极端情况下才会抛出
如果不抛出,那就是GC清理的⼀点内存很快会被再次填满,迫使GC再次执⾏,这样就恶性循环,cpu使⽤率⼀直是100%,⼆GC却没有任何成果
int i = 0;List list = new ArrayList<>();try{ while(true){ list.add(String.valueOf(++i).intern()); }}catch(Throwable e){ System.out.println("********"); e.printStackTrace(); throw e; }
java.lang.OutOfMemoryError:Direct buffer memory
执⾏内存挂了
写NIO程序经常使⽤ByteBuffffer来读取或写⼊数据,这是⼀种基于通道(Channel)与缓存区(Buffffer)的I/O⽅式,它可以使⽤Native函数库直接分配堆外内存,然后通过⼀个存储在java堆⾥⾯的DirectByteBuffffer对象作为这块内存的引⽤进⾏操作,这样能在⼀些场景中显著提⾼性能,因为避免了在java堆和native堆中来回复制数据。
ByteBuffffer.allocate(capability) 第⼀种⽅式是分配JVM堆内存,属于GC管辖,由于需要拷⻉所以速度较慢。
ByteBuffffer.alloctedDirect(capability)分配os本地内存,不属于GC管辖,不需要拷⻉,速度较快但如果不断分配本地内存,堆内存很少使⽤,那么jvm就不需要执⾏GC,DirectByteBuffffer对象们就不会被回收,这时候堆内存充⾜,但本地内存可能已经使⽤光了,再次尝试分配本地内存救护i出现oom,程序崩溃。
java.lang.OutOfMemoryError:unable to create new native thread ==好案例==
应⽤创建了太多线程,⼀个应⽤进程创建了多个线程,超过系统承载极限
你的服务器并不允许你的应⽤程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024,超过这个数量,就会报错。
解决办法
降低应⽤程序创建线程的数量,分析应⽤给是否针对需要这么多线程,如果不是,减到最低修改linux服务器配置
java.lang.OutOfMemoryError:Metaspace
元空间主要存放了虚拟机加载的类的信息、常量池、静态变量、即时编译后的代码
static class OOMTest{}public static void main(String[] args){ int i = 0; try{ while(true){ i++; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMTest.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor(){ @Override public Object intercept(Object o,Method method,Object[]objects, MethodProxy methodProxy)throwsThrowable{ return methodProxy.invokeSuper(o,args); } }); enhancer.create(); } } catch(Throwable e){ System.out.println(i+"次后发⽣了异常"); e.printStackTrace(); }}
六、==GC垃圾回收算法和垃圾收集器的关系?分别是什么
垃圾收集器就是算法的具体实现
1、GC算法
引⽤计数
复制
标记清理
标记整理
4种主要垃圾收集器
1. Serial 串⾏回收
为单线程换进该设计且只是⽤过⼀个线程进⾏垃圾回收,会暂停所有的⽤户线程,不适合服务器环境。
2. Paralle 并⾏回收
多个垃圾收集线程并⾏⼯作,此时⽤户线程是暂停的,适⽤于科学计算/⼤数据处理⾸台处理弱交互场景。
3. CMS 并发标记清除
⽤户线程和垃圾⼿机线程同时执⾏(不⼀定是并⾏,可能交替执⾏),不需要停顿⽤户线程,互联⽹公司多⽤它,适⽤堆响应时间有要求的场景。
4. G1
将堆内存分割城不同的区域然后并发的对其进⾏垃圾回收
5. ZGC
七、==怎么查看服务器默认的垃圾收集器是哪个?⽣产上如何配置垃圾收集器?对垃圾收集器的理解?==
1. 查看:java-XX:+PrintCommandLinedFlags-version
2. 配置:
有七种:UseSerialGC UseParallelGC UseConcMarkSweepGC UseParNewGC
UseParallelOldGC UseG1GC
八、==垃圾收集器==
1、 部分参数说明
DefNew:Default New Generation
Tenured:Old
ParNew:Parallel New Generation
PSYoungGen:Parallel Scavenge
ParOldGen:Parallel Old Generation
2、Server/Client模式分别是什么意思
1. 适⽤范围:只需要掌握Server模式即可,Client模式基本不会⽤
2. 操作系统:
32位Window操作系统,不论硬件如何都默认使⽤Client的JVM模式
32位其他操作系统,2G内存同时有2个cpu以上⽤Server模式,低于该配置还是Client模式
64位only server模式
3、新⽣代
1. 串⾏GC (Serial)/(Serial Copying)
串⾏收集器:Serial收集器 1:1⼀个单线程的收集器,在进⾏垃圾收集的时候,必须暂停其他所有的⼯作线程知道它收集结束。串⾏收集器是最古⽼,最稳定以及效率最⾼的收集器,只使⽤⼀个线程去回收但其在进⾏垃圾收集过程中可能会产⽣较⻓的停顿(Stop-The-World 状态)。虽然在收集垃圾过程中需要暂停所有其他的⼯作线程,但是它简单⾼效,==对于限定单个CPU环境来说,没有线程交互的开销可以获得最⾼的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运⾏在Client模式下默认的新⽣代垃圾收集器。
==对应的JVM参数是:==-XX:+UseSerialGC==
==开启后会使⽤:Serial(Young区)+Serial Old(Old区)的收集器组合==
表示:==新⽣代、⽼年代都会使⽤串⾏回收收集器,新⽣代使⽤复制算法,⽼年代使⽤标记整理算法==
DefNew-----Tenured
2. 并⾏GC (ParNew)
并⾏收集器 N:1
使⽤多线程进⾏垃圾回收,在垃圾收集时,会Stop-the-world暂停其他所有的⼯作线程知道它收集结束。
==ParNew收集器其实就是Serial收集器新⽣代的并⾏多线程版本,==最常⻅的应⽤场景时配合==⽼年代的CMS GC⼯作==,其余的⾏为和Serial收集器完全⼀样,ParNew垃圾收集器在垃圾收集过程成中童谣也要暂停所有其他的⼯作线程,他是很多
==java虚拟机运⾏在Server模式下新⽣代的默认垃圾收集器。==
常⽤对应JVM参数:==-XX:+UseParNewGC==启⽤ParNew收集器,只影响新⽣代的收集,不影响⽼年代
==开启后会使⽤:ParNew(Young区)+Serial Old(Old 区)的收集器组合==
,新⽣代使⽤复制算法,⽼年代采⽤标记-整理算法
ParNew------Tenured
备注:
==-XX:ParallelGCThreads==
限制线程数量,默认开启和CPU数⽬相同的线程数
3. 并⾏回收GC (Parallel)/(Parallel Scavenge)
并⾏收集器 N:N
类似ParNew也是⼀个新⽣代垃圾收集器,使⽤复制算法,也是⼀个并⾏的多线程的垃圾收集器,俗称吞吐量有限收集器。串⾏收集器在新⽣代和⽼年代的并⾏化。
他重点关注的是:
可控制的吞吐量(Thoughput=运⾏⽤户代码时间/(运⾏⽤户代码时间+垃圾收集时间),业绩⽐如程序运⾏100分钟,垃圾收集时间1分钟,吞吐量就是99%)。⾼吞吐量意味着⾼效利⽤CPU时间,他多⽤于在后台运算⽽不需要太多交互的任务。
⾃适应调节策略也是ParallelScavenge收集器与ParNew收集器的⼀个重要区别。(⾃适应调节策略:虚拟机会根据当前系统的运⾏情况⼿机性能监控信息,动态调整这些参数以提供最合适的停顿时间==(-XX:MaxGCPauseMillis)==或最⼤的吞吐量
==-XX:ParallelGCThreads=数字N==表示启动多少个GC线程
cpu>8 n=5/8
cpu<8 n=实际个数
==-
XX:+UseParallelGC==、==-XX:+UseParallelOldGC==
4、⽼年代
1. 串⾏GC(Serial Old)/(Serial MSC)
2. 并⾏GC(Parallel Old)/(Parallel MSC)
Parallel Scavenge的⽼年代版本,使⽤多线程的标记-整理算法,Parallel Old收集器在JDK1.6开始提供
1.6之前,新⽣代使⽤ParallelScavenge收集器只能搭配年⽼代的Serial Old收集器,只能保证新⽣
代的吞吐量优先,⽆法保证整体的吞吐量。在1.6之前(ParallelScavenge+SerialOld)
Parallel Old正式为了在⽼年代同样提供吞吐量游戏的垃圾收集器,如果系统对吞吐量要求⽐较
⾼,JDK1.8后可以考虑新⽣代Parallel Scavenge和年⽼代Parallel Old收集器的搭配策略
常⽤参数:==-XX:+UseParallelOldGC==》》》》》新⽣代Paralle+⽼年代Paralle Old
3. 并发标记清除GC(CMS)
CMS收集器(Concurrent Mark Sweep:并发标记清除)是⼀种以获取最短回收停顿时间为⽬标的收集器。
适合在互联⽹站或者B/S系统的服务器上,这列应⽤尤其中使服务器的响应速度,希望系统停顿时间最短。
CMS⾮常适合堆内存⼤、CPU核数多的服务区端应⽤,也是G1出现之⼤型应⽤的⾸选收集器。
并发标记清除收集器:ParNew+CMS+Serial Old
CMS,并发收集低停顿,并发指的是与⽤户线程⼀起执⾏JVM参数:==-XX:+UseConcMarkSweepGC==,开启该参数后会⾃动将-XX:UseParNewGC打开
开启该参数后,使⽤ParNew+CMS+Serial Old的收集器组合,Serial Old将作为CMS出错的后备收集器
4步过程:
初始标记(CMS initial mark)
只是标记⼀下GC Roots能够直接关联的对象,速度很快,仍然需要暂停所有的⼯作线程。
并发标记(CMS concurrent mark)和⽤户线程⼀起
进⾏GC Roots跟踪过程,和⽤户线程⼀起⼯作,不需要暂停⼯作线程。主要标记过程,标记
全部对象重新标记(CMS remark)
为了修正并发标记期间,因⽤户程序继续运⾏⽽导致标记产⽣变动的那⼀部分对象的标记记录,仍然需要暂停所有的⼯作线程,由于并发标记时,⽤户线程依然运⾏,因此在正式清理前,再做修正。
并发清除(CMS concurrent sweep)和⽤户线程⼀起
清除GC Roots不可达对象,和⽤户线程⼀起⼯作,不需要暂停⼯作线程。基于标记结果,直接清理对象由于耗时最⻓的并发标记和并发清除过程中,垃圾收集线程可以和⽤户现在⼀起并发⼯作,所以总体上看来CMS收集器的内存回收和⽤户线程是⼀起并发的执⾏。
优缺点:
并发收集停顿低
并发执⾏,cpu资源压⼒⼤
由于并发进⾏,CMS在收集与应⽤线程会同时增加对堆内存的占⽤,也就是说,==CMS必须要在⽼年代堆内存⽤尽之前完成垃圾回收==,否则CMS回收失败时,将出发担保机制,串⾏⽼年代收集器将会以STW的⽅式进⾏⼀次GC,从⽽造成较⼤停顿时间。
采⽤的标记清除算法会导致⼤量的碎⽚
标记清除算法⽆法整理空间碎⽚,⽼年代空间会随着应⽤时⻓被逐步耗尽,最后将不得不通过担保机制堆堆内存进⾏压缩。CMS也提供了参数==-XX:CMSFullGCsBeForeCompaction==(默认0,即每次都进⾏内存整理)来制定多少次CMS收集之后,进⾏⼀次压缩的FullGC。
5 如何选择垃圾选择器
单CPU或⼩内存,单机内存
-XX:+UseSerialGC
多CPU,需要最⼤吞吐量,如后台计算型应⽤
-XX:+UseParallelGC-XX:+UseParallelOldGC
多CPU,最求低停顿时间,需快速相应,如互联⽹应⽤
-XX:+ParNewGC-XX:+UseConcMarkSweepGC
相关推荐
1、浅谈JVM原理及性能调优分享
2、Java设计模式精讲以及JVM、线程池高频面试题独家分享
3、大型企业JVM实战:优化及面试热点分析
4、大型企业JVM性能调优实战Java技术体系发展史
5、大型企业JVM性能调优实战Java垃圾收集器及gcroot
6、大型企业JVM性能调优实战之Java栈和Java栈方法调用
7、JVM垃圾算法和GC三⼤算法及面试题分享
回复关键词
Redis 分布式限流 消息队列 alibaba JVM性能调优
看更多精彩教程
喜欢本文,记得点击个在看,或者分享给朋友哦!