A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 王震阳老师   /  2014-8-8 22:19  /  15273 人查看  /  143 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

请查收。

JVM的GC机制.zip

98.2 KB, 阅读权限: 200, 下载次数: 1

评分

参与人数 1技术分 +3 收起 理由
王震阳老师 + 3 赞一个!

查看全部评分

回复 使用道具 举报
我是来领题目的
回复 使用道具 举报
不懂啊   看来拿不到技术分了
回复 使用道具 举报
烦请查收,relive原创手打!
java GC.rar (107.07 KB, 下载次数: 237)

评分

参与人数 1技术分 +3 收起 理由
王震阳老师 + 3 满分!

查看全部评分

回复 使用道具 举报
来看一下子
回复 使用道具 举报
喜爱 发表于 2014-8-10 18:13
有学习到一些新东西,一点一点积累!

挺好:
  1. GC(Garbage Collection) 垃圾收集
  2.         在堆中,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象有哪些还“存活”着,哪些已经“死去”
  3. 1、引用计数算法:
  4.                 给对象中添加一个引用计算器,每当有一个地方引用它时,计算器值就加1;当引用失效时,计数器就减1;
  5.         任何时刻计数器都为0的对象就是不可能再被使用的。
  6.         这种算法判断效率很高,但是在Java却没有使用种进行管理内容,其中最主要的原因是它很难解决对象之间的
  7.         相互循环引用的问题。
  8.         简单的例子:
  9.        
  10. public class ReferenceCountingGC {
  11.         public Object instance = null;
  12.         private static final int _1MB = 1024 * 1024;
  13.         private byte[] bigSize = new byte[2 * _1MB];
  14.         punlic  static void testGC(){
  15.                 ReferenceCountingGC objA = new ReferenceCountingGC();
  16.                 ReferenceCountingGC objB = new ReferenceCountingGC();
  17.                 objA.instance = objB;
  18.                 objB.instance = objA;
  19.                
  20.                 objA = null;
  21.                 objB = null;
  22.                
  23.                 System.gc();
  24.         }
  25.        
  26.         public static void main(String[] orgs){
  27.                 ReferenceCountingGC.testGC();
  28.         }
  29. }
  30. objA.instance = objB,objB.instance = objA,除此之外,这两个对象再无任何的引用,实际上这两个对象已经不可能
  31. 再被访问,但是它们因为互相引用着对方,导致它们的引用计书都不为0,于是引用计数算法无法通知GC收集器回收它们。

  32. 2、标记 - 清除算法
  33.         分为两个阶段:
  34.                 首先标记所有需要回收的对象,
  35.                 在标记完成后统一回收掉所有被标记的对象。
  36.         该算法有两个缺点:
  37.                 一个是效率问题,标记和清除过程的效率都不高。
  38.                 另外一个是空间问题,标记清除之后产生大量不连续的内存碎片,空间碎片大多可能导致,当程序在以后的远行
  39.                 过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

  40. 3、复制算法:
  41.             将可用内存按内量划分为大小相等的两块,每一次只使用其中一块。当这一块内存用完了,就将还存活着的对象复制
  42.         到另外一块上面,然后再把已使用过的内存空间一次清理掉。
  43.             现在的商业虚拟机都用这种算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivo空间。每次使用
  44.         Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,
  45.         最后清理掉Eden获取刚才用过的Survivor的空间。
  46.         缺点:默认情况下Eden:Survivor=8:1, 所以总会有100-(80+10)%的新生代内存会被浪费掉。
  47.        
  48. 4、标记 - 整理算法(适用于存放生命周期较长对象的)
  49.         标记过程与“标记 - 清楚”算法一样,但是继步骤不是直接对可回收对象进行清理,而是让
  50.         所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。


  51.        
复制代码
回复 使用道具 举报

挺好:
  1. JVM的GC机制
  2.     内存作为系统中重要的资源,对系统的稳定运行和高效运行起到了关键作用。虽然Java和C之类的语言不同,不需要开发人员来分配内存和回收内存,而是由JVM来管理对象内存的分配以及回收(又称垃圾回收,GC),对开发人员来说确实大大降低了编写程序的难度,但是它也带来了一个副作用。就是当系统运行过程中出现JVM抛出内存异常(例如OutOfMemoryError)的时候,很难知道原因是什么。另外要编写高性能的程序,我们是必须要借助内存来提升性能的,所以如何合理的使用内存以及让JVM合理的进行内存回收也是我们必须掌握的知识。在本文中,我们就从JVM的内存结构,JVM的GC机制以及GC的常用算法这三个方面来详细的了解一下JVM的垃圾收集机制。

  3. 1.JVM的内存结构

  4. JVM内存结构模型

  5. 1.1 JVM运行
  6. JVM的运行过程:CLASS LOADER  RUNTIME DATA AREA  EXECUTION ENGINE  NATIVE INTERFAXE  NATIVE LIBRARIES.

  7. CLASS LOADER:类加载器。负责加载类文件到内存。比如编写好了一个HelloWorld.java程序,通过javac编译成class文件,之后Class Loader负责将这个Class文件加载到内存中。
  8. RUNNTIME DATA AREA:运行数据区。这是整个JVM的重点,后面具体介绍。
  9. EXECUTION ENGINE:执行引擎。也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。
  10. NATIVE INTERFAXE:本地接口。负责融合不同的编程语言为Java所用。内存中专门有一块区域标记为native的代码,通过在Native Method Stack中等级native方法,在Executive Engine执行是加载native libraries。
  11. NATIVE LIBRARIES:本地方法库。

  12. 1.2 RUNTIME DATA AREA (运行数据区)
  13.     运行数据区,也就是内存区域,这是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行。
  14. 区域        存储那类数据
  15. Method Area        线程共享的内存区域
  16. 非堆主要区域
  17. 存储类信息、常量、静态变量、即时编译器
  18. Java Stack        线程私有,存储局部变量表,操作栈,动态链接,方法出口
  19. Native Method Stack        为虚拟机使用到的Native方法服务
  20. Heap        线程共享
  21. 所有的对象实例以及数组都要在堆上分配
  22. 回收器主要管理的对象
  23. Program Counter Register        线程私有、指向下一条要执行的指令
  24. 下面对各个区域进行更加详细的描述。
  25. 1.2.1  Program Counter Register(程序计数器)
  26. 程序计数器是一块较小的内存空间,作用是:当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。它是线程私有的内存。

  27. 1.2.2  Method Area(方法区)
  28.     方法区,用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。JVM规范把方法区描述为堆的一个逻辑部分,但是它又与堆不同,所以为了与堆区分开来,它有一个别名叫“非堆”。
  29. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

  30. 1.2.3  Java Stack(Java虚拟机栈)
  31. 虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时常见一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。生命周期与线程相同,是线程私有的。
  32. 局部变量表存储了各种基本的数据类型和对象引用类型。

  33. 1.2.4  Native Method Stack(本地方法栈)
  34. 与虚拟机栈作用相似,其区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

  35. 1.2.5  Heap(堆)
  36. 堆是Java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。该内存区存储对象实例及数组(所有new的对象)。
  37. 堆是垃圾收集器管理的主要区域,因此很多时候被称为“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代、老年代和持久代;再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread LocalAllocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

  38. 2. JVM的GC机制
  39. 垃圾收集(Garbage Collected)提供了内存管理的机制,使得应用程序不需要再关注内存如何释放,内存用完后,垃圾收集会进行收集,这样就减轻了因为人为的管理内存而造成的错误,比如在C++中,出现内存泄露是很常见的。
  40. 常见的GC策略
  41. 所有的垃圾收集算法都面临同一个问题,那就是找出应用程序中不可到达的内存块,将其释放。这里的不可到达主要是指:应用程序已经没有内存块的引用了。在Java中,某个对象对应用程序是可达的是指:这个对象被根(根主要是指类的静态变量,或者活跃在所有线程栈的对象的引用)引用或者对象被另一个可到达的对象引用。
  42. 垃圾收集首先需要确定从根开始哪些是可达的和哪些是不可达的,从根可达的对象都是活动对象,他们不能作为垃圾被回收,这也包括从根间接可达的对象。而根通过任意路径不可达的对象符合垃圾收集的条件,应该被回收。
  43. 3. GC的常用算法
  44. 下面是几种常见的GC算法。

  45. 3.1 引用计数收集器(Reference Counting)
  46. 引用计数是垃圾回收的早期一种策略,是最简单直接的一种方式。
  47. 其思想是:
  48. 堆中每一个对象都有一个引用计数。一个对象被创建了,并且指向该对象的引用被分配给一个变量,这个对象的引用计数被置为1。当任何其他变量被赋值为对这个对象的引用时,计数加1。当一个对象的引用超过了生存期或者被设置一个新的值时,对象的引用计数减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集的时候,它引用的任何对象计数值减1。在这种方法中,一个对象被垃圾收集后可能导致后续其他对象的垃圾收集行动。
  49. 优点:
  50. 简单,直接,不需要暂停整个应用,引用计数收集器可以交织在程序的运行之中很快地执行。这种特性对于程序不能被长时间打断的实时环境很有利。
  51. 缺点:
  52. 1)引用计数无法检测出循环,即两个或者更多的对象互相引用。如父对象有一个对子对象的引用,子对象又反过来引用父对象,这些对象永远都不可能计数为0,就算他们已经无法被执行程序的根对象可触及。
  53. 2)需要编译器的配合,编译器要生成特殊的指令来进行引用计数的操作。比如每次将对象赋值给新的引用,或者对象的引用超出了作用域等。

  54. 3.2 标记-清除收集器(Mark-Sweep)或追踪回收器(Tracing Collector)
  55. 这种算法是为了解决引用计数法的问题而提出的,它使用了根集的概念。
  56. 其思想是:
  57. 从根集开始扫描,识别出哪些对象可达,哪些对象不可达,并用某种方式标记可达对象,例如在对象本身设置标记,或者是用一个独立的位图来设置标记。当扫描结束时,未被标记的对象就是无法触及的,从而可以被回收。垃圾回收过程分两个阶段:标记阶段:垃圾收集器遍历引用树,标记每一个遇到的对象;在清除阶段,未被标记的对象被释放了,使用的内存被返回到正在执行的程序中。
  58. 优点:
  59. 1)解决循环引用的问题。
  60. 2)不需要编译器的配合,从而就不执行额外的指令。
  61. 缺点:
  62. 1)每个活跃的对象都要进行扫描,收集暂停时间比较长。
  63. 2)这种收集器一般使用单线程工作并停止其他操作。

  64. 3.3 基于标记-清除的压缩收集器(Compacting Collector)
  65. 这种算法是为了解决堆碎片的问题,基于Tracing的垃圾回收吸收了Compacting算法的思想。
  66. 其思想是:
  67. 标记阶段同标记-清除收集器。在清除阶段,将所有的对象移到堆的一端,堆的另一端就变成了一个相邻的空闲内存区,收集器会对它移动的所有对象的所有引用进行更新,使得这些应用在新的位置能识别原来的对象。
  68. 更新被移动的对象的引用有时候通过一个间接对象引用层可以变得更简单。不直接引用堆中的对象,对象的引用实际上指向一个对象句柄表。对象句柄才指向堆中对象的实际位置。当对象被移动了,只有这个句柄需要被更新为新位置。所有的程序中对这个对象的引用仍然指向这个具有新值的句柄,而句柄本身没有移动。
  69. 优点:极大的减少了内存碎片。
  70. 缺点:工作期间暂停其他操作,每一次对象访问都带来了性能损失。

  71. 3.4 拷贝收集器(Coping Collector)
  72. 典型的一种拷贝收集器算法是stop-and-copy算法,该算法的提出是为了克服句柄的开销和解决堆碎片的问题。
  73. 其思想是:堆被分为两个区域:对象面和空闲面,任何时候都只能使用其中的一个区域。程序从对象面为对象分配空间,当对象满了,就从根集中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。
  74. 优点:克服了句柄的开销,并且解决了堆碎片的问题。
  75. 缺点:对于指定大小的堆来说,任何时候都只能使用其中的一半,并且工作期间暂停其他操作,程序等待时间较长。

  76. 3.5 按代收集器(Generational Collector)
  77. stop-and-copy算法的一个缺陷是收集器必须复制所有的活动对象,这增加了程序等待时间,这是coping算法低效的原因。而程序设计中有这样的规律:多数对象存在的时间比较短,少数的存在时间比较长。因此我们提出了按代收集器。
  78. 3.5.1 按代收集器的思想
  79. 基于不同对象的生命周期是不一样的这样一个事实,按代收集的收集器通过把对象按照寿命来分组解决这个效率低下的问题,更多地收集那些短暂出现的年幼对象,而非寿命较长的对象。在这种方法里,堆被划分成两个或者更多的子堆,每一个子堆为一“代”对象服务。最年幼的那一代进行最频繁的垃圾收集。因为大多数对象都是短促出现的,只有很小部分的年幼对象可以在它们经历第一次收集后还存活。如果一个最年幼的对象经历了好几次垃圾收集后仍然存活,那么这个对象就成长为寿命更高的一代:它被转移到另外一个子堆中去。年龄更高的每一代的收集都没有年径的那一代来得频繁。每当对象在它所属的年龄层(代)中变得成熟(逃过了多次垃圾收集)之后,它们就被转移到更高的年龄层中去。 

  80. 3.5.2 如何分代
  81. 在JVM内存结构的时候,我们也介绍过堆分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation) 。
  82. 年轻代:
  83. 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制 过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时 存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空 的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
  84. 年老代:
  85. 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
  86. 持久代:
  87. 用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
  88. 3.5.3 什么情况下触发垃圾回收
  89. 由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC 和Full GC 。
  90. Scavenge GC
  91.       一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对 年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因 而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
  92. Full GC
  93.       对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
  94.       年老代(Tenured)被写满
  95.       持久代(Perm)被写满
  96.       System.gc()被显示调用
  97.      上一次GC之后Heap的各域分配策略动态变化

  98. 总结:
  99.       引用计数收集器 基本上已经不被使用了。 标记-清除收集器 是目前寻找垃圾对象的主流策略,但是它对回收后产生的内存碎片却无能为力,因此我们必须采用一些合并空闲内存的算法,这就产生了基于标记-清除的 压缩收集器 和 拷贝收集器 。而基于性能原因,拷贝收集器是比较受欢迎的。但是它有一个比较大的弱点:对于寿命较长的对象会不停的拷贝,这就付出了不必要的代价。因此, 按代收集器 的出现 改进了这一性能。而我们的JDK中的JVM就采用了按代收集策略。   

  100. 有关垃圾回收的几个补充问题:
  101. 1.垃圾回收的起点在哪里?
  102. 除引用计数收集器之外,垃圾回收的起点都是一些根对象(Java栈,静态变量,寄存器…)。而最简单的Java栈就是Java程序执行的main函数。
  103. 2.如何解决同时存在的对象创建和对象回收问题?
  104.     垃圾回收线程是回收内存的,而程序运行线程则是消耗(或分配)内存的,一个回收内存,一个分配内存 ,从这点看,两者是矛盾的。因此,在现有的垃圾回收方式中,要进行垃圾回收前,一般都需要暂停整个应用(即:暂停内存的分配),然后进行垃圾回收,回收完成后再继续应用。这种实现方式是最直接,而且最有效的解决二者矛盾的方式。
  105.       但是这种方式有一个很明显的弊端,就是当堆空间持续增大时,垃圾回收的时间也将会相应的持续增大,对应应用暂停的时间也会相应的增大 。 一些对相应时间要求很高的应用,比如最大暂停时间要求是几百毫秒,那么当堆空间大于几个G时,就很有可能超过这个限制,在这种情况下,垃圾回收将会成为系统运行的一个瓶颈。为解决这种矛盾,有了并发垃圾回收算法 ,使用这种算法,垃圾回收线程与程序运行线程同时运行。在这种方式下,解决了暂停的问题,但是因为需要在新生成对象的同时又要回收对象,算法复杂性会大大增加,系统的处理能力也会相应降低,同时,“碎片”问题将会比较难解决。


  106. 4 新一代的垃圾回收算法
  107. 4.1 垃圾回收的瓶颈
  108. 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限。但是他无法解决的一个问题,就是Full GC所带来的应用暂停。在一些对实时性要求很高的应用场景下,GC暂停所带来的请求堆积和请求失败是无法接受的。这类应用可能要求请求的返回时间在几百甚至几十毫秒以内,如果分代垃圾回收方式要达到这个指标,只能把最大堆的设置限制在一个相对较小范围内,但是这样有限制了应用本身的处理能力,同样也是不可接收的。
  109. 分代垃圾回收方式确实也考虑了实时性要求而提供了并发回收器,支持最大暂停时间的设置,但是受限于分代垃圾回收的内存划分模型,其效果也不是很理想。
  110. 为了达到实时性的要求(其实Java语言最初的设计也是在嵌入式系统上的),一种新垃圾回收方式呼之欲出,它既支持短的暂停时间,又支持大的内存空间分配。可以很好的解决传统分代方式带来的问题。
  111. 4.2 增量收集的演进
  112. 增量收集的方式在理论上可以解决传统分代方式带来的问题。增量收集把对堆空间划分成一系列内存块,使用时,先使用其中一部分(不会全部用完),垃圾收集时 把之前用掉的部分中的存活对象再放到后面没有用的空间中,这样可以实现一直边使用边收集的效果,避免了传统分代方式整个使用完了再暂停的回收的情况。
  113. 当然,传统分代收集方式也提供了并发收集,但是他有一个很致命的地方,就是把整个堆做为一个内存块,这样一方面会造成碎片(无法压缩),另一方面他的每次 收集都是对整个堆的收集,无法进行选择,在暂停时间的控制上还是很弱。而增量方式,通过内存空间的分块,恰恰可以解决上面问题。
  114. 4.3 Garbage First(G1)
  115. 1)目标
  116. 支持很大的堆
  117. 高吞吐量
  118.       --支持多CPU和垃圾回收线程
  119.       --在主线程暂停的情况下,使用并行收集
  120.       --在主线程运行的情况下,使用并发收集
  121. 实时目标: 可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收

  122. 2)算法详解

  123. G1可谓博采众家之长,力求到达一种完美。他吸取了增量收集优点,把整个堆划分为一个一个等大小的区域(region)。内存的回收和划分都以 region为单位;同时,他也吸取了CMS的特点,把这个垃圾回收过程分为几个阶段,分散一个垃圾回收过程;而且,G1也认同分代垃圾回收的思想,认为 不同对象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。为了达到对回收时间的可预计性,G1在扫描了region以后,对其中的 活跃对象的大小进行排序,首先会收集那些活跃对象小的region,以便快速回收空间(要复制的活跃对象少了),因为活跃对象小,里面可以认为多数都是垃 圾,所以这种方式被称为Garbage First(G1)的垃圾回收算法,即:垃圾优先的回收。
  124. 3)回收步骤:
  125.     初始标记(Initial Marking)
  126.      G1对于每个region都保存了两个标识用的bitmap,一个为previous marking bitmap,一个为next marking bitmap,bitmap中包含了一个bit的地址信息来指向对象的起始点。
  127. 开始Initial Marking之前,首先并发的清空next marking bitmap,然后停止所有应用线程,并扫描标识出每个region中root可直接访问到的对象,将region中top的值放入next top at mark start(TAMS)中,之后恢复所有应用线程。
  128. 触发这个步骤执行的条件为:
  129. G1定义了一个JVM Heap大小的百分比的阀值,称为h,另外还有一个H,H的值为(1-h)*Heap Size,目前这个h的值是固定的,后续G1也许会将其改为动态的,根据jvm的运行情况来动态的调整,在分代方式下,G1还定义了一个u以及soft limit,soft limit的值为H-u*Heap Size,当Heap中使用的内存超过了soft limit值时,就会在一次clean up执行完毕后在应用允许的GC暂停时间范围内尽快的执行此步骤;
  130. 在pure方式下,G1将marking与clean up组成一个环,以便clean up能充分的使用marking的信息,当clean up开始回收时,首先回收能够带来最多内存空间的regions,当经过多次的clean up,回收到没多少空间的regions时,G1重新初始化一个新的marking与clean up构成的环。
  131. 并发标记(Concurrent Marking)
  132. 按照之前Initial Marking扫描到的对象进行遍历,以识别这些对象的下层对象的活跃状态,对于在此期间应用线程并发修改的对象的以来关系则记录到remembered set logs中,新创建的对象则放入比top值更高的地址区间中,这些新创建的对象默认状态即为活跃的,同时修改top值。
  133. 最终标记暂停(Final Marking Pause)
  134. 当应用线程的remembered set logs未满时,是不会放入filled RS buffers中的,在这样的情况下,这些remebered set logs中记录的card的修改就会被更新了,因此需要这一步,这一步要做的就是把应用线程中存在的remembered set logs的内容进行处理,并相应的修改remembered sets,这一步需要暂停应用,并行的运行。
  135. 存活对象计算及清除(Live Data Counting and Cleanup)
  136. 值得注意的是,在G1中,并不是说Final Marking Pause执行完了,就肯定执行Cleanup这步的,由于这步需要暂停应用,G1为了能够达到准实时的要求,需要根据用户指定的最大的GC造成的暂停时间来合理的规划什么时候执行Cleanup,另外还有几种情况也是会触发这个步骤的执行的:
  137. G1采用的是复制方法来进行收集,必须保证每次的”to space”的空间都是够的,因此G1采取的策略是当已经使用的内存空间达到了H时,就执行Cleanup这个步骤;
  138. 对于full-young和partially-young的分代模式的G1而言,则还有情况会触发Cleanup的执行,full-young模式下,G1根据应用可接受的暂停时间、回收young regions需要消耗的时间来估算出一个yound regions的数量值,当JVM中分配对象的young regions的数量达到此值时,Cleanup就会执行;partially-young模式下,则会尽量频繁的在应用可接受的暂停时间范围内执行Cleanup,并最大限度的去执行non-young regions的Cleanup。
  139.     G1为了能够尽量的做到准实时的响应,例如估算暂停时间的算法、对于经常被引用的对象的特殊处理等,G1为了能够让GC既能够充分的回收内存,又能够尽量少的导致应用的暂停,可谓费尽心思,从G1的论文中的性能评测来看效果也是不错的,不过如果G1能允许开发人员在编写代码时指定哪些对象是不用mark的就更完美了,这对于有巨大缓存的应用而言,会有很大的帮助。
复制代码
回复 使用道具 举报
relive 发表于 2014-8-11 01:21
烦请查收,relive原创手打!

很给力:
Java GC
                ---- relive
1.Jvm内存结构
java虚拟机在解析执行java程序的时候会把其管理的内存主要分配成5块数据区域。
1.程序计数器
i.占用较小内存空间,可看作是当前线程所执行的字节码的行号指示器(学过汇编语言的同学应该了解);
ii.工作原理类似通过改变计数器的值来选取下一条执行的指令的字节码;
iii.线程私有:即为了让各个线程之间的计数器互不影响,独立存储,所以每条线程都需要一个独立的程序计数器,不能共享,否则易造成数据不一致;
2.java虚拟机栈
i.俗称栈区,描述java方法执行的内存模型,即每个方法在执行时都会创建一个栈帧用于存放变量、局部变量表、操作数栈、动态链接、方法出口等信息,一个方法从调用至完成,对应着一个栈帧在虚拟机中的出入栈过程;
ii.线程私有,与线程的生命周期一致;
3.本地方法栈
i.和java虚拟机栈类似,二者的区别即java虚拟机栈存放的对象是java中的字节码,而本地方法栈存放的对象是本地方法,例如c/c++写的程序等;
ii.线程私有,生命周期与线程一致;
4.堆区
i.java虚拟机管理的内存中最大的一块,也是线程共享的内存区;
ii.其作用主要是存放对象实例,java虚拟机规范中声明“所有的对象实例以及数组都需要在对上分配内存。”;
iii.Java GC的主要区域,jvm基本采用分代手机算法,可以把堆区细分为新生代和老年代;
5.方法区
i.线程共享;
ii.用于存放已经被虚拟机加载的类信息、常量、运行时常量、静态变量、即时编译器编译后的代码等;
iii.又叫做non-heap;
 
2.Java GC机制
i.GC面临的问题
i.什么样的对象会被回收?
参看“什么样的对象会被回收”
2.何时回收?
有jvm决定,对程序、程序员透明,何时回收不可预知;
iii.怎么回收?
参看“c,d,e,f,g”
b.什么样的对象将会被回收
当一个对象在对内存中完成初始化之后,其所经历的状态有三种,其中,当对象的状态变为不可达之后,将会被java GC回收内存资源。
i.可达状态:存在直接引用,例如Object oo = new Object();
ii.可恢复状态:如果不再有任何引用,则进入可恢复状态,在GC回收内存前,会调用其finalize方法来清理资源,例如oo=null;
iii.不可达状态:不再有任何引用,且调用其finalize方法后对象没有变成回达状体,则该对象将会永久的失去引用,变成不可达状态,将会真正的被GC回收内存资源;
iii.强制垃圾回收
当一个对象失去引用后,会进入可恢复状态,此时如果调用其finalize方法没有使其重回可达状态,则该对象会永久失去引用等待java GC回收内存,系统什么时候回收内存,对程序、程序员是透明的,由jvm来选择何时进行回收;但是程序中可以调用以下两种方式来进行强制内存回收,但是这种“强制”对jvm来说其实是一种“建议”,即便是写了轻质回收的代码,jvm也不一定会立即回收内存资源。
i.System.gc();
ii.Runtime.getRuntime().gc();
iii.例证
1.Java_bin
2.
3.Main

iv.执行结果

5.结论
程序new了10个无引用的匿名对象,每new一个无引用的对象都会“强制”系统回收内存,但是从程序多次执行的表现观察,调用finalize方法的次数基本上每次都不相同而且不一定等于10次或0次,所以得出结论:java强制回收不是真的强制,而是对jvm的一种“建议”,“建议”其现在进行GC操作,但是jvm什么时候真正开始回收内存,对程序、程序员而言是透明的,不可预测。
d.finalize方法
i.解释:finalize方法源自Object中,protected修饰,即允许其被子类继承、重写;在java真正执行GC回收一个对象占用的内存之前会调用该方法来清理资源,如果在该方法中,对象没有变变回达状态,则finalize方法执行完成返回后对象消失,垃圾回收开始真正执行;
ii.建议:程序员不要主动调用对象的finalize方法,应该交由垃圾回收机制来调用;
iii.备注:finalize方法何时被调用、是否被调用具有不确定性;当jvm首先执行finalize的时候,有可能使对象重可恢复状态变成可达状态,从而不被GC掉;当JVM执行finalize方法的时候出现异常,垃圾回收机制不会报告一场,程序继续运行,但是finalize异常之后的代码不会继续执行(如下,修改Main.class的finalize方法,使其抛出异常,再重新执行Main.java文件的时候,并无任何异常抛出,但是再也不会输出System.out的字符);

 
e.java中的引用
i.强引用:对象有一个及其以上的引用,例如Object oo = new Object();
ii.软引用:java中通过SoftReference类来实现引用级别低于强引用,当系统内存足够时,起不会被系统回收;当系统内存不够使,其可能被回收;
iii.弱引用:通过WeakReference类实现,引用级别低于软引用,无论系统内存是否足够,系统进行垃圾回收的时候总是会回收其占用的内存;
iv.虚引用:通过PhantomReference类实现,引用级别最低,类似没有引用,主要用于跟踪对象被垃圾回收的状态;
v.用法示例:
String str = new String("我是xx");
WeakReference wr = new WeakReference(str);
//软引用和虚引用类似

f.常用对象判死算法
i.引用计数法
通过引用计数器来判断对象是否被引用,例如有一个引用则引用计数器+1。
a.缺点:对象的相互引用会导致无法回收内存;
b.代码:
Java_bin_a:

Java_bin_b:

c.图示:

ii.可达性分析
类似树状结构,从“GC Root”节点开始通过引用链搜索,如果一个对象无路可达,则表示该对象处于可恢复状态,等待被GC回收内存资源;
g.垃圾收集算法
i.标记-清除
先标记“不可达”状态的对象,然后统一回收内存资源;效率不高,同时会导致内存碎片化;
b.复制算法
为了提高“标记-清楚”算法的效率,将可用内存划分成大小相等的两块,每次只是用其中的一块,当这一块内存用完了(包含存活对象、等待回收的对象和碎片占用的空间),则一次性拷贝到另一块,此时将会释放等待回收对象的内存空间,同时解决内存碎片问题,存活对象连续占用一片内存,再一次性清空原先的内存块;
iii.标记-整理
相比较“复制算法”,不会浪费50%的内存,算法不直接回收不可达对象占用的内存,而是让存活对象都向一端移动,然后清理释放边界的内存空间;
iv.分代收集
根据对象的存活周期在堆中将对象分为新生代和老年代,根据不同代特点不同选择不同的算法:
新生代——绝大部分对象很快不可达需要回收内存,采用复制算法,每次只需要付出极少的复制代价;
老年代——绝大部分对象长期保持可达状态,适合采用“标记-清理”或“标记-整理”算法;
 
 
3.其他
a.随着操作系统的发展,32bit系统必然会逐渐被64bit系统取代。虽然java虚拟机在很早以前就已经推出了64bit的版本,但是直到目前,任旧存在问题——java程序跑在64bit系统上系能消耗比32bit系统的多,多消耗20%左右内存,存在10%左右的性能差距;
b.首先要注意,java GC只针对内存的回收,对类似数据库连接、io等物力资源的释放不会自动回收,均需要手动close掉;
c.程序员无法精确的控制何时回收内存,只能给jvm提供“建议”或在代码上做一定的优化;
 
回复 使用道具 举报
看看什么题
回复 使用道具 举报
就业指导-王震阳老师 发表于 2014-8-11 11:54
很给力:
Java GC
                ---- relive

oneNote写的,格式转码过程中出问题,我重新发出来吧~
回复 使用道具 举报
relive 发表于 2014-8-11 13:21
oneNote写的,格式转码过程中出问题,我重新发出来吧~

你自己直接公布出来吧,还能排版,你里面的图片我无法排版。
回复 使用道具 举报
总结的不好,请阳哥指正。

JVM内存结构、垃圾回收机制以及GC基本算法知识点总结.zip

3.49 KB, 阅读权限: 200, 下载次数: 1

评分

参与人数 1技术分 +2 收起 理由
王震阳老师 + 2 赞一个!

查看全部评分

回复 使用道具 举报
我来看题目的
回复 使用道具 举报
呀嘛咕 来自手机 中级黑马 2014-8-12 17:52:20
74#
我要领题,嘿嘿嘿
回复 使用道具 举报
火火火火火,顶起!!!!!!!!!!!!!!!!
回复 使用道具 举报
看题 做做
回复 使用道具 举报
只是来看题目的!!!!!!
回复 使用道具 举报
来学习!
回复 使用道具 举报
回帖领题。
回复 使用道具 举报
看一下会不会打额呢
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马