1. 什么是GC
GC全称是Garbage Collection, 垃圾收集
内存的空间是有限的,那么在程序的运行时如何及时把不再使用的对象(垃圾对象)清楚从而释放资源呢,这就是GC要做的事情.
2. GC的操作区域
JVM是有内存划分的,再回忆下JVM内存划分,以JDK1.8的JVM内存划分为例.
绿色的呢是线程私有的区域,也就是每个线程都有自己的这部分内存; 而白色的呢是线程共享,也就是所有线程公用这部分内存.
- 线程私有的不需要进行GC,像是Java虚拟机栈,本地方法栈,程序计数器是不需要GC,因为这个都是线程私有的,线程私有的就会随着线程的产生而产生,随着线程的结束而销毁.
- JVM内存中的堆和方法区是需要GC,需要GC来及时的清理运行过程中产生的垃圾.
3. 如何发现垃圾
垃圾对象,就是不再使用的对象.
Java语言规范并没有明确的说明JVM使用哪种垃圾回收算法,但是常见的用于「发现垃圾」的算法有两种,引用计数算法和根搜索算法。
- 引用计数算法
该算法很古老(了解即可)。核心思想是,堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时可以被当作垃圾收集。
优点:快。
缺点:无法检测出循环引用。如两个对象互相引用时,他们的引用计数永远不可能为0。 - 根搜索算法
根搜索算法是把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。
Java中可作为GC Root的对象有
1.虚拟机栈中引用的对象
2.本地方法栈引用的对象
2.方法区中静态属性的引用对象
3.方法区中常量的引用对象
4. GC的运行时机
- 手动调用 System.gc(), 可以触发GC操作.
- 系统本身自己触发: 内存不足时就会触发.
5. GC做了什么事情
- 清理对象
- 整理内存
6. 如何回收垃圾
- 标记-清除算法
- 标记-压缩算法
- 复制算法
- 分代收集算法
JVM采用的是分代收集算法.
(1)标记-清除算法
给每个对象存储一个标记位, 记录对象的状态(死/活), 分为两个阶段.
第一是标记阶段:去检查对象的状态, 判断对象是否死亡.
第二是清除阶段: 将死亡对象清理掉.
缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不连续的内存碎片。
(2)标记-压缩算法
标记-压缩算法是标记-清楚算法的一个改进版. 也分为两个阶段.
第一是标记阶段: 去检查对象的状态,判断对象是否死亡.
第二阶段: 将所有存活的对象整理放到另外一处空间,把剩余的死亡对象清除掉.
优点:内存被整理后不会产生大量不连续内存碎片。
(3)复制算法
会将内存平均分两块,每次只使用其中的一块.当这个内存存满了, 就会将这个块内存中存活的对象复制到另外一块内存中.将刚才那块内存清空.
缺点:可使用的内存只有原来一半。
(4)分代收集算法
当前主流JVM都采用分代收集(Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。堆内存细分, 还可分为新生代, 老年代, 永久代.
在新生代里对象存活的时间短,所以采用的算法是复制算法.
在老年代里对象存活率高,所以采用标记-清除算法.
最后还存活下来的对象,放到永久代中.
年轻代(Young Generation)
1.所有新生成的对象首先都是放在年轻代的。
2.新生代内存按照8:1:1的比例分为一个eden区和两个Survivor(survivor0,survivor1)区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
3.当survivor1区不足以存放eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)
年老代(Old Generation)
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
2.内存比新生代也大很多(大概是2倍),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率比较高。
持久代(Permanent Generation)
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃, 将存放静态文件,如Java类、方法等这些存储到了元数据区.
7.JVM调优参数
这里只给出一些常见的性能调优的参数及其代表的含义。(大家记住5.6个就行, 并不需要都记住.)
-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。注意:此值一般设置成和-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss256k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,以前每个线程栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。
-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4。(该值默认为2)
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4。