概述
上一篇讲解了JVM 的内存模型,这篇介绍一下JVM的垃圾回收机制。
如何定义垃圾
有两种方式,一种是引用计数(但是无法解决循环引用的问题);另一种就是可达性分析。
判断对象可以回收的情况:
- 显示的把某个引用置位NULL或者指向别的对象
- 局部引用指向的对象
- 弱引用关联的对象
垃圾回收的方法
Mark-Sweep标记-清除算法 (老年代),这种方法优点就是减少停顿时间,但是缺点是会造成内存碎片。
Copying复制算法, 这种方法不涉及到对象的删除,只是把可用的对象从一个地方拷贝到另一个地方,因此适合大量对象回收的场景,比如新生代的回收。
Mark-Compact标记-整理算法,这种方法可以解决内存碎片问题,但是会增加停顿时间。
Generational Collection 分代收集
最后的这种方法是前面几种的合体,即目前JVM主要采取的一种方法,思想就是把JVM分成不同的区域。每种区域使用不同的垃圾回收方法。
上面可以看到堆分成三个区域:
- 新生代(Young Generation):用于存放新创建的对象,采用复制回收方法,如果在s0和s1之间复制一定次数后,转移到年老代中。这里的垃圾回收叫做minor GC;
- 年老代(Old Generation):这些对象垃圾回收的频率较低,采用的标记整理方法,这里的垃圾回收叫做 major GC。
- 永久代(Permanent Generation):存放Java本身的一些数据,当类不再使用时,也会被回收。
新生代-复制算法
该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象复制到另外一块上面, 然后把已使用过的内存空间一次清理掉.
这使得每次只对其中一块内存进行回收, 分配也就不用考虑内存碎片等复杂情况, 实现简单且运行高效.
现代商用VM的新生代均采用复制算法, 但由于新生代中的98%的对象都是生存周期极短的, 因此并不需完全按照1∶1的比例划分新生代空间, 而是将新生代划分为一块较大的Eden区和两块较小的Survivor区(HotSpot默认Eden和Survivor的大小比例为8∶1), 每次只用Eden和其中一块Survivor. 当发生MinorGC时, 将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor上, 最后清理掉Eden和刚才用过的Survivor的空间. 当Survivor空间不够用(不足以保存尚存活的对象)时, 需要依赖老年代进行空间分配担保机制, 这部分内存直接进入老年代.
老年代-标记清除算法
该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象.
该算法会有以下两个问题:
1. 效率问题: 标记和清除过程的效率都不高;
2. 空间问题: 标记清除后会产生大量不连续的内存碎片, 空间碎片太多可能会导致在运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集.
老年代-标记整理算法
标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存.
永久代-方法区回收
- 在方法区进行垃圾回收一般”性价比”较低, 因为在方法区主要回收两部分内容: 废弃常量和无用的类. 回收废弃常量与回收其他年代中的对象类似, 但要判断一个类是否无用则条件相当苛刻:
- 该类所有的实例都已经被回收, Java堆中不存在该类的任何实例;
- 该类对应的
Class
对象没有在任何地方被引用(也就是在任何地方都无法通过反射访问该类的方法); - 加载该类的ClassLoader已经被回收.
但即使满足以上条件也未必一定会回收, Hotspot VM还提供了-Xnoclassgc参数控制(关闭CLASS的垃圾回收功能). 因此在大量使用动态代理、CGLib等字节码框架的应用中一定要关闭该选项, 开启VM的类卸载功能, 以保证方法区不会溢出.
附:(面试被问到过)
Minor GC ,Full GC 触发条件
- Major GC 清理年老区(Tenured space).
- Full GC 清理整个内存堆 – 既包括年轻代也包括年老代.
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
JVM小工具
在${JAVA_HOME}/bin/目录下Sun/Oracle给我们提供了一些处理应用程序性能问题、定位故障的工具, 包含
bin | 描述 | 功能 |
---|---|---|
jps | 打印Hotspot VM进程 | VMID、JVM参数、main() 函数参数、主类名/Jar路径 |
jstat | 查看Hotspot VM 运行时信息 | 类加载、内存、GC[可分代查看]、JIT编译 |
jinfo | 查看和修改虚拟机各项配置 | -flag name=value |
jmap | heapdump: 生成VM堆转储快照、查询finalize执行队列、Java堆和永久代详细信息 | jmap -dump:live,format=b,file=heap.bin [VMID] |
jstack | 查看VM当前时刻的线程快照: 当前VM内每一条线程正在执行的方法堆栈集合 | Thread.getAllStackTraces() 提供了类似的功能 |
javap | 查看经javac之后产生的JVM字节码代码 | 自动解析.class 文件, 避免了去理解class文件格式以及手动解析class文件内容 |
jcmd | 一个多功能工具, 可以用来导出堆, 查看Java进程、导出线程信息、 执行GC、查看性能相关数据等 | 几乎集合了jps、jstat、jinfo、jmap、jstack所有功能 |
jconsole | 基于JMX的可视化监视、管理工具 | 可以查看内存、线程、类、CPU信息, 以及对JMX MBean进行管理 |
jvisualvm | JDK中最强大运行监视和故障处理工具 | 可以监控内存泄露、跟踪垃圾回收、执行时内存分析、CPU分析、线程分析… |
VM常用参数整理
参数 | 描述 |
---|---|
-Xms | 最小堆大小 |
-Xmx | 最大堆大小 |
-Xmn | 新生代大小 |
-XX:PermSize | 永久代大小 |
-XX:MaxPermSize | 永久代最大大小 |
-XX:+PrintGC | 输出GC日志 |
-verbose:gc | - |
-XX:+PrintGCDetails | 输出GC的详细日志 |
-XX:+PrintGCTimeStamps | 输出GC时间戳(以基准时间的形式) |
-XX:+PrintHeapAtGC | 在进行GC的前后打印出堆的信息 |
-Xloggc:/path/gc.log | 日志文件的输出路径 |
-XX:+PrintGCApplicationStoppedTime | 打印由GC产生的停顿时间 |
总结
在众多的垃圾回收器中,没有最好的,只有最适合应用的回收器,根据应用软件的特性以及硬件平台的特点,选择不同的垃圾回收器,才能有效的提高系统性能。
Minor GC 和 Full GC 有什么区别?
新生代 GC (Minor GC) :发生在新生代的垃圾收集动作。Minor GC 非常频繁,回收速度比较快。
老年代 GC (Major GC/Full GC):发生在老年代的 GC, Major GC 一般比 Minor GC 慢 10 倍以上。
内存分配规则
对象优先在 Eden 分配
大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起 Minor GC。
大对象直接进入老年代
长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在 Eden 出生在 Survivor 区中每熬过一次 Minor GC,年龄加 1 岁,当年龄到一定程度(默认15岁),就会晋升到老年代中。
动态对象年龄判定
在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可能连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这个 Minor GC 是有风险的;如果小于,或 HandlePromotionFailure 设置不允许冒险,那这次也要改为进行一次 Full GC。
优秀的编程习惯
(1)避免在循环体中创建对象,即使该对象占用内存空间不大。
(2)尽量及时使对象符合垃圾回收标准。
(3)不要采用过深的继承层次。
(4)访问本地变量优于访问类中的变量。
最后
以上就是壮观皮皮虾为你收集整理的JVM 垃圾回收机制总结的全部内容,希望文章能够帮你解决JVM 垃圾回收机制总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复