1.定义

垃圾回收是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。

什么是java的垃圾?

heap中不再被引用的对象称作垃圾```

2.判断对象是否是垃圾的算法

  • 引用计数算法(能讲多尽量多)

堆中每个对象(不是引用)都有一个引用计数器。当一个对象被创建并初始化赋值后,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1(a = b, b被引用,则b引用的对象计数+1)。当引用失效时(一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时),计数器值就减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。
优点:引用计数收集器执行简单,判定效率高,交织在程序运行中。对程序不被长时间打断的实时环境比较有利(OC的内存管理使用该算法)。
缺点: 难以检测出对象之间的循环引用。同时,引用计数器增加了程序执行的开销。所以Java语言并没有选择这种算法进行垃圾回收。 早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历(根搜索算法)。

  • 根搜索算法(可达性分析法)

首先了解一个概念:根集(Root Set)
所谓根集(Root Set)就是正在执行的Java程序可以访问的引用变量(注意:不是对象)的集合(包括局部变量、参数、类变量),程序可以使用引用变量访问对象的属性和调用对象的方法。
这种算法的基本思路:
从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用,那么虚拟机就判断是可回收对象。
Java和C#中都是采用根搜索算法来判定对象是否存活的。
标记可达对象:

JVM中用到的所有现代GC算法在回收前都会先找出所有仍存活的对象。根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图。

垃圾收集算法

1. 标记-清除
该算法标记阶段是标记出所有需要被回收的对象,然后清除被标记的。但是很大碎片化问题。

2. 标记整理算法
也是标记要回收的对象,然后不直接清理,而是把存活的都移动到一端,然后清理,解决碎片化问题。

3. 复制算法
内存分为两块。每次使用一块。一块满了之后,就把存活的复制到另一块,然后清除第一块。
解决碎片化,空间利用率低。

4. 分代收集算法(复制+标记清除)
当前JVM采用的算法。
将java堆分成新生代和老年代比例为1:2

3.Java的堆内存(Java Heap Memory)



3.1.年轻代(Young Generation)***

几乎所有新生成的对象首先都是放在年轻代的。新生代内存按照8:1:1的比例分为一个Eden区和两个Survivor(Survivor0,Survivor1)区。大部分对象在Eden区中生成。当Eden满了,则会发起一次GC(Scavenge GC)。回收时先将Eden区存活对象复制到Survivor0区,然后清空Eden区,当这个Survivor0区也放满了,则将Eden区和Survivor0区存活对象复制到另一个Survivor1区,然后清空Eden和这个Survivor0区,此时Survivor0区是空的,然后将Survivor0区和Survivor1区交换,也就是保持Survivor1区为空, 如此往复。当Survivor1区不足以存放 Eden和Survivor0的存活对象时,就将存活对象直接存放到老年代。当对象在Survivor区躲过一次GC,其年龄便会加1,默认情况下,对象年龄达到15岁,就会移动到老年代中。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。

3.2.年老代(Old Generation)***

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,年老代中存放的都是一些生命周期较长的对象。内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,年年代对象存活时间比较长。一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组。比如:
byte[] data = new byte[410241024]
这种一般会直接在老年代分配存储空间。
当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。

3.3.持久代(Permanent Generation)

用于存放静态文件(class类、方法)和常量等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新【增的类。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
永久代空间在Java SE8特性中已经被移除。取而代之的是元空间(MetaSpace)。因此不会再出现“java.lang.OutOfMemoryError: PermGen error”错误。

4.JVM调优,调参数。

Full GC :对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。
有如下原因可能导致Full GC:
1.年老代(Tenured)被写满;
2.持久代(Perm)被写满;
3.System.gc()被显示调用;
4.上一次GC之后Heap的各域分配策略动态变化.

5.与垃圾回收时间相关的两个函数

5.1.System.gc()方法(FullGc 影响速度)

使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。

5.2.2finalize()方法

概述:在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源。但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象以释放资源,这个方法就是finalize()。
在finalize()方法返回之后,对象消失,垃圾收集开始执行。

6.减少gc开销的措施

(1)不要显式调用System.gc()
此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
(4)尽量使用StringBuffer,而不用String来累加字符串
(5)能用基本类型如int,long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

7.GC性能调优

我们可以通过给Java虚拟机分配超大堆(前提是物理机的内存足够大)来提升服务器的响应速度,但分配超大堆的前提是有把握把应用程序的Full GC频率控制得足够低,因为一次Full GC的时间造成比较长时间的停顿。