shineon
垃圾回收机制
1、jvm内存(运行时数据区),包括:程序计数器、java栈和本地方法栈,堆、方法区。
java栈和本地方法栈会在方法退出时出栈,程序计数器保存是当前线程执行的字节码行号指示器,不会随着程序执行而变化,它们的内存分配和回收都是确定的。垃圾回收关注的是堆和方法区。
 
2、常用的垃圾回收算法:
(1)引用计数法
每个对象都含有一个引用计数器,当有引用连接对象时,引用数+1,当应用离开作用域或被指置为null时,引用数-1。垃圾回收器在含有全部对象的列表上遍历,当发现基本对象引用计数器为0时,立即释放其所占用的空间。缺陷是,如果对象间存在循环引用,则会出现对象应该被回收,但引用计数却不为0的情况。OC采用引用计数法,通过弱引用也解决了循环引用的问题。
(2)根搜索算法
思想:对任何“活”的对象一定能最终追溯到其存活在堆栈或静态存储区(方法区)之中的引用,形成的引用链可能会穿过多个对象层次。
具体方法:
从堆栈和静态存储区(方法区)中、遍历所有引用,对于发现的每个引用,必须追踪他所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区(方法区)的引用”所形成的网络全部被访问到为止。所有过的对象就是“活”的对象。
 
 
从堆栈和静态存储区(方法区)中,遍历所有引用指向的对象,被称为GC Roots。
通过一系列 GC Roots 对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则该对象不可达,该对象不可使用,垃圾回收器将回收其所占内存。
java采用根搜索算法,java中可作为GC Roots对象有:
a、java栈(栈帧中的本地变量表)中引用的对象。
b、方法区中类静态成员变量引用的对象。
c、方法区中常量引用的对象。
d、本地方法栈中JNI本地方法引用的对象
注意 类的成员变量(非静态)存储在堆中的对象里面,由垃圾回收器负责回收,不是GC Roots。所以java不会有循环引用的问题。
 
3、java中常用的垃圾回收算法
(1)标记-清除算法
标记阶段,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。清除阶段,清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。适用于少量垃圾甚至没有产生垃圾需要清理,即存活对象较多的情况。
缺陷:
a、剩下堆空间是不连续的,即产生内存碎片。再来一个大对象(如:该对象的大小大于空闲表中每一块大小但小于两块的和),会提前触发垃圾回收。
b、效率比较低,扫描整个空间两次:第一次标记存活对象,第二次清除没有被标记的对象。
 
(2)标记-整理算法
标记-整理算法在标记-清除算法基础上做了优化,标记阶段是相同,标记出所有活的对象,然后不是简单清理未标记对象,而是将活的对象压缩到内存的一端(整理)。之后,清理边界外所有空间。
 
(3)复制算法
将原有空间分为两块,每次使用其中一块,在垃圾回收时,将正在使用的内存中存活对象复制到另一块未使用的内存中(复制到新堆时,是一个挨着一个,解决了剩余空间连续的问题)。之后清理正在使用的内存中所有对象,交换两块内存角色。反复进行,完成垃圾回收。适用于存活对象较少的情况。
缺陷:
a、需要维护比实际多一倍的空间
b、需要复制对象
 
上面三个算法都基于根搜索算法,在GC过程开始时,除了gc的线程,它们都要暂停应用程序(stop the world)。
 
(4)分代收集算法
当前商业虚拟机的GC采用的是“分代收集算法”,根据对象的存活周期分为老年代(Tenured Generation)和新生代(Young Generation),永久代(Permanet Generation)(jdk1.8以后被元空间 (Metaspace)完全替代)
a、在新生代中,每次GC时都发现有大批对象死去,只有少量存活,使用复制算法。
 
SUN/Oracle 的HotSpot JVM 又把新生代进一步划分为3个区域:一个相对大点的区域,称为”伊甸园区(Eden)”;两个相对小点的区域称为”From 幸存区(survivor)”和”To 幸存区(survivor)”。
 
按照规定,新对象会首先分配在 Eden 中(如果新对象过大,会直接分配在老年代中)。在GC中,Eden 中的对象会被移动到survivor中,直至对象满足一定的年纪(定义为熬过GC的次数),会被移动到老年代。
 
具体方法为:
基于大多数新生对象都会在GC中被收回的假设。新生代的GC 使用复制算法。
在GC前To 幸存区(survivor)保持清空,对象保存在 Eden 和 From 幸存区(survivor)中(第一次gc的时候from也是空的)。
GC运行时,Eden中的幸存对象被复制到 To 幸存区(survivor)。针对 From 幸存区(survivor)中的幸存对象,会考虑对象年龄,如果年龄没达到阀值(tenuring threshold),对象会被复制到To 幸存区(survivor)。如果达到阀值对象被复制到老年代。复制阶段完成后,Eden 和From 幸存区中只保存死对象,可以视为清空。如果在复制过程中To 幸存区被填满了,剩余的对象会被复制到老年代中。最后 From 幸存区和 To幸存区会调换下名字,在下次GC时,To 幸存区会成为From 幸存区。
 
b、在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,使用“标记-清理”/“标记-整理”算法。
c、永久代(Permanet Generation)/ 元空间(Metaspace)
永久代用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。是JVM规范中方法区的具体实现。
是Hotspot虚拟机特有的概念,方法区/永久代是非堆内存。
 
从JDK1.7开始了永久代的转移工作,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
 
直到JDK1.8,永久代被元空间完全替代。他们最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小。
如:在jdk1.7中,while循环加载大量类,就会抛出java.lang.OutOfMemoryError: PermGen space 永久代内存溢出。 jdk1.8中已经没有PermGen space。
 
为什么叫元空间,是因为这里面存储的是类的元数据信息
元数据(Meta Date),关于数据的数据或者叫做用来描述数据的数据或者叫做信息的信息。
       这些定义都很是抽象,我们可以把元数据简单的理解成,最小的数据单位。元数据可以为数据说明其元素或属性(名称、大小、数据类型、等),或其结构(长度、字段、数据列),或其相关数据(位于何处、如何联系、拥有者)
 
4、Minor GC、Major GC和Full GC
Minor GC 清理新生代。
Major GC 清理老年代。(没有明确定义)
Full GC 清理整个堆空间—包括年轻代和老年代。
 
Minor GC触发条件:当Eden区满时或者新创建的对象大小 > Eden所剩空间,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足 (jdk1.8 后 永久代 被 元空间取代)
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
 
参考
《java编程思想第四版》
 

分类:

技术点:

相关文章:

  • 2021-10-16
  • 2021-07-15
  • 2022-12-23
  • 2022-01-03
  • 2021-10-16
  • 2021-10-16
  • 2021-10-16
  • 2021-11-28
猜你喜欢
  • 2021-12-15
  • 2021-10-26
  • 2021-10-16
  • 2021-05-28
  • 2022-12-23
  • 2021-10-16
  • 2020-02-13
相关资源
相似解决方案