本文转自https://blog.csdn.net/rabbit_in_android/article/details/50386954

内存管理和垃圾回收

JVM内存组成结构

JVM栈由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:

JVM内存回收

Sun的JVMGenerationalCollecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)

1.Young(年轻代)

年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

2.Tenured(年老代)

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

3.Perm(持久代)

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。

关于JVM内存管理的一些建议

1、手动将生成的无用对象,中间对象置为null,加快内存回收。

2、对象池技术如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。

3、JVM调优通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证JVM内存回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。

垃圾对象的判定

Java堆中存放着几乎所有的对象实例,垃圾收集器对堆中的对象进行回收前,要先确定这些对象是否还有用,判定对象是否为垃圾对象有如下算法:

引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器都为0的对象就是不可能再被使用的。

引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的选择,当Java语言并没有选择这种算法来进行垃圾回收,主要原因是它很难解决对象之间的相互循环引用问题。

根搜索算法

Java和C#中都是采用根搜索算法来判定对象是否存活的。这种算法的基本思路是通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。在Java语言里,可作为GC Roots的兑现包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI(Native方法)的引用对象。

实际上,在根搜索算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会(因为一个对象的finalize()方法最多只会被系统自动调用一次),稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中让该对象重新引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。

垃圾收集算法

判定除了垃圾对象之后,便可以进行垃圾回收了。下面介绍一些垃圾收集算法,由于垃圾收集算法的实现涉及大量的程序细节,因此这里主要是阐明各算法的实现思想,而不去细论算法的具体实现。

标记—清除算法

标记—清除算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。标记—清除算法的执行情况如下图所示:

回收前状态:

    回收后状态:

该算法有如下缺点:

  • 标记和清除过程的效率都不高。
  • 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不触发另一次垃圾收集动作。

复制算法

复制算法比较适合于新生代,复制算法是针对标记—清除算法的缺点,在其基础上进行改进而得到的,它讲课用内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面,然后再把已使用过的内存空间一次清理掉。复制算法有如下优点:

  • 每次只对一块内存进行回收,运行高效。
  • 只需移动栈顶指针,按顺序分配内存即可,实现简单。
  • 内存回收时不用考虑内存碎片的出现。

它的缺点是:可一次性分配的最大内存缩小了一半。

复制算法的执行情况如下图所示:

回收前状态:

    回收后状态:

但一般不用按1:1划分内存空间,可以分成一个大的eden和两块小的survivor。

标记—整理算法

在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如标记—整理算法。该算法标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。标记—整理算法的回收情况如下所示:

回收前状态:

回收后状态:

分代收集

当前商业虚拟机的垃圾收集都采用分代收集来管理内存,它根据对象的存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。

每个对象都有一个年龄(Age)计数器,如果对象在Eden出声并讲过一次Minor GC还存活,将被移动到Survivor区并将Age设置为1,之后每在Survivor区中熬过一次Minor GC,Age就加1,当增加到一定程度(默认为15),就可以放到老年代中。

垃圾收集器

垃圾收集器是内存回收算法的具体实现,Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别。Sun  HotSpot虚拟机1.6版包含了如下收集器:Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old。这些收集器以不同的组合形式配合工作来完成不同分代区的垃圾收集工作。

垃圾回收分析

    在用代码分析之前,我们对内存的分配策略明确以下三点:
  • 对象优先在Eden分配。当Eden没有足够空间分配时,将发起一次Minor GC
  • 大对象(需要大量连续空间的java对象,如长的字符串和数组)直接进入老年代。由于新生代使用复制算法回收内存,这样可以避免在Eden和两个Survivor区之间发生大量的内存复制。
  • 长期存活的对象将进入老年代。
    对垃圾回收策略说明以下两点:
  • 新生代GC(Minor GC):发生在新生代的垃圾收集动作,因为Java对象大多都具有朝生夕灭的特性,因此Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC/Full GC):发生在老年代的GC,出现了Major GC,经常会伴随至少一次Minor GC。由于老年代中的对象生命周期比较长,因此Major GC并不频繁,一般都是等待老年代满了后才进行Full GC,而且其速度一般会比Minor GC慢10倍以上。另外,如果分配了Direct Memory,在老年代中进行Full GC时,会顺便清理掉Direct Memory中的废弃对象。

--------------------- 本文来自 rabbit_in_android 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/rabbit_in_android/article/details/50386954?utm_source=copy

Java GC策略的更多相关文章

  1. 大战Java虚拟机【2】—— GC策略

    前言 前面我们已经知道了Java虚拟机所做的事情就是回收那些不用的垃圾,那些不用的对象.那么问题来了,我们如何知道一个对象我们不需要使用了呢?程序在使用的过程中会不断的创建对象,这些所创建的对象指不定 ...

  2. Java垃圾回收机制(GC策略)

    Java垃圾回收机制(GC策略) 核心:1,哪些是垃圾?[怎么确定这个是垃圾]:2,如何回收垃圾?[怎么更好收垃圾]. Java语言相对于C++等语言有一个自动垃圾回收机制,只用管使用[实例化对象], ...

  3. 深入学习重点分析java基础---第一章:深入理解jvm(java虚拟机) 第一节 java内存模型及gc策略

    身为一个java程序员如果只会使用而不知原理称其为初级java程序员,知晓原理而升中级.融会贯通则为高级 作为有一个有技术追求的人,应当利用业余时间及零碎时间了解原理 近期在看深入理解java虚拟机 ...

  4. jvm系列(三):java GC算法 垃圾收集器

    GC算法 垃圾收集器 概述 垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了. jvm 中,程序计 ...

  5. 详解Java GC的工作原理+Minor GC、FullGC

    详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...

  6. Java GC回收机制

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

  7. java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

    java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较 ...

  8. 【转】JVM 分代GC策略分析

    我们以Sun HotSpot VM来进行分析,首先应该知道,如果我们没有指定任何GC策略的时候,JVM默认使用的GC策略.Java虚拟机是按照分代的方式来回收垃圾空间,我们应该知道,垃圾回收主要是针对 ...

  9. jvm系列:Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

随机推荐

  1. BZOJ 1296 粉刷匠(分组背包套DP)

    刚开始往网络流的方向想.建不出图... 因为每次只能对一行进行染色.每一行都是独立的. 对于每一行,因为格子只能染一次,所以可以发现这是一个多阶段决策问题,这个决策就是当前格子染0还是染1. 令dp[ ...

  2. hdu 1150 Machine Schedule (二分匹配)

    Machine Schedule Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  3. [BZOJ5339] [TJOI2018]教科书般的亵渎

    题目链接 BZOJ题面. 洛谷题面. Solution 随便推一推,可以发现瓶颈在求\(\sum_{i=1}^n i^k\),关于这个可以看看拉格朗日插值法. 复杂度\(O(Tm^2)\). #inc ...

  4. 项目管理---git----快速使用git笔记(四)------远程项目代码的首次获取

    使用git最常见的场景是 你需要参与到一个项目中,而这个项目的代码,同事已经上传到github或者https://coding.net了. 这时候他会给你一个项目代码的远程仓库链接. 例如: http ...

  5. Linux之socket套接字编程20160704

    介绍套接字之前,我们先看一下传输层的协议TCP与UDP: TCP协议与UDP协议的区别 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UD ...

  6. Linux之根文件系统的构建20160611

    说一下LINUX根文件系统的构建: 制作文件系统 1. 交叉编译busybox 安装:make install CONFIG_PREFIX=/work/nfs_root/fs_mini_mdev_ne ...

  7. 关于xml文件头部xmlsn

    样本: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://ww ...

  8. UIColor延伸:判断两个颜色是否相等

    不管UIColor使用CIColor,CGColor还是其他方式初始化的,其CGColor属性都是可用的.CoreGraphics中提供一个函数,用于判断两个CGColor是否相等,因此我们可以通过这 ...

  9. shell中的$* $@

    shell中$*与$@的区别 关于$* 和 $@的 一点 认识 同是菜鸟一起学习 $* 所有的位置参数,被作为一个单词. 注意:"$*"必须被""引用. $@ ...

  10. HDU1254 bfs

    推箱子 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...