内存回收与分配重点关注的是堆内存方法区内存(程序计数器占用小,虚拟机栈和本地方法栈随线程有相同的生命周期)。

一、判断对象是否存活?

1. 引用计数算法

优势:实现简单,效率高。

致命缺陷:无法解决对象相互引用的问题——会导致对象的引用虽然存在,但是已经不可能再被使用,却无法被回收。

2. 可达性分析算法

对象到GC Roots没有引用链,则回收。

GC Roots包括:

  • (1)Java虚拟机栈中引用的对象。
  • (2)方法区中类静态属性引用的对象。
  • (3)方法去中常量引用的对象。
  • (4)本地方法栈中Native方法(JNI)引用的对象。

3. 关于引用

JDK1.2之后,Java对引用进行了扩充。

  • (1)强引用:Object obj = new Object(),不会被jvm回收
  • (2)软引用:在内存溢出异常发生之前才被强制回收。
  • (3)弱引用:延迟到下次垃圾回收之前再被回收。
  • (4)虚引用:仅为了在被回收时收到一个系统通知。

4. finalize方法:

在对象被JVM回收之前,有一个低优先级的线程去执行。只有覆盖了finalize方法,才会执行,且只会执行一次。

5. 回收方法区:

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

二、垃圾收集算法

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

  • 效率问题:标记和清除两个过程的效率都不高。
  • 空间问题:清除后剩余空间零散不连续,无法为大的对象分配内存。

2. 复制算法

将内存分为两个半区,将区A中的存活对象全部复制到B区的连续空间,然后清理A中所有空间。

缺点:内存实际空间减半。在对象存活率较高时需要进行较多的复制操作。

实际应用:将堆内存分为新生代和老年代。由于新生代中的对象98%都是可回收的,故将新生代又划分为Eden空间和两块较小的Survivor空间,默认Eden:Survivor(单个)=8:1

这样每次垃圾收集时,将Eden和S1中的存活对象复制到S2,然后清空Eden和S1区。

为了防止S2中空间不足以存储Eden和S1的所有剩余存活对象,提供老年代作为保障(Handle Promotion:分配担保)。

3. 标记-整理算法(将存活对象移动到一起)

将标记后的存活对象进行移动,清除剩余对象。

应用:老年代

4. 分代收集

  • 新生代:存活率低,使用复制算法
  • 老年代:存活率高,使用“标记-整理”或“标记-清除”算法

三、垃圾收集器

垃圾收集中的并行与并发:

  • 并行(Parallel):多条垃圾收集线程
  • 并发(Concurrent):用户线程与垃圾收集线程同时执行

1. Serial收集器

单线程、Stop the World.

Client模式下的默认新生代收集器(一个Client分配给JVM管理的内存一般不会很大,收集时间一般很快)。简单高效(相对于其他单线程收集器)

主要参数:

-XX:SurvivoRatio:新生代中Eden区域和Survivor区域(单个Survivor)的容量比值,默认为8

-XX:PretenureSizeThreshold:直接晋升到老年代的对象大小,大于该值的对象直接在老年代分配。

-XX:HandlePromotionFailure:允许老年代分配担保失败,开启后可以冒险YGC。

2. ParNew收集器

Serial的多线程版本(多条垃圾收集线程)。

Server模式下首选的新生代收集器。

除Serial外,只有它能与CMS收集器配合工作。

3. Parallel Scavenge收集器

新生代、复制算法、多线程。

注重吞吐量,适合后台进程。

-XX:MacGCPauseMillis:最大垃圾收集停顿时间

-XX:GCTimeRatio:吞吐量大小

GCTimeRatio=99,意味着允许最大垃圾收集时间占比为1/(1+99)=1%,GCTimeRatio=用户代码运行时间/GC时间。

-XX:+UseAdaptiveSizePolicy:动态自适应调整JVM参数(-Xmn、SurvivorRatio等)

4. Serial Old收集器

Serial的老年代版本,使用“标准—整理”算法,适合client端。

5. Parallel Old收集器

Parallel Scavenge的老年代版本

6. CMS收集器(Concurrent Mark Sweep)

老年代、GC系统停顿时间最短

“标记-清除”

四个阶段:

  • (1)初始标记:找GC Roots
  • (2)并发标记:找引用链
  • (3)重新标记:找变更
  • (4)并发清除:清除

(1)(3):stop the world

(2)(4):与用户线程并发

缺陷:

  • 对cpu资源敏感:垃圾收集线程数和每个收集线程占用cpu的时间受cpu数量影响

  • 无法处理“浮动垃圾”

    即并发清除阶段用户线程又产生的对象。

    -XX:CMSInitiatingOccupancyFranction:老年代被使用的百分比,达到时触发GC(如果等老年代占满了再GC,则GC时并发产生的对象可能就获取不到存储空间)

    CMSInitiatingOccupancyFranction过高会导致大量Concurrent Mode Failure,即老年代预留的内存无法满足程序需要。

  • 内存空间碎片

    -XX:+UseCMSCompactAtFullCollection:FullGC时启动内存碎片的合并整理

    -XX:CMSFullGCsBeforeCompaction:执行多少次不压缩的FullGC后压缩一次,默认为0。即每次FullGC时都合并整理内存碎片。

7. G1收集器

最前沿成果。削弱新生代与老年代概念,将整个堆划分为独立的不同Region。根据各Region的回收价值,确定优先列表。

从整体来看:“标记-整理” 算法

从局部(两个Region之间)来看:“复制”算法

四、内存分配与回收策略

1. 优先在Eden区分配(如果启动本地线程分配缓冲TLAB-Thread Local Allocation Buffer,则优先在TLAB)

如果Eden区满,则触发一次Minor GC(也称Young GC)

-XX:+PrintGCDetails

  • 在JVM发生垃圾收集时打印内存回收日志
  • 在进程退出时输出当前各区域的内存分配情况

在JDK8中,PermGen(永久代)被Metaspace(元空间)取代了。

2. 大对象直接进入老年代

-XX:PretenureSizeThreshold:直接进入老年代的对象大小

3. 长期存活的对象将进入老年代

-XX:MaxTenuringThreshold:设置对象在新生代中能存活的最大年龄,默认15

-XX:+PrintTenuringDistribution:打印老年代内的各年龄对象内存分配情况

4. 动态对象年龄判定

若Survivor中相同年龄的所有对象大小总和超过Survivor的一半,则年龄大于或等于该年龄的对象就可以直接进入老年代。

5. 空间分配担保

  • (1)YGC发生前先检查“老年代最大可用的连续空间”是否大于新生代所有对象总空间。如果是,则触发YGC,无风险;如果否,进入(2)
  • (2)再次判断“老年代最大可用的连续空间”是否大于历次平均晋升到老年代的对象大小总和。如果否,则直接执行FullGC;如果是,进入(3)
  • (3)执行YGC,有风险。
  • (4)如果3中的YGC失败,则再执行FullGC。

五、垃圾回收和GC实战

1. YGC测试代码:

/**
* @author zni.feng
*/
import java.lang.management.ManagementFactory; public class YGCTest {
/**
* VM参数: -verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
*/
private static final int _1MB= 1024 * 1024; public static void main(String[] args) {
System.out.println(ManagementFactory.getRuntimeMXBean().getInputArguments());//打印JVM参数
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2*_1MB];
allocation2 = new byte[2*_1MB];
allocation3 = new byte[2*_1MB];
allocation4 = new byte[4*_1MB]; //出现一次YGC
System.out.println("exit");
} }

测试结果:

[-verbose:gc, -XX:+UseSerialGC, -Xms20M, -Xmx20M, -Xmn10M, -XX:+PrintGCDetails, -XX:SurvivorRatio=8, -Dfile.encoding=UTF-8]
[GC (Allocation Failure) [DefNew: 6815K->282K(9216K), 0.0077121 secs] 6815K->6426K(19456K), 0.0077785 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
exit
Heap
def new generation total 9216K, used 4620K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 52% used [0x00000007bec00000, 0x00000007bf03c8d8, 0x00000007bf400000)
from space 1024K, 27% used [0x00000007bf500000, 0x00000007bf546800, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
Metaspace used 2695K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 294K, capacity 386K, committed 512K, reserved 1048576K

测试结果分析:

从结果可以看到,发生了一次YGC,GC后新生代(DefNew:Default New Generaion)从6815K降到了282K,即基本上全部回收,而整个堆内存的大小并没有显著减小(从6815K降到了6426K),这是因为allocation1、allocation2、allocation3对象仍存在(强引用),并没有被真正回收,只是从新生代进入了老年代(Survivor区不足以容纳6M的对象)。并且,从程序结束时的各区域内存分配情况可以看到,老年代占用了约6M(tenured generation total 10240K,used 6144K);新生代Eden区占用了52%,约4M(total 8192K,即8M),是用来存放allocation4的对象。

2. 大对象直接进入老年代

/**
* @author zni.feng
*/
public class PretenureSizeThresholdTest {
/**
* VM参数: -verbose:gc -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
* (3145728=3*1024*1024=3MB)
*/ private static final int _1MB=1024*1024;
public static void main(String[] args) {
byte[] allocation;
allocation = new byte[4*_1MB]; } }

测试结果:

Heap
def new generation total 9216K, used 835K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 10% used [0x00000007bec00000, 0x00000007becd0f90, 0x00000007bf400000)
from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
Metaspace used 2621K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K

测试结果分析:可以看到,allocation对象直接进入了老年代。

JVM学习笔记三:垃圾收集器与内存分配策略的更多相关文章

  1. 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...

  2. JVM基础知识2 垃圾收集器与内存分配策略

    如何判断堆中的哪些对象可以被回收 主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活 基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下 ...

  3. 深入理解JVM(三)垃圾收集器和内存分配策略

    3.1 关于垃圾收集和内存分配 垃圾收集和内存分配主要针对的区域是Java虚拟机中的堆和方法区: 3.2 如何判断对象是否“存活”(存活判定算法) 垃圾收集器在回收对象前判断其是否“存活”的两个算法: ...

  4. JVM系列2:垃圾收集器与内存分配策略

    垃圾收集是一个很大话题,本文也只是看了深入理解Java虚拟机总结了下垃圾收集的知识. 首先按照惯例,先上思维导图: 垃圾收集简而言之就是JVM帮我们清理掉内存区域不需要的数据.它主要负责清理堆中实例对 ...

  5. 《深入理解 Java 虚拟机》读书笔记:垃圾收集器与内存分配策略

    正文 垃圾收集器关注的是 Java 堆和方法区,因为这部分内存的分配和回收是动态的.只有在程序处于运行期间时才能知道会创建哪些对象,也才能知道需要多少内存. 虚拟机栈和本地方法栈则不需要过多考虑回收的 ...

  6. 《深入理解Java虚拟机》读书笔记:垃圾收集器与内存分配策略

    请移步至:http://zhanjindong.info/2014/05/18/java-gc/

  7. JVM学习笔记-第三章-垃圾收集器与内存分配策略

    JVM学习笔记-第三章-垃圾收集器与内存分配策略 tips:对于3.4之前的章节可见博客:https://blog.csdn.net/sanhewuyang/article/details/95380 ...

  8. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  9. 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略

    1.  前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...

  10. jvm系列 (二) ---垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 前言:本文基于<深入java虚拟机>再加上个人的理解以及其他相关资料,对内容进行整理浓缩总结.本文中的图来自网络,感谢图的作者.如果有不正确的地方,欢迎指出. 目 ...

随机推荐

  1. C语言中关于三目运算符的注意事项

    C语言中常见的条件运算符?:在运算符优先级中排行13.部分时候可以代替if--else语句,使代码更加简洁.但是更容易隐含一些不易觉察的错误. 最近接了一个项目,本来通信协议部分很简单,自己的STM3 ...

  2. Virtualbox 复制 CentOS 虚拟机无法联网

    Centos刚装好后无法联网 复制虚拟机后,出现 No such device eth0 我们要处理的三个问题: 在Virtualbox上安装好Centos后如何联网 如何在Virtualbox上复制 ...

  3. vue-router2 使用

    VUE-ROUTER2  API http://router.vuejs.org/zh-cn/api/router-link.html   1,安装vue-router npm install vue ...

  4. JAVA----类的继承1(extends)

    要学习类的继承,首先应当理解继承的含义: 来自新华词典的释义: ①依法承受(死者的遗产等):-权ㄧ-人. ②泛指把前人的作风.文化.知识等接受过来:-优良传统ㄧ-文化遗产. ③后人继续做前人遗留下来的 ...

  5. Azure IoT 技术研究系列5-Azure IoT Hub与Event Hub比较

    上篇博文中,我们介绍了Azure IoT Hub的使用配额和缩放级别: Azure IoT 技术研究系列4-Azure IoT Hub的配额及缩放级别 本文中,我们比较一下Azure IoT Hub和 ...

  6. hadoop单机环境搭建

    [在此处输入文章标题] Hadoop单机搭建 1. 工具准备 1) Hadoop Linux安装包 2) VMware虚拟机 3) Java Linux安装包 4) Window 电脑一台 2. 开始 ...

  7. jquery判断文本框输入的是非数字内容(交流QQ群:452892873)

    isNaN($(this).val())==false   输入的是数字, isNaN($(this).val())==true  输入的是非数字内容

  8. Docker - 在Ubuntu16.04中安装Docker CE

    Get Docker for Ubuntu Check system version root@Ubuntu16:~# uname -a Linux Ubuntu16 4.8.0-36-generic ...

  9. poj 1001 分析

    1) n = 0; return 1: 2) n = 1; bool standardizeNumNoDot(string &s){标准化是一定要得} _将‘.’前后的〇全部去除,正常retu ...

  10. 深入浅出理解yield

    索引 转载部分内容来自:http://www.jianshu.com/p/d09778f4e055 [彻底理解yield] http://blog.csdn.net/haskei/article/de ...