深入了解jvm垃圾回收
1、为什么要有GC,哪些内存对象需要回收?
对于一个Java开发者来说,了解过Java内存区域的都知道,Java内存区域分了堆、栈、程序计数器等等。
Java的程序计数器,栈内存 ,他们随线程生,随线程灭,方法结束后内存也就回收了。
一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。
常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
当Java虚拟机发现内存资源紧张的时候,就会自动地去清理无用变量所占用的内存空间,为我们的程序提升更高的性能。
2、如何判断对象需要回收?
一般常见的两种回收判断算法:
2.1、 引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。
任何时刻计数器为0的对象就是不可能再被使用的。
该方法实现简单,效率高,但是它很难它很难解决对象之间相互循环引用的问题。比如图中的 Object3 和Object4相互引用,引用计数不可能为0,虽然它们已经没有被Root引用了。
所以,大多数jvm判断对象是否存活基本并没有采取该方法。
2.2、可达性分析算法(根搜索算法)
这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。
当一个对象到GC Roots没有任何引用链相连时(不可达)则证明此对象是不可用的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
被认为GC Roots的有以下几种:
- 虚拟机栈中引用的对象
- 方法区中静态属性、常量引用的对象
- Native方法引用的对象
3、如何回收(垃圾收集算法)?
随着Java虚拟机的发展,jvm衍生出了很多种垃圾回收算法。
1、标记-清除(Mark-Sweep)算法
最基础的垃圾回收算法,分为两个阶段,标记和清除。
标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
2、复制算法(copying )
为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为相等大小的两块。
每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。
3、标记-整理(Mark-Compact)算法
结合了以上两个算法,为了避免缺陷而提出。标记阶段和 标记-清除(Mark-Sweep)算法 相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除两端边界外的对象。
直接清除边界的对象也不好,如果边界是老年代,每一次都被清除就很不合理。
4、分代收集算法
分代收集法是目前虚拟机(包括HotSpot VM)收集器都是采用该方法。
对象将根据存活的时间被分为:新生代(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收集器
注:串行、并行 其它工作线程要暂停,并发不会。
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数。并行收集线程数。