`
yinwufeng
  • 浏览: 286894 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

JVM GC 再深入一点

    博客分类:
  • JVM
 
阅读更多
这篇文章写的不错,大多来自于深入java虚拟机,确实比很多文章都深入一点,直接拷贝吧。引用自:http://blog.csdn.net/dc_726/article/details/7934101
 
垃圾回收包含的内容不少,但顺着下面的顺序捋清知识也并不难。首先要
搞清垃圾回收的范围(栈需要GC去回收吗?),然后就是回收的前提条件
如何判断一个对象已经可以被回收(这里只重点学习根搜索算法就行了),
之后便是建立在根搜索基础上的三种回收策略,最后便是JVM中对这三种
策略的具体实现。
 
1.范围:要回收哪些区域?
 
Java方法栈、本地方法栈以及PC计数器随方法或线程的结束而自然被回收,
所以这些区域不需要考虑回收问题。Java堆和方法区是GC回收的重点区域,
因为一个接口的多个实现类需要的内存不一样,一个方法的多个分支需要
的内存可能也不一样,而这两个区域又对立于栈可能随时都会有对象不再
被引用,因此这部分内存的分配和回收都是动态的。
 
 
2.前提:如何判断对象已死?
 
(1)引用计数法
 
引用计数法就是通过一个计数器记录该对象被引用的次数,方法简单高效,
但是解决不了循环引用的问题。比如对象A包含指向对象B的引用,对象B
也包含指向对象A的引用,但没有引用指向A和B,这时当前回收如果采用的
是引用计数法,那么对象A和B的被引用次数都为1,都不会被回收。
 
下面是循环引用的例子,在Hotspot JVM下可以被正常回收,可以证实JVM
采用的不是简单的引用计数法。通过-XX:+PrintGCDetails输出GC日志。
[java] view plaincopy
 
  1. package com.cdai.jvm.gc;  
  2.   
  3. public class ReferenceCount {  
  4.   
  5.     final static int MB = 1024 * 1024;  
  6.       
  7.     byte[] size = new byte[2 * MB];  
  8.       
  9.     Object ref;  
  10.       
  11.     public static void main(String[] args) {  
  12.         ReferenceCount objA = new ReferenceCount();  
  13.         ReferenceCount objB = new ReferenceCount();  
  14.         objA.ref = objB;  
  15.         objB.ref = objA;  
  16.           
  17.         objA = null;  
  18.         objB = null;  
  19.           
  20.         System.gc();  
  21.         System.gc();  
  22.     }  
  23.   
  24. }  

[Full GC (System) [Tenured: 2048K->366K(10944K), 0.0046272 secs] 4604K->366K(15872K), [Perm : 154K->154K(12288K)], 0.0046751 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
 
(2)根搜索
 
通过选取一些根对象作为起始点,开始向下搜索,如果一个对象到根对象
不可达时,则说明此对象已经没有被引用,是可以被回收的。可以作为根的
对象有:栈中变量引用的对象,类静态属性引用的对象,常量引用的对象等。
因为每个线程都有一个栈,所以我们需要选取多个根对象。
 
 
附:对象复活
 
在根搜索中得到的不可达对象并不是立即就被标记成可回收的,而是先进行一次
标记放入F-Queue等待执行对象的finalize()方法,执行后GC将进行二次标记,复活
的对象之后将不会被回收。因此,使对象复活的唯一办法就是重写finalize()方法,
并使对象重新被引用。
[java] view plaincopy
 
  1. package com.cdai.jvm.gc;  
  2.   
  3. public class DeadToRebirth {  
  4.   
  5.     private static DeadToRebirth hook;   
  6.       
  7.     @Override  
  8.     public void finalize() throws Throwable {  
  9.         super.finalize();  
  10.         DeadToRebirth.hook = this;  
  11.     }  
  12.       
  13.     public static void main(String[] args) throws Exception {  
  14.         DeadToRebirth.hook = new DeadToRebirth();  
  15.         DeadToRebirth.hook = null;  
  16.         System.gc();  
  17.         Thread.sleep(500);  
  18.         if (DeadToRebirth.hook != null)  
  19.             System.out.println("Rebirth!");  
  20.         else  
  21.             System.out.println("Dead!");  
  22.           
  23.         DeadToRebirth.hook = null;  
  24.         System.gc();  
  25.         Thread.sleep(500);  
  26.         if (DeadToRebirth.hook != null)  
  27.             System.out.println("Rebirth!");  
  28.         else  
  29.             System.out.println("Dead!");  
  30.     }  
  31.       
  32. }  
 
要注意的两点是:
第一,finalize()方法只会被执行一次,所以对象只有一次复活的机会。
第二,执行GC后,要停顿半秒等待优先级很低的finalize()执行完毕。
 
 
3.策略:垃圾回收的算法
 
(1)标记-清除
 
没错,这里的标记指的就是之前我们介绍过的两次标记过程。标记完成后就可以
对标记为垃圾的对象进行回收了。怎么样,简单吧。但是这种策略的缺点很明显,
回收后内存碎片很多,如果之后程序运行时申请大内存,可能会又导致一次GC。
虽然缺点明显,这种策略却是后两种策略的基础。正因为它的缺点,所以促成了
后两种策略的产生。
 
 
(2)标记-复制
 
将内存分为两块,标记完成开始回收时,将一块内存中保留的对象全部复制到另
一块空闲内存中。实现起来也很简单,当大部分对象都被回收时这种策略也很高效。
但这种策略也有缺点,可用内存变为一半了!
 
怎样解决呢?聪明的程序员们总是办法多过问题的。可以将堆不按1:1的比例分离,
而是按8:1:1分成一块Eden和两小块Survivor区,每次将Eden和Survivor中存活的对象
复制到另一块空闲的Survivor中。这三块区域并不是堆的全部,而是构成了新生代
 
从下图可以看到这三块区域如何配合完成GC的,具体的对象空间分配以及晋升请
参加后面第6条补充。
 
 
为什么不是全部呢?如果回收时,空闲的那一小块Survivor不够用了怎么办?这就是
老年代的用处。当不够用时,这些对象将直接通过分配担保机制进入老年代。那么
老年代也使用标记-复制策略吧?当然不行!老年代中的对象可不像新生代中的,
每次回收都会清除掉大部分。如果贸然采用复制的策略,老年代的回收效率可想而知。
 
(3)标记-整理
 
根据老年代的特点,采用回收掉垃圾对象后对内存进行整理的策略再合适不过,将
所有存活下来的对象都向一端移动。
 
 
 
4.实现:虚拟机中的收集器
 
(1)新生代上的GC实现
 
Serial:单线程的收集器,只使用一个线程进行收集,并且收集时会暂停其他所有
工作线程(Stop the world)。它是Client模式下的默认新生代收集器。
 
ParNew:Serial收集器的多线程版本。在单CPU甚至两个CPU的环境下,由于线程
交互的开销,无法保证性能超越Serial收集器。
 
Parallel Scavenge:也是多线程收集器,与ParNew的区别是,它是吞吐量优先
收集器。吞吐量=运行用户代码时间/(运行用户代码+垃圾收集时间)。另一点区别
是配置-XX:+UseAdaptiveSizePolicy后,虚拟机会自动调整Eden/Survivor等参数来
提供用户所需的吞吐量。我们需要配置的就是内存大小-Xmx和吞吐量GCTimeRatio。
 
(2)老年代上的GC实现
 
Serial Old:Serial收集器的老年代版本。
 
Parallel Old:Parallel Scavenge的老年代版本。此前,如果新生代采用PS GC的话,
老年代只有Serial Old能与之配合。现在有了Parallel Old与之配合,可以在注重吞吐量
及CPU资源敏感的场合使用了。
 
CMS:采用的是标记-清除而非标记-整理,是一款并发低停顿的收集器。但是由于
采用标记-清除,内存碎片问题不可避免。可以使用-XX:CMSFullGCsBeforeCompaction
设置执行几次CMS回收后,跟着来一次内存碎片整理。
 
 
5.触发:何时开始GC?
 
Minor GC(新生代回收)的触发条件比较简单,Eden空间不足就开始进行Minor GC
回收新生代。而Full GC(老年代回收,一般伴随一次Minor GC)则有几种触发条件:
 
(1)老年代空间不足
 
(2)PermSpace空间不足
 
(3)统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
 
这里注意一点:PermSpace并不等同于方法区,只不过是Hotspot JVM用PermSpace来
实现方法区而已,有些虚拟机没有PermSpace而用其他机制来实现方法区。
 
 
6.补充:对象的空间分配和晋升
 
(1)对象优先在Eden上分配
 
(2)大对象直接进入老年代
 
虚拟机提供了-XX:PretenureSizeThreshold参数,大于这个参数值的对象将直接分配到
老年代中。因为新生代采用的是标记-复制策略,在Eden中分配大对象将会导致Eden区
和两个Survivor区之间大量的内存拷贝。
 
(3)长期存活的对象将进入老年代
 
对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度
(默认为15岁)时,就会晋升到老年代中。
分享到:
评论

相关推荐

    深入JVM内核—原理、诊断与优化视频教程-5. GC参数

    深入理解JVM内核对于优化应用性能、排查问题至关重要。本教程将聚焦于JVM的垃圾收集(Garbage Collection, 简称GC)参数,这是JVM性能调优的重要一环。 GC的主要任务是自动回收不再使用的内存空间,避免内存泄漏,...

    JVM基础系列

    ### JVM基础系列——深入了解Java虚拟机的重要性 #### 一、引言 随着Java技术的不断发展,Java虚拟机(JVM)已成为软件开发人员不可或缺的核心技能之一。对于初学者而言,掌握JVM的基础知识不仅可以帮助深入理解Java...

    优秀的Java程序员必须了解GC的工作原理

    GC通过监控每个对象的地址、大小及使用情况来实现这一点。在Java中,GC通常使用有向图结构来记录和管理堆(heap)中的所有对象,以此来判断哪些对象是可达的,哪些是不可达的。当GC发现某些对象不再可达时,它们的内存...

    接口偶尔超时,竟又是JVM停顿的锅!.doc

    首先,我们要明确一点:JVM停顿并不总是意味着系统崩溃或异常,它是一种正常的行为,用于执行垃圾收集、线程同步等操作。然而,当这种停顿时间过长时,就可能导致接口响应延迟,进而表现为超时。 在描述中提到,...

    Plumbr Handbook Java Garbage Collection

    理解这一点后,我们可以开始更深入地探讨Java虚拟机中实现自动内存回收机制的具体细节。 垃圾收集的通用概念和核心方法是本手册的开始部分。之后,手册会逐步介绍不同垃圾收集算法的基本原理和实际实现。这里提到的...

    jstat官方介绍

    jstat是一个强大的监控工具,可以为开发者和系统管理员提供关于JVM性能的深入见解,尤其是与垃圾收集和类加载相关的性能指标。通过监控这些指标,用户可以及时发现和解决问题,优化Java应用程序的性能。尽管工具本身...

    StringTable.pdf

    《深入理解JVM中的StringTable》 在Java虚拟机(JVM)中,StringTable扮演着至关重要的角色,它是字符串常量池的实现,存放着程序中所有的字符串字面量。本文将详细探讨StringTable的原理、特点以及相关优化策略。 ...

    2020年最新Java核心知识点整理.pdf

    文件还特别提到了JVM(Java虚拟机)以及GC(垃圾回收)等核心知识点,这些都是深入理解和开发Java应用的关键部分。 针对文件内容的【部分内容】摘录,以下是知识点的详细阐述: ### JVM内存区域 JVM是Java程序能够...

    java核心知识.pdf

    文档中的知识点覆盖了Java的主要知识点,从JVM内存管理到Java I/O模型,再到集合框架和并发编程,每一点都是Java学习者必须掌握的基础。通过阅读这份文档,可以系统地学习和回顾Java编程语言的核心概念,更好地掌握...

    Jconsole JDK自带的监控程序

    1. **Summary Tab**:提供JVM及操作系统级别的基本概览信息,包括但不限于JVM版本、运行时间、操作系统类型等基本信息以及一些关键的性能指标如CPU使用率、GC频率等。 2. **Memory Tab**:显示JVM内存的使用情况,...

    java内存泄漏

    很多开发者基于这一点,认为Java不会出现内存泄漏问题,或者即便出现也是垃圾收集器(Garbage Collection, GC)或Java虚拟机(Java Virtual Machine, JVM)的问题。然而,这种观念并不完全正确。虽然Java的内存管理...

    Java线上故障排查方案(2).pdf

    JVM命令如jps、jmap、jstack、jinfo和jstat等能够提供JVM内部状态和性能数据,帮助工程师进行深入分析。 GC分析,即垃圾收集分析,也是一大关键点。通过分析GC日志,工程师可以了解垃圾回收的行为和影响,对于性能...

    java基础总结(非常详细)

    Java是一种广泛使用的编程语言,以其跨平台的特性、丰富的库和强大的面向对象编程能力而闻名。以下是关于Java基础知识的详细总结: ...对于初学者来说,掌握这些基础知识是进一步深入学习Java编程的关键。

    2024最新阿里、京东、蚂蚁等大厂面试题

    根据给定文件的信息,我们可以提炼出一系列针对2024年阿里、京东、蚂蚁等大厂面试的关键知识点。...以上仅为部分内容摘要,每一点都可以展开深入讨论。希望这些知识点能够帮助求职者更好地准备即将到来的大厂面试。

    Java核心技术(原书第8版)卷II_高级特性

    以上内容仅为Java高级特性的一部分概述,每一点都可以深入展开讨论,形成更为详尽的知识体系。对于想要深入了解Java技术栈的专业人士而言,这些高级特性不仅是提升编程能力的关键,也是构建高效、稳定软件系统的基石...

    如果你想写自己的Benchmark框架(推荐)

    在编写Benchmark的同时,一定要开启JVM的日志,例如:-XX:+PrintCompilation、-verbose:gc等。这样可以校验Benchmark的准确性,並且避免JVM在运行Benchmark时做其他不相关的操作。 第四条军规:注意JIT的分层编译 ...

    19套java面试题

    Java是一种广泛使用的面向对象的编程语言,以其跨平台、高性能和...以上是根据题目可能涵盖的Java知识点的详细解释,每一点都可以深入探讨,面试时,候选人应结合具体问题,展示自己的理解、实践经验和解决问题的能力。

    java面试题(100道)

    以上只是对Java面试题可能涵盖的知识点的概览,每一点都可以深入探讨,理解并掌握这些知识点将有助于你在面试中展现出扎实的技术功底。准备面试时,不仅要理解和记忆这些知识,还要尝试通过实际编码来加深理解,做到...

Global site tag (gtag.js) - Google Analytics