Java堆中是JVM管理的最大一块内存空间。主要存放对象实例。
在JAVA中堆被分为两块区域:新生代(young)、老年代(old)。
堆大小=新生代+老年代;(新生代占堆空间的1/3、老年代占堆空间2/3)
新生代又被分为了eden、from survivor、to survivor(8:1:1);
新生代这样划分是为了更好的管理堆内存中的对象,方便GC算法---复制算法来进行垃圾回收。
JVM每次只会使用eden和其中一块survivor来为对象服务,所以无论什么时候,都会有一块survivor空间,因此新生代实际可用空间只有90%。
新生代GC(minor gc)----------指发生在新生代的垃圾回收动作,因为JAVA对象大多数都是朝生夕死的特性,所以minor gc非常平凡,使用复制算法快速的回收。
新生代几乎是所有JAVA对象出生的地方,JAVA对象申请的内存和存放都是在这个地方。
当对象在eden(其中包括一个survivor,假如是from),当此对象经过一次minor gc后仍然存活,并且能够被另外一块survivor所容纳(这里survivor则是to了),则使用复制算法将这些仍然存活的对象复制到to survior区域中,然后清理掉eden和from survivor区域,并将这些存活的对象年龄+1,以后对象在survivor中每熬过一次gc则增加1,当年龄达到某个值时(默认15,通过设置参数-xx:maxtenuringThreshold来设置),这些对象就会成为老年代!
但是也不一定,当一些较大的对象(需要分配连续的内存空间)则直接进入老年代。
 
 
老年代GC(major gc)----------指发生在老年代的垃圾回收动作,所采用是的标记--整理算法。
老年代几乎都是经过survivor熬过来的,它们是不会那么容易“死掉”,因此major gc不会想minor gc那样频繁。
 
 

JVM 新生代为何需要两个 Survivor 空间?

我们知道,目前主流的虚拟机实现都采用了分代收集的思想,把整个堆区划分为新生代和老年代;新生代又被划分成 Eden 空间、 From Survivor 和 To Survivor 三块区域。

看书的时候有个疑问,为什么非得是两个 Survivor 空间呢?要回答这个问题,其实等价于:为什么不是0个或1个 Survivor 空间?为什么2个 Survivor 空间可以达到要求?

为什么不是0个 Survivor 空间?

这个问题等价于:为什么需要 Survivor 空间。我们看看如果没有 Survivor 空间的话,垃圾收集将会怎样进行:一遍新生代 gc 过后,不管三七二十一,活着的对象全部进入老年代,即便它在接下来的几次 gc 过程中极有可能被回收掉。这样的话老年代很快被填满, Full GC 的频率大大增加。我们知道,老年代一般都会被规划成比新生代大很多,对它进行垃圾收集会消耗比较长的时间;如果收集的频率又很快的话,那就更糟糕了。基于这种考虑,虚拟机引进了“幸存区”的概念:如果对象在某次新生代 gc 之后任然存活,让它暂时进入幸存区;以后每熬过一次 gc ,让对象的年龄+1,直到其年龄达到某个设定的值(比如15岁), JVM 认为它很有可能是个“老不死的”对象,再呆在幸存区没有必要(而且老是在两个幸存区之间反复地复制也需要消耗资源),才会把它转移到老年代。

总之,设置 Survivor 空间的目的是让那些中等寿命的对象尽量在 Minor GC 时被干掉,最终在总体上减少虚拟机的垃圾收集过程对用户程序的影响。

为什么不是1个 Survivor 空间?

回答这个问题有一个前提,就是新生代一般都采用复制算法进行垃圾收集。原始的复制算法是把一块内存一分为二, gc 时把存活的对象(Eden和Survivor to)从一块空间(From space)复制到另外一块空间(To space),再把原先的那块内存(From space)清理干净,最后调换 From space 和 To space 的逻辑角色(这样下一次 gc 的时候还可以按这样的方式进行)。

我们知道,在 HotSpot 虚拟机里, Eden 空间和 Survivor 空间默认的比例是 8:1 。我们来看看在只有一个 Survivor 空间的情况下,这个 8:1 会有什么问题。此处为了方便说明,我们假设新生代一共为 9 MB 。对象优先在 Eden 区分配,当 Eden 空间满 8 MB 时,触发第一次 Minor GC 。比如说有 0.5 MB 的对象存活,那这 0.5 MB 的对象将由 Eden 区向 Survivor 区复制。这次 Minor GC 过后, Eden 区被清理干净, Survivor 区被占用了 0.5 MB ,还剩 0.5 MB 。到这里一切都很美好,但问题马上就来了:从现在开始所有对象将会在这剩下的 0.5 MB 的空间上被分配,很快就会发现空间不足,于是只好触发下一次 Minor GC 。可以看出在这种情况下,当 Survivor 空间作为对象“出生地”的时候,很容易触发 Minor GC ,这种 8:1 的不对称分配不但没能在总体上降低 Minor GC 的频率,还会把 gc 的时间间隔搞得很不平均。把 Eden : Survivor 设成 1 : 1 也一样,每当对象总大小满 5 MB 的时候都必须触发一次 Minor GC ,唯一的变化是 gc 的时间间隔相对平均了。

上面的论述都是以“新生代使用复制算法”这个既定事实作为前提来讨论的。如果不是这样,比如说新生代采用“标记-清除”或者“标记-整理”算法来实现幸存对象的移动,好像确实是只需要一个 Survivor 就够了。至于主流的虚拟机实现为什么不考虑采用这种方式,我也不是很清楚,或许有实现难度、内存碎片或者执行效率方面的考虑吧。

为什么2个 Survivor 空间可以达到要求?

问题很清楚了,无论 Eden 和 Survivor 的比例怎么设置,在只有一个 Survivor 的情况下,总体上看在新生代空间满一半的时候就会触发一次 Minor GC 。那有没有提升的空间呢?比如说永远在新生代空间满 80% 的时候才触发 Minor GC ?

事实上是可以做到的:我们可以设两个 Survivor 空间( From Survivor 和 To Survivor )。比如,我们把 Eden : From Survivor : To Survivor 空间大小设成 8 : 1 : 1 ,对象总是在 Eden 区出生, From Survivor 保存当前的幸存对象, To Survivor 为空。一次 gc 发生后: 
1)Eden 区活着的对象 + From Survivor 存储的对象被复制到 To Survivor ; 
2) 清空 Eden 和 From Survivor ; 
3) 颠倒 From Survivor 和 To Survivor 的逻辑关系: From 变 To , To 变 From 。

可以看出,只有在 Eden 空间快满的时候才会触发 Minor GC 。而 Eden 空间占新生代的绝大部分,所以 Minor GC 的频率得以降低。当然,使用两个 Survivor 这种方式我们也付出了一定的代价,如 10% 的空间浪费、复制对象的开销等。

 
分类: JVM

JAVA分代收集机制详解的更多相关文章

  1. 【转】java的动态代理机制详解

    java的动态代理机制详解   在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们 ...

  2. java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

    java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html

  3. java 深拷贝与浅拷贝机制详解

    概要: 在Java中,拷贝分为深拷贝和浅拷贝两种.java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝. (一 ...

  4. Java的内存回收机制详解X

    http://blog.csdn.net/yqlakers/article/details/70138786 1 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前 ...

  5. Java并发之Synchronized机制详解

    带着问题阅读 1.Synchronized如何使用,加锁的粒度分别是什么 2.Synchronized的实现机制是什么 3.Synchronized是公平锁吗 4.Java对Synchronized做 ...

  6. 初识JVM:(二)Java的垃圾回收机制详解

    声明:本文主要参考https://www.cnblogs.com/codeobj/p/12021041.html 仅供个人学习.研究之用,请勿用于商业用途,如涉及侵权,请及时反馈,立刻删除. 一.Ja ...

  7. JAVA中的GC机制详解

    优秀Java程序员必须了解的GC工作原理 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只 ...

  8. java虚拟机垃圾回收机制详解

    首先,看一下java虚拟机运行的时候内存分配图: jvm虚拟机栈:一个是线程独有的,每次启动一个线程,就创建一个jvm虚拟机栈,线程退出的时候就销毁.这里面主要保存线程本地变量名和局部变量值. 本地方 ...

  9. java的动态代理机制详解

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

随机推荐

  1. lombok问题

    今天研究了下以dubbo作为分布式的开源项目dubbo-app. 为了排除一些依赖的冲突和干扰,我另外开辟一个新的工作空间,同时我也将公司项目的依赖打个压缩包分类备份下. 这样一来,dubbo-app ...

  2. List集合的子类ArrayList和LinkedList

    一: 我们常用对集合的操作,查询.增删等操作. 由于集合的存储的方式的不同,导致有些集合查询快但是增删慢.有些集合增删快.但是查询慢. ArrayList:由于ArrayList存储的方式为数组形式. ...

  3. Jmeter之集合点与关联

    在Jmeter中,实现类似于LoadRunner中集合点的方法是采用同步定时器(Synchronizing Timer),而实现类似于LoadRunner中关联的方法是采用正则表达式提取器. 一.集合 ...

  4. KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程

    接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从gue ...

  5. iOS 输入框限制输入字节数

    iOS中限制输入框文字长度的方法有好多,百度一下,最常用的是这种: - (BOOL)textField:(UITextField *)textField shouldChangeCharactersI ...

  6. iOS开发-Object-C获取手机设备信息(UIDevice)

    一.获取UiDevice设备信息 // 获取设备名称 NSString *name = [[UIDevice currentDevice] name]; // 获取设备系统名称 NSString *s ...

  7. UML类图6种主要关系区别和联系

    UML类图关系图示,因为长得都很类似,所以大家总会混淆,本文主要目的就是分析一下6种主要的关系,找到联系与区别,便于记忆. 6种主要的关系如图1所示.继承与实现.组合与聚合.关联与依赖可分别划分为一组 ...

  8. CPP strcat函数使用

    strcat函数原型 char * strcat ( char * destination, const char * source ); strcat常见写法 // main.cpp // 字符数组 ...

  9. Linux下onvif客户端获取ipc摄像头 GetStreamUri:rtsp地址(h264、h265)

    GetStreamUri:rtsp地址 鉴权:但是在使用这个接口之前是需要鉴权的.ONVIF协议规定,部分接口需要鉴权,部分接口不需要鉴权,在调用需要鉴权的接口时不使用鉴权,会导致接口调用失败.实现鉴 ...

  10. Scala(三):类

    类:Class 1.简单类和无参方法 2.带getter和setter属性 3.只带getter属性 4.对象私有字段 5.Bean属性 6.辅助构造器 7.主构造器 8.嵌套类 1.简单类和无参方法 ...