2.4 垃圾收集器
如果说垃圾收集算法是内存回收的
方法论,垃圾收集器就是内存回收的
具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数共用户根据自己的应用特点和要求组合出各个年代所使用的收集器。这里讨论的收集器基于Sun HotSpot虚拟机1.6版Update 22,这个虚拟机包含的所有收集器如图:
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。具体收集器的实现和特点这里就不分析了,需要的话可以查看详细资料。
2.5 内存分配与回收策略 ---回答何时回收
上一篇《2-1》里解答了java内存的两个问题,回收哪些内存,如何回收内存。这一小节重点讲述内存分配,但也间接回答了何时回收内存。
Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:
给对象分配内存以及回收分配给对象的内存。关于回收内存这一点,我们已经介绍了挺多,现在我们再看下给对象分配内存的那点事。
对象的内存分配,往大方向说,就是堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其
细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存有关的参数设置。
接下来我们将会介绍几条最普遍的内存分配规则。
2.5.1 对象优先在Eden分配
在大多数情况下,对象在新生代Eden区中分配。
当Eden区没有足够空间进行分配时,虚拟机就发起一次Minor GC。
虚拟机提供了-XX:+PrintGCDetails这个收集器
日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出时输出当前内存各区域的分配情况。在实际应用中,内存回收日志一般是打印到文件后通过日志工具进行分析。
下面从实例验证内存分配、Minor GC的时机以及学习查看GC日志。这里先介绍下Minor GC和Full GC:
(1) Minor GC,就是新生代GC。指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
(2) Full GC, 也叫Major GC, 老年代GC。指发生在老年代的GC,出现了Major GC, 经常伴随至少一次Minor GC(但也不是绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
上面例子尝试分配3个2MB的对象和1个4MB的对象。在运行时通过-Xmx20M, -Xms20M和-Xmn10M这3个参数限制java堆大小为20M,且不可扩展,其中10M分配给新生代,剩下的10M分配给老年代。默认的-XX:SurvivorRatio=8决定了新生代中Eden区与一个Survivor区的空间比例是8比1,新生代总可用空间为9M。
理论分析上面的代码,执行testAllocation()中分配allocation4对象的语句时会发生一次MinorGC,因为分配4M内存时发现Eden已经被占用了6M, 可用的只有3M,因此发生了Minor GC。GC期间又发现已有的3个2MB的对象全部无法放入1MB的Survivor空间,所以只好通过分配担保机制提前转移到老年代中。
这次GC结束后,4MB的allocation4对象被顺利分配在Eden中。因此程序执行完的结果是Eden占用4MB, Survivor空闲,老年代被占用6MB。
现在查看运行结果输出的GC日志:
从GC日志我们看出,垃圾回收后新生代内存明显减少,但总堆区内存变化不大,主要是6M内存从新生代转移到了老年代。
关于GC日志的查看,可以参考:
http://blog.csdn.net/huangzhaoyang2009/article/details/11860757
http://wenku.baidu.com/link?url=HNGi_DOsSPWnxFyXFXpyB1gkNpndcgOom6kwU22KSnuTasH8hxZvzf_xfY4Au21vOc60HMlXWXyFYCXyDjJykPM4QpPK-6Ob6bikfGIN4Y_
简单的日志可以人工阅读,复杂的日志还是要借助专门的gC日志分析工具来分析。
2.5.2 大对象直接进入老年代
所谓大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组。
大对象对虚拟机的内存分配来说就是一个坏消息(比遇到一个大对象更加坏的消息就是遇到一群“
朝生夕灭”的“短命大对象”,写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来安置它们。
虚拟机提供了一个-XX:PretenureSizeThreshold参数(只是对Serial和ParNew两款收集器有效),令大于这个设置值得对象直接到老年代中分配。这样做的目的是避免在Eden区以及两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
2.5.3 长期存活的对象将进入老年代
虚拟机既然采用了分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象应当放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给这个对象定义了一个
对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC仍然存活,并且能被Survivor容纳的话,将被
移动到Survivor空间中,并将对象年龄设为1.对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁)时,就会晋升到老年代中。对象晋升老年代的年龄阀值,可以通过-XX:MaxTenuringThreshold设置。
2.5.4 动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到maxTenuringThreshlold中要求的年龄。
2.5.5 空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,那也要改为进行一次Full GC。
前面提到过,
新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时(最极端就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行full GC来让老年代腾出更多空间。
取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败,那就只好在失败后重新出发一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免full GC过于频繁。
2.6 小结
《2-1》和本文《2-2》介绍了垃圾收集的算法思想,以及java虚拟机中自动该内存分配及回收的主要规则。
内存回收与垃圾收集器在很多时候都是影响系统性能,并发能力的主要因素之一,虚拟机之所以提供多种不同的收集器及大量的调节参数,是因为只有根据实际应用需求,实现方式选择最优的收集方式才能获取最好的性能。没有固定收集器,参数组合,也没有最优的调优方法,虚拟机也没有什么必然的内存回收行为。因此学习虚拟机内存知识,如果要用到实践调优阶段,必须了解每个具体收集器的行为,优势和劣势、调节参数。
参考资料:
《深入理解java虚拟机》
- 大小: 158.9 KB
- 大小: 82.8 KB
- 大小: 99.4 KB
分享到:
相关推荐
主要整理内容为:分析了垃圾收集的算法和JDK1.7中提供的7款垃圾收集器的特点以及运作原理。以及内存分配策略
JVM内存管理是Java虚拟机的核心机制之一,其主要包含对象的创建、内存分配、...通过对内存分配策略、对象生死判定、垃圾收集算法和垃圾收集器的理解与应用,可以更好地掌握JVM的内存管理,从而提升应用性能和稳定性。
- 对象晋升策略:理解对象在新生代和老年代之间的移动,有助于优化内存分配。 - 深入理解GC日志,分析内存泄漏和性能瓶颈。 6. **编译与即时编译(JIT)** - Java代码在运行时可以被JIT编译为本地机器码,提高...
了解和掌握Java的垃圾收集器与内存分配策略对于开发高性能、稳定的应用至关重要,这涉及到程序的运行效率、内存消耗和避免内存泄漏等问题。通过理解这些概念,开发者可以更好地理解和解决Java应用程序中的内存问题,...
这里 `-Xms` 和 `-Xmx` 设置初始和最大堆大小,`NewRatio` 和 `SurvivorRatio` 调整年轻代和老年代的比例,`UseParallelGC` 指定使用并行垃圾收集器。 4. **会话管理**:减少会话超时时间,避免无效会话占用资源。...
在深入理解JVM内存管理和垃圾收集器之前,我们需要先了解JVM内存模型的基本结构。 内存模型主要包括以下几个部分: 1. **Java堆**:这是JVM管理的最大的内存区域,所有线程共享,主要用于存储类实例和数组。堆内存...
垃圾收集器自动检测并回收不再使用的内存块,避免了程序员手动管理内存可能导致的错误。在嵌入式系统中,垃圾收集面临的主要挑战是资源限制和实时性。由于嵌入式系统的计算能力和内存有限,垃圾收集算法必须高效且低...
漫谈Java垃圾收集器 Java垃圾收集器是Java虚拟机(JVM)中的一种自动内存管理机制,旨在释放程序员从手动内存管理的繁琐工作中解脱出来。垃圾收集器通过跟踪对象的引用关系,确定哪些对象是可以被释放的,然后将其...
深入理解JVM内存分配、垃圾收集(Garbage Collection, GC)原理以及垃圾收集器的工作方式对于优化程序性能至关重要。 首先,我们要了解JVM内存结构。在Java中,内存主要分为以下几个区域: 1. **堆内存(Heap)**...
Java垃圾收集器是Java语言中一个关键的特性,它负责管理程序运行时的内存空间,尤其是对象的分配和回收。在传统的编程语言如C++中,堆内存的管理需要程序员手动进行,分配和释放对象可能会涉及到复杂的内存操作,...
- 无用的对象不一定会在垃圾收集器的每次运行中被回收,也可能在整个程序运行期间都保留着,直到程序结束,除非它们的内存被重新分配或由其他方式释放。 6. **垃圾收集策略**: - Java提供了多种垃圾收集策略,如...
- 垃圾收集器需要与JVM的监控工具(如MX Beans和诊断命令)兼容。Epsilon GC已经处理了这部分工作,我们可以直接使用。 4. **根对象(GC Roots)**: - GC Roots是垃圾收集器识别活动对象的起点,包括线程栈、...
.NET框架提供了几个关键方法来与垃圾收集器交互,如`GC.Collect()`用于强制执行垃圾收集,`GC.WaitForPendingFinalizers()`等待所有终结器执行完毕,`GC.GetTotalMemory(true)`获取应用程序使用的总内存等。...
Java的垃圾收集器(Garbage Collector, GC)负责监控和清理不再有引用指向的对象,防止内存泄漏。 1. 对象生命周期:当一个对象被创建后,它会被分配到堆内存中。随着程序的执行,如果对象不再被任何引用指向,那么...
例如,调整堆大小(`-Xms`和`-Xmx`)、新生代大小(`-XX:NewSize`和`-XX:MaxNewSize`)、并发收集器设置(`-XX:+UseConcMarkSweepGC`)等,以优化垃圾回收和内存分配。 在进行这些优化时,需要注意根据实际的服务器资源和...
垃圾收集器在一个JAVA程序中的执行是自动的,不能强制执行,即是程序员能明确的判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。希望通过本文的介绍,能够给你带来帮助。
3. **低优先级与紧急响应**:垃圾收集器线程在系统资源紧张时,可能会被触发以释放内存,但这并不总是按照预期进行,其执行时机是不确定的。 4. **不可强制执行**:虽然程序员可以通过调用`System.gc()`来建议执行...
Java垃圾收集器与内存分配策略是Java性能优化的重要组成部分。垃圾收集器的主要任务是自动管理Java应用程序的内存,确保程序运行过程中有效地回收不再使用的对象,从而避免内存泄漏。本文将详细讲解Java垃圾收集器的...