欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java gc

程序员文章站 2022-07-05 14:50:57
...

新生代老生代参考http://www.cnblogs.com/E-star/p/5556188.html

垃圾回收算法参考http://blog.csdn.net/java2000_wl/article/details/8022293

优化参考http://www.importnew.com/author/wangxiaojie 

面向GC编程http://www.importnew.com/11372.html

Java 堆内存http://www.blogjava.net/fancydeepin/archive/2013/09/29/jvm_heep.html

面试GC问答https://www.zhihu.com/question/35164211

 

一、算法

1.引用计数法:一个对象被引用计数器加一,取消引用计数器减一,引用计数器为0才能被回收。优点:简单。缺点:不能解决循环引用的问题,比如A引用B,B引用A,但是这两个对象没有被其他任何对象引用,属于垃圾对象,却不能回收;每次引用都会附件一个加减法,影响性能。

引用计数的方法由于存在显著的缺点,实际上并未被JVM所使用。

 

2.标记清除法:分为两个阶段:标记阶段和清除阶段。标记阶段通过根节点标记所有可达对象,清除阶段清除所有不可达对象。缺点:因为清除不可达对象之后剩余的内存不连续,会产生大量内存碎片,不利于大对象的分配。由于无法找到足够的连续内存,不得不触发另一次GC。

可以做根节点的对象:

虚拟机栈(栈帧中的本地变量表)中的引用的对象

方法区中的类静态属性引用的对象

方法区中的常量引用的对象

本地方法栈中JNI(Native方法)的引用对象

 

3.复制算法:将内存空间分成相同的两块,每次只是用其中的一块,垃圾回收时,将正在使用的内存中的存活对象复制到另外一块空间,然后清除正在使用的内存空间中的所有对象,这种回收算法适用于新生代垃圾回收。优点:垃圾回收对象比较多时需要复制的对象很少,性能较好;不会存在内存碎片。缺点:将系统内存折半。 

 

4.标记压缩算法:是一种老年代回收算法,在标记清除的基础上做了一些优化,首先从根节点开始标记所有不可达的对象,然后将所有可达的对象移动到内存的一端,最后清除所有不可达的对象。优点:不用将内存分为两块;不会产生内存碎片。

 

分代算法:新生代使用复制算法,老生代使用标记清除算法或者标记压缩算法。几乎所有的垃圾回收期都区分新生代和老生带。

 

分区算法:将整个堆空间分成很多个连续的不同的小空间,每个小空间独立使用,独立回收。为了更好的控制gc停顿时间,可以根据目标停顿时间合理地回收若干个小区间,而不是整个堆空间,从而减少gc停顿时间。

 

二、新生代young generation(执行minor gc 复制算法)和老年代old generation(执行major gc或叫full gc标记清除算法)

1.为什么要分代?

总有一些对象是长期使用的,如果每次GC都扫描他们,然后发现无法回收,这样效率就很差。所以就把对象增加了一个年龄属性,这样如果每次GC都回收不掉他们,他们的年龄就增加1,慢慢地就变成老年人了。达到一定年龄后,就要分区了。老年人会离开新生代区域,进入老年代区域。这样常规的GC只扫描新生代区域,处于老年代区域的对象就不会被频繁地扫描了。

80%的对象都是朝生夕死,对于负载不高的应用,可能数月都不会发生FullGC。

 

2.新生代再细分,有三个区域:Eden区,即伊甸园区。还有2个Survivor区,即幸存者区,分别叫from和to。from和to这两块就是复制算法中提到的两块内存,它们互相复制来复制去,互相换着当from和to.当然其中有一个肯定是空的。当eden满时,minorgc开始,Eden区也会执行复制算法,把所有存活对象全部复制到to区域。from区域的对象会根据年龄决定去向,达到要求的就去老年代,没达到要求的也会进入to区域。然后,Eden和from区都会被清空。当to区域也被塞满时,所有对象都会被复制到老年区。

一个对象的一辈子:我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就*去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

新生代98%的对象都是朝生夕死,虚拟机默认eden和survivor大小比例是8;1,也就是每次只有10%的内存空间是作为to区域被浪费,而不是传统复制算法中所说的浪费一半内存。

 

三、面向GC编程优化

1.减少创建对象的数量,比如使用StringBuilder代替string

2.老年代的GC很耗时,要减少进入老年代的对象数量。比如对象池,因为对象长期存活,会晋升到老年代,影响GC效率。另外对象池通常涉及并发访问,处理同步带来的开销也很大。而重新创建一个对象的开销,可能比这两者都要小。

3.在一个非常大的方法体内,对于较大的对象,将其引用置为null,某种程度可以帮助GC。但大部分情况下这种行为意义不大。

4.一些基于数组的数据结构,例如StringBuilder、StringBuffer、ArrayList、HashMap等等,在扩容的时候都需要做ArrayCopy,对于不断增长的结构来说,经过若干次扩容,会存在大量无用的老数组,而回收这些数组的压力,全都会加在GC身上。这些容器的构造函数中通常都有一个可以指定大小的参数,如果对于某些大小可以预估的容器,建议加上这个参数。

5.尽可能缩小对象的作用域,即生命周期。如果可以在方法内声明的局部变量,就不要声明为实例变量。除非你的对象是单例的或不变的,否则尽可能少地声明static变量。