GC调优与jvm参数问题


参考https://www.zhihu.com/question/41922036/answer/93079526

内存

什么时候产生堆溢出

在堆中创建对象一直被GC Roots引用无法被清理,同时堆中的对象内存超过了最大堆内存,发生堆内存溢出。

什么时候产生栈溢出

单个线程情况下,如果栈深度过大,导致栈空间占用超过最大栈内存,会导致栈溢出;
多线程情况下,在32位系统下,多个线程每个线程占用一个最大栈空间,如果线程过多,且受限于32位系统的最大进程内存,会产生栈空间不足的情况,也会发生栈溢出

JVM内存为什么要分成新生代,老年代,持久代

分代理论的3个假说:
1)弱分代假说(WeakGenerationalHypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(StrongGenerationalHypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
3)跨代引用假说(IntergenerationalReferenceHypothesis):跨代引用相对于同代引用来说仅占极少数。
jdk8之前采用永久代实现方法区,认为该空间一般不需要进行垃圾回收,jdk8开始使用叫做元空间的本地内存实现方法区,会进行垃圾回收;分代主要是为了让不同代的对象按照不同的频率回收,减少不必要的垃圾回收开销,这里考虑了弱分代假说和强分代假说。

新生代中为什么要分为Eden和Survivor

因为弱分代假说,大多数对象都是朝生夕灭的,所以只需要一般只需要留较小的Survivor就可以容纳垃圾收集后存活的新生代,只会浪费一个Survivor大小的空间,这个叫做Appel算法。否则按照常规的标记-复制算法(半区复制)就会浪费半个新生代的大小,这样不合算。另外后老年代的空间担保作为逃生门。

JVM 是怎么从新生代到老年代

(1)新创建的对象进入eden区,经过一次young GC后如果没有被清理掉将进入某个Survivor空间,同时这个对象的代数增长一个,当代数增长到某一个阈值的时候将进入老年代;
(2)也可能大对象直接进入老年代;
(3)也可能是某次young GC后一个Survivor空间容不下剩下的新生代对象,一部分对象直接通过担保机制进入老年代,
(4)也可能在Survivor空间中有相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX:MaxTenuringThreshold中要求的年龄。

内存的哪些部分会参与GC的回收

堆、方法区、直接内存

GC与调优

一个完整的GC流程是怎样的

G1之前,一般是minor GC(一般时新生代空间不足时发生) -> full GC(当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。)并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。
G1,一般是minor GC -> mixed GC ->full GC,详见

怎么打出一个线程的堆栈信息?如果cpu过高怎么定位?

jstack,cpu过高,top,找到线程,jstack中找线程,定位代码

如果内存过高怎么分析

内存过高,dump分析;

经典垃圾收集器频繁老年代回收怎么分析解决

  • 频繁full gc会导致
  1. 机器 cpu 负载过高
  2. 频繁 full gc 告警
  3. 系统无法请求处理或者过慢, 接口无关 全面性的
    出现以上异常的时候,要第一时间反应过来可能是full gc的问题
  • 频繁full gc的常见原因
    full gc 触发条件是 老年代空间不足, 所以追因的方向就是导致 老年代空间不足的原因:
    大量对象频繁进入老年代 + 老年代空间释放不掉
    系统并发高、执行耗时过长,或者数据量过大,导致 young gc频繁,且gc后存活对象太多,但是survivor 区存放不下(太小 或 动态年龄判断) 导致对象快速进入老年代 老年代迅速堆满
    程序一次性加载过多对象到内存 (大对象),导致频繁有大对象进入老年代 造成full gc
    存在内存溢出的情况,老年代驻留了大量释放不掉的对象, 只要有一点点对象进入老年代 就达到 full gc的水位了
    元数据区加载了太多类 ,满了 也会发生 full gc
    堆外内存 direct buffer memory 使用不当导致
    也许, 你看到老年代内存不高 重启也没用 还在频繁发生full gc, 那么可能有人作妖,在代码里搞执行了 System.gc();
  • 定位思路
    如果有监控,那么通过图形能比较直观、快速的了解gc情况;
    如果没有监控,那么只能看gc日志或jstat来分析 这是基本技能 一定要熟练
    观察年轻代 gc的情况,多久执行一次、每次gc后存活对象有多少 survivor区多大
    存活对象比较多 超过survivor区大小或触发动态年龄判断 => 调整内存分配比例
    观察老年代的内存情况 水位情况,多久执行一次、执行耗时多少、回收掉多少内存
    如果在持续的上涨,而且full gc后回收效果不好,那么很有可能是内存溢出了 => dump 排查具体是什么玩意
    如果年轻代和老年代的内存都比较低,而且频率低 那么又可能是元数据区加载太多东西了
    直接内存超过限制,导致触发 System.gc()
    其实如果是自己负责的系统,可能要看是不是发版改了什么配置、代码

G1调优

参考

其他

常用jvm参数和其作用

  • 经典垃圾收集器参数
    -Xms:初始大小内存,默认为物理内存的1/64等价于-XX:InitialHeapSize
    -Xmx:最大分配内存,默认为物理内存的1/4等价于-XX:MaxHeapSize
    -Xss:设置单个线程栈的大小,一般默认为512k~1024k等价于-XX:ThreadStackSize

当值等于0的时候,代表使用得是默认大小
-Xmn:设置年轻代大小
-XX:MetaspaceSize:设置元空间大小(元空间与永久代最大的区别为:元空间并不在虚拟机中,而使用的是本地内存,因此,元空间只收本地内存的限制)
手动设置:-XX:MetaspaceSize=1024m
-XX:MaxMetaspaceSize: 设置元空间的最大大小

-XX:SurvivorRatio:设置新生代中 eden 和 S0/S1 空间比例,默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1
? -XX:SurvivorRatio=4==》Eden : S0 : S1 = 4 : 1 : 1
-XX:NewRatio:配置年轻代和老年代在堆结构的占比,默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
? -XX:NewRatio=4 新生代占1,老年代占4,年轻代占整个堆的 1/5
-XX:MaxTenuringThreshold:设置垃圾最大年龄。默认是15。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果此值设置为一个较大的值,则年前对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率!

  • 微调 G1 GC 时,请记住以下建议:
    年轻代大小:避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。
    暂停时间目标:-XX:MaxGCPauseMillis设定暂停时间目标,每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。
    掌握混合垃圾回收:当您调优混合垃圾回收时,请尝试以下选项
    -XX:InitiatingHeapOccupancyPercent:堆内存比例,当整个堆占用超过某个百分比时,就会触发并发GC周期,这个百分比默认是45%。
    -XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent:G1MixedGCLiveThresholdPercent是Region的存活比例,默认85%,即只有存活对象低于85%的Region才可以被回收。G1HeapWastePercent是允许堆的浪费比例,默认10%,即全局并发标记后统计出所有可回收垃圾比例超过G1HeapWastePercent才会触发回收混合回收。
    -XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent:执行混合垃圾回收的目标次数和设置混合垃圾回收期间要回收的最大旧区域数

Java的四种引用有了解么?引用队列怎么使用?作用是什么?

  • 强引用
    普通赋值就是强引用
  • 软引用
    用WeakReference类实现,被软引用关联的对象,当系统要发生OOM异常时,会将软引用关联的对象列进回收范围之中进行第二次回收,如果这次回收后还没有足够资源才抛出OOM异常
  • 弱引用
    用WeakReference类实现,被弱引用关联的对象只能生存到下次垃圾回收发生为止,不管内存是否够用,只要发生垃圾回收就会收回只被弱引用关联的对象。
  • 虚引用
    用PhantomReference类实现,无法通过虚引用来获取一个对象实例,虚引用也不会对对象的生存实践构成影响。唯一作用是能在对象被收集器回收的时候收到一个系统通知。
  • 引用队列
    当联合使用软引用、弱引用和引用队列时,系统在回收被引用的对象之后,将把它所回收对象对应的引用添加到关联的引用队列中。而虚引用在对象被释放之前,将把它对应的虚引用添加到它关联的引用队列中,这使得可以在对象被回收之前采取行动。

While(true)里一直new thread().start()会有什么问题

线程一直运行的情况在已经实践;
线程不一直运行的情况会导致创建大量线程对象,导致垃圾收集器频繁young gc.

创建对象的方法


JVM