OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)
硬件升级系统反而卡顿的问题(见上)【换垃圾回收期】
线程池不当运用产生OOM问题(见上)
不断的往List里加对象(实在太LOW)
jira问题
真正问题在哪儿?不知道,实际系统不断重启
解决方案: 加内存 + 更换垃圾回收器 G1
tomcat http-header-size过大问题


发现每个Http11OutputBuffer都占用很大的内存,当请求过多时,设置堆内存太小,会导致OOM问题。

lamda表达式导致方法区溢出问题:
产生的原因为class逐渐增多了:
public class LambdaGC {
public static void main(String[] args) {
for(;;) {
I i = C::n;
}
}
public static interface I {
void m();
}
public static class C {
static void n() {
System.out.println("hello");
}
}
}
直接内存溢出问题
使用Unsafe分配直接内存,或者使用NIO的问题
栈溢出问题(StackOverflowError)
方法之间调用深度特别深【产生的栈帧大】 -Xss设定太小
public class StackOverFlow {
public static void main(String[] args) {
m();
}
static void m() {
m();
}
}
相较于来说第一种写法更优,第二种写法,如果循环没有结束,对象
会不断产生,并且循环结束前不会进行垃圾回收。

9.重写finalize引发频繁GC
小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题。
C++程序员重写finalize的目的是自己释放内存,调用析构函数(new delete【语句】 ),就理所当然认为java也有类似的析构函数,造成频繁GC的原因是因为重写了finalize耗时比较长(200ms),如果一下子要回收很多对象的话则会造成频繁FullGC。
10.如果有一个系统,内存消耗一直不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?
有人频繁调用system.gc()
11.new 大量线程,会产生 native thread OOM,(low)应该用线程池,
解决方案:减少堆空间,预留更多内存产生native thread,当然采用线程池更好。
JVM内存占物理内存比例 50% - 80%
CMS垃圾回收器在发展历史上是承上启下的。
其是垃圾回收线程和生产线程一起工作:
但是会造成内存碎片化,并且还有浮动垃圾不断产生,
1、初始标记
2、并发标记
3、重新标记
4、并发回收

初始标记:
初始标记是找到所有GC root以及直接相关联的对象 但是引用链上其他对象不会标记,要等到并发标记,而并发标记产生的垃圾,重新标记其实是并发标记的脏卡重新清理标记
通过GCRoots找到根对象,这是STW的:

即便是STW的,但是它的根对象相对来说比较少,所以耗时不会太长。
并发标记:
在上一步找到根对象之后,并发顺着原来的根对象继续往下找,可以回收的对象。
重新标记:
找到标记了要回收的对象,但是在并发标记的过程中,有重新引用的对象,给它在并发清理中回收掉:

从线程角度理解:
如上在并发清理的时候工作线程运行可能会产生浮动垃圾,并且也是采用标记清除算法,所以也会产生很多内存碎片。
参考链接:
https://www.oracle.com/technicalresources/articles/java/g1gc.html

通过并发或者并行的手段,降低垃圾回收暂停时间并且维持不错的吞吐量。
G1与PS相比它的吞吐量降低了10%-15%的样子但是G1的响应时间只有差不多200ms,所以看程序不管怎样200ms都有响应用G1,如果你的程序追求吞吐量,那么用PS更好。
原理:

humongous:是放大对象的内存区域
特点:
1、并发收集
2、压缩空间不会延长GC的暂停时间
3、更易预测GC暂停时间
4、适用不需要实现很高的吞吐量的场景

G1的内存区域不是固定的E或者O,非常的灵活。
分为年轻代和老年代,但是如何去判断哪些对象是老年代呢?如下红色的部分是根对象,这个根对象指向了老年代里面的
一个对象,这个老年代的对象又指向了年轻代的要给对象,怎么确定年轻代的对象是活的呢?
一般的思路是遍历所有老年代的对象,找到老年代里面的对象有没有指向年轻代活着的对象。【效率极低】
所以JVM内部是把内存区域分成多个card,具体的对象存在card里面,在程序的演化
过程中,如果某个card里面的对象指向了年轻代,那么就会把card标记为dirty,然后会用
一个位图来代表哪些card是dirty的。这样就在扫描时就只需要扫描Dirty Card就行了。

Cset = Collection Set
一组可被回收的分区的集合。
在CSet中存活的数据会在GC过程中被移动到另一个可用的分区,CSet
中的分区可以来自eden空间、survivor空间、或者老年代,Cset会占用
不到整个堆空间的1%大小。
每一个region里面都有一个Hashset记录其他region到当前region的引用,帮助垃圾回收
使用的。

Rset与赋值的效率
由于Rset的存在,那么每次给对象赋引用的时候,就得做一些额外的操作,指的是Rset中
做一些额外的记录(在GC中被称为写屏障)
这个写屏障 不等于内存屏障,是GC专有的写屏障
G1新老年代比例【动态调整Y区及Old区大小】:
如果某个区慢了的话则动态减少那个区的大小,


注意:G1在对象分配不下的时候也会产生FGC。
G1产生FGC应该做什么?
1、扩内存
2、提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3、降低MixedGC触发的阈值,让MixedGC提早发生(默认时45%)
G1中的MixedGC:
相当于CMS,当Old区超过默认值,整体占用堆内存空间超过45%


Java10以前是串行的FullGC,10以后是并行的FullGC


黑色是,它本身自己被标记了,并且成员变量引用的对象也被标记了。

漏标产生条件:
黑色对象的引用指向了白色对象,与此同时灰色对象指向白色对象的引用没有了,

解决方案:跟踪A指向D让它重新扫描,或者说当B指向D消失了,则重新扫描,这两种方式的算法:
第一种方式黑色指向了白色之后将黑色重新标记为灰色(CMS)
第二种方式则是将引用推入到GC的堆栈,重新扫描(G1)

为什么CMS采用第一种方案,为什么G1采用第二种方案?
CMS第一种方案,会重新扫描所有成员,但是它的成员已经被扫描了,会影响效率。
SATB则做一个快照,将改变的引用给重新扫描一遍【扫RSet】看有没有其他引用指向那个白色的块就好了。
灰色 → 白色 引用消失时,如果没有黑色指向白色引用会被push到堆栈下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高SATB 配合 RSet ,浑然天成
UseConcMarkSweepGC 使用cms垃圾回收器在老年代,年轻代使用的是ParNew


可以看到开始为ParNew执行年轻代的垃圾回收,后面再通过CMS执行老年代的垃圾回收。
年轻代回收日志:

CMS回收日志:
注意initial Mark 和 final remark 都是STW的

注意CMS产生在老年代使用了8.5M的时候,CMS的产生情况可以根据一个参数指定,
-XX:CMSinitiatingOccupancyFraction:使用多少比例的老年代后开始CMS收集,默认是68%(近似值)
如果此参数设置过大容易引起FGC【年轻代对象往老年代移】【这样也会造成Serial Old单线程执行
,导致整个系统卡顿】,如果参数设置过小则会频繁CMS回收。
G1有三个过程,YGC, MixedGC,FGC
G1是分为各个region来回收的,有YGC【是STW的,不推荐指定-Xmn年轻代大小,
G1会根据YGC的暂停时间动态调整】





-XX:SurvivorRatio
它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
参考公式:
Eden = (R*Y)/(R+1+1)
From = Y/(R+1+1)
To = Y/(R+1+1)



如下图程序到进程到线程到纤程是逐步调优的一个过程

一个QQ.exe跑起来分为不同的进程,然后再划分为线程,最后再到纤程。
线程和纤程的区别一个是通过内核空间【启动线程需要内核调用,消耗资源较多
,线程级别的并发就是比较重量级的】一个是不通过内核空间【每个纤程对应一个
纤程栈并且纤程间的切换在用户空间】,还有一个区别是操作系统不支持启动很多线程的,会花大量时间
在线程的切换上,但是纤程可以启动很多
引入【go,python常使用】
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.7.6</version>
</dependency>
先看线程计算:
public class HelloFiber {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Runnable r = new Runnable() {
@Override
public void run() {
calc();
}
};
int size = 10000;
Thread[] threads = new Thread[size];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(r);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) result += i;
}
}
}

再看纤程:
public class HelloFiber2 {
@SuppressWarnings("")
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int size = 10000;
Fiber<Void>[] fibers = new Fiber[size];
for (int i = 0; i < fibers.length; i++) {
fibers[i] = new Fiber<Void>(new SuspendableRunnable() {
public void run() throws SuspendExecution, InterruptedException {
calc();
}
});
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].start();
}
for (int i = 0; i < fibers.length; i++) {
fibers[i].join();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
static void calc() {
int result = 0;
for (int m = 0; m < 10000; m++) {
for (int i = 0; i < 200; i++) result += i;
}
}
}

快了差不多十倍的样子。