深入了解jvm垃圾回收

1、为什么要有GC,哪些内存对象需要回收?

对于一个Java开发者来说,了解过Java内存区域的都知道,Java内存区域分了堆、栈、程序计数器等等。

Java的程序计数器,栈内存 ,他们随线程生,随线程灭,方法结束后内存也就回收了。

一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。
常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

当Java虚拟机发现内存资源紧张的时候,就会自动地去清理无用变量所占用的内存空间,为我们的程序提升更高的性能。

2、如何判断对象需要回收?

一般常见的两种回收判断算法:

2.1、 引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。

任何时刻计数器为0的对象就是不可能再被使用的。
深入了解jvm垃圾回收_第1张图片
该方法实现简单,效率高,但是它很难它很难解决对象之间相互循环引用的问题。比如图中的 Object3 和Object4相互引用,引用计数不可能为0,虽然它们已经没有被Root引用了。

所以,大多数jvm判断对象是否存活基本并没有采取该方法。

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

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。

当一个对象到GC Roots没有任何引用链相连时(不可达)则证明此对象是不可用的。

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

深入了解jvm垃圾回收_第2张图片
被认为GC Roots的有以下几种:

  • 虚拟机栈中引用的对象
  • 方法区中静态属性、常量引用的对象
  • Native方法引用的对象

3、如何回收(垃圾收集算法)?

随着Java虚拟机的发展,jvm衍生出了很多种垃圾回收算法。

1、标记-清除(Mark-Sweep)算法

最基础的垃圾回收算法,分为两个阶段,标记和清除。

标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。

深入了解jvm垃圾回收_第3张图片
从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。

2、复制算法(copying )

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为相等大小的两块。

每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:
深入了解jvm垃圾回收_第4张图片
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

3、标记-整理(Mark-Compact)算法

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 标记-清除(Mark-Sweep)算法 相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除两端边界外的对象。
深入了解jvm垃圾回收_第5张图片
直接清除边界的对象也不好,如果边界是老年代,每一次都被清除就很不合理。

4、分代收集算法

分代收集法是目前虚拟机(包括HotSpot VM)收集器都是采用该方法。
深入了解jvm垃圾回收_第6张图片
对象将根据存活的时间被分为:新生代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区),然后进行分代回收,分算法回收。

新生代:对象被创建时,内存的分配首先发生在新生代(准确地说是Eden区,大对象(大于Eden空间)可以直接 被创建在年老代)

新生代又划分为 Eden 区 + Survivor区 (Survivor区又分 from 和 to 区),大小分别占 80%,10%,10%。

Eden区是连续的内存空间,因此在其上分配内存极快。

老年代:老年代存储的对象比新生代多,而且大对象也多。老年代用的算法是标记-整理算法。

永久代(方法区):1.8就没有了,只有元空间。常见的就是常量池、类信息等等。

分代收集算法的GC过程:

(1)在年轻代中,Eden区提供堆内存如果满了,Eden进行MinorGC,将存活的对象→from ,Eden区清空;

(2)Eden区再次满, Eden 区和 from 区同时进行 Minor GC,把存活对象放入 to 区,Eden和from 同时清空;

如果在to区中的对象仍然存活,则把对象标志 +1。

(3)重复(2)的操作, 某些对象在反复 Survive 15 次后,或者Eden+from 的存活对象 > to ,这些对象就只能放到老年代了,如果老年代放不下了,就进行Full GC);

(4)当 Old 区也被填满时,进行 Full GC,对 Old 区进行垃圾回收。

可以通过参数 SurvivorRatio 手动配置 Eden 区和单个 Survivor 区的比例,默认为 8。可以通过参数–XX:SurvivorRatio 来设定,即将堆内存中年轻代划分为8:1:1

4、垃圾回收器

垃圾回收器是虚拟机不断发展产生的,不同的垃圾回收器使用不同的垃圾回收算法(下面讲到)

以HotSpot VM来说,垃圾回收器大致分为七种类型:

  • 串行:Serial New收集器

  • 串行:Serial Old收集器

  • 串行:ParNew收集器

  • 并行:Parallel收集器

  • 并行:Parallel Old 收集器

  • 并发标记扫描CMS收集器

  • G1收集器

  • 深入了解jvm垃圾回收_第7张图片

注:串行、并行 其它工作线程要暂停,并发不会。

5.JVM常见参数

设置合理的jvm参数对Java程序有至关重要的作用。

基本设置:
-Xms300m 起始内存(堆大小)设置为300m
-Xmx 最大内存
-Xmn 新生代内存
-Xss 栈大小。就是创建线程后,分配给每一个线程的内存大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

你可能感兴趣的:(jvm,java)