它在串行回收器的基础上做了改进,它使用多个线程同时进行垃圾回收.并行能力强的机器可以缩短回收时间.
2.1新生代ParNew回收器
它知识简单的将串行回收器多线程化,回收策略,算法,参数和串行的回收器一样。
开启的参数:
-XX:+UseParNewGC:新生代用ParNew,老年代用串行的.
-XX:ParallelGCThreads:指定parnew回收器工作时使用的线程数量,一般最好和cpu数量相同.默认情况,cpu<8时.线程数量等于cpu数量.cpu>8时,线程数量等于3+((5*cpu)/8)
-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS.
2.2新生代ParallelGC回收器
它也是复制算法的回收器.它和Parnew的区别在于,它很关注系统的吞吐量.
启用的参数如下:
-XX:+UseParallelGC:新生代用ParallelGC回收器,老年代用串行的.
-XX:+UseParallelOldGC:新生代用ParallelGC,老年代用ParallelGCOld
它提供了两个重要的参数控制吞吐量:
-XX:MaxGCPauseMillis:设置最大垃圾手机停顿时间.它会调整堆大小或者其他一些参数,如果把值设置的很小,虚拟机可能会使用一个小堆,不过这会导致回收频繁,增加回收总时间,降低吞吐.
-XX:GCTimeRatio:设置吞吐量大小(0-100).如果设置为n,那么系统在垃圾收集的时间不会超过(1/1+n)%。默认n=99;
除此之外,它还支持一种自适应的gc调节策略.使用下面的参数打开.
-XX:+UseAdaptiveSizePolicy
在手工调优比较困难的场合,可以用这个方式,让虚拟机自己调优。
2.3老年代ParallelOldGC回收器
和ParallelGC类似,不过它作用于老年代,使用标记压缩算法,在jdk1.6中才可以使用.也可以使用-XX:ParallelGCThreads
3.CMS回收器 Cms主要关注系统停顿时间。CMS(Concurrent Mark Sweep),意为并发标记清除,是使用标记清除算法,也是一个使用多线程并行回收的垃圾回收器.
3.1 CMS主要工作步骤
其中,初始标记和重新标记是独占系统资源的。默认情况下,并发标记之后会有一个预清理(-XX:-CMSPrecleaningEnabled:不进行预清理).
如果新生代GC和重新标记接连触发,会导致停顿时间太长.预清除可以根据历史数据预测下一次新生代GC的时间,然后让重新标记发生在当前时间和预测时间的中间节点.
3.2 CMS主要参数
-XX:+UseConcMarkSweepGC:启动CMS回收器
-XX:ConcGCThreads -XX:ParallelCMSThreads:手工设定并发线程数量。默认并发线程数量是ParallelGCThreads+3 /4.ParallelGCThreads表示GC并行时使用的线程数量。
CPU资源紧张时,收到CMS回收器线程的影响,应用系统的性能可能很糟糕.
(PS:并发是指收集器和应用线程交替执行,并行是应用程序停止,多个线程一起GC。并行的回收器不是并发的.)
它不会等到堆能存饱和时候才进行垃圾回收,而是达到一个阀值的时候就开始回收,以确保CMS工作过程中,依然有足够的空间支持应用程序运行.
-XX:CMSInitiatingOccupancyFrction:设置阀值,默认68.当老年代的空间使用率达到68%时,就会执行一次CMS回收。如果应用的内存增长率很快,执行过程中,已经出现了内存不足的情况,此时,CMS回收就会失败,虚拟机机会启用串行收集器进行垃圾回收。
因为它使用的是标记清除算法,所以会产生垃圾碎片.这个对于系统性能是相当不利的.下面几个参数可以解决这个问题.
-XX:+UseCMSCompactAtFullCollection开关可以使CMS在垃圾手机完成后,进行一次碎片整理,碎片整理不是并发进行的.
-XX:CMSFullGCsBeforeCompaction:可以用于设定进行多少次CMS回收后,进行一次内存压缩.
3.3 CMS日志分析
如果在日志中发现(concurrent mode failure),说明CMS回收器并发收集失败.可能是中老年代空间不足.如果频繁出现这个提示,就应该预留一个较大的老年代空间.
3.4Class的回收
如果需要回收Perm区,默认情况下,要触发一次Full GC.如果希望CMS回收Perm,需要打开-XX:+CMSClassUnloadingEnabled.
4.G1回收器 G1回收器是在jdk1.7中使用的全新垃圾回收器.从分代上看,G1依然属于分代垃圾回收器,它会区分年轻和老年代,依然有eden和survivor,但是从堆的结构上来看,它不要求整个eden,年轻代老年代 都连续.
它有如下特点:
并行:多个GC线程同时工作.
并发:可以和应用程序同时执行.
分代GC:它可以同时用于年轻代和老年代.
空间整理:它每次回收都会进行有效的复制.
可以预见性:由于分区的原因,可以每次只选取部分区域进行内存回收,这样缩小了回收的范围.
4.1 G1内存划分和收集过程.
G1把堆内存分区,每次只收集几个分区.
G1收集过程有4个阶段 :
新生代GC;并发标记周期;混合收集;如果需要会进行FULLGC
4.2 G1的新生代GC
新生代GC主要回收eden和survivor.一旦eden区被占满,新生代GC就会启动,
下图所示,E,S,O分别代表eden,survivor,老年代.
4.3G1的并发标记周期
G1的并发阶段和CMS有点类似.
初始标记:标志根结点可达对象,这个过程会伴随一次新生代GC产生全局停顿。
根区域扫描:不能被GC打断.
并发标记:扫描整个堆空间,并做好标记.
重新标记:对上一次的标记结果进行修正,G1在这个过程中使用SATB算法,类似快照,加速标记过程.
独占清理:这个过程会引起停顿.
并发清理:清理需要清理的区域.
4.4混合回收
在并发标记周期中,回收的比例不会高.但是,G1已经知道那些区域有比较多的垃圾,混合回收阶段就会专门针对这些区域回收。
混合GC会执行多次,直到回收了足够多的内存,然后会触发一次新生代GC。之后又可能会发生一次并发标记周期的处理,最后又会引起混合GC.
在某些特别繁忙的场合会出现在回收过程中内存不足的情况.遇到这种情况,就会发生FullGC
二 .system.gc对于虚拟机参数的影响
-XX:+DisableExplicitGC:禁用System.gc
System.gc默认会忽略CMS和G1,加上-XX:ExplicitGCInvokesConcurrent可以改变这个默认行为.
使用串行回收器调用system.gc,只会发生full gc.
并行GC会先触发新生代GC,再发生full gc;这样做是避免所有回收工作同时交给一次Full GC进行,缩短停顿时间.
三. 对象内存分配
1.对象何时进入老年代
-Xms50m -Xmx50m -XX:+PrintGCDetails
public static void main(String[] args) {
for(int i =0 ;i< 5*1024 ;i++){
//一次分配1kb,总共分配5m
byte[] b = new byte[1024];
}
}
可以观察到只有eden的空间被使用了.那么新生代对象普通情况下什么时候会进入到老年代呢.新生代对象每经历一次GC,年龄就+1,MaxTenuringThreshold这个参数用来设置新生代对象的最大年龄. 默认是15.但是也有可能在小于15次GC的时候晋升到老年代,这个取决于survivor区GC后的使用率,默认50.
如果优先达到这个使用率,那么就自动晋升,如果一直没有达到这个使用率,那么在age等于15的时候晋升.
使用参数:
-XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC
-Xms100M -Xmx100M -Xmn24m -XX:SurvivorRatio=2 -XX:MaxTenuringThreshold=6 -XX:TargetSurvivorRatio=99
如果不使用TargetSurvivorRatio,那么有可能在age=2的时候就被晋升到老年代.这里分配了5m不会被回收的空间,新生代为24m,ratio=2,所有eden=12M,from=to=6M,足够存放5m的空间,使用率5/6=83.3%<99%.所以肯定会触发MaxTenuringThreshold这个条件.有兴趣的同学可以自己去调整TargetSurvivorRatio这个参数测试.
static Map<Integer,byte[]> map = new HashMap<Integer,byte[]>();
public static void main(String[] args) {
//分配5m内存
for(int i =0 ;i< 5*1024 ;i++){
map.put(i, newbyte[1024]);
}
//分配100 m空间
for(int j = 0 ;j< 100 ;j++){
byte[] b = newbyte[1024*1024];
}
}
2.大对象进入老年代
如果对象的体积很大,survivor无法容纳,那么会直接晋升到老年代.
-XX:PretenureSizeThreshold可以设置这个默认值(单位字节).默认为0.这参数只对串行和parnew有效,对ParallelGC无效.
-XX:+PrintGCDetails -XX:+UseSerialGC -XX:PretenureSizeThreshold=500 -Xms32M -Xmx32M
static Map<Integer,byte[]> map = new HashMap<Integer,byte[]>();
public static void main(String[] args) {
//分配5m内存
for(int i =0 ;i< 5*1024 ;i++){
map.put(i, newbyte[1024]);
}
}
理论上所有的字节都应该被分配到老年代,但是实际情况却是如下所示.
Heap
def new generation total 9792K, used 6474K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000)
eden space 8704K, 74% used [0x00000007be000000, 0x00000007be6528d0, 0x00000007be880000)
from space 1088K, 0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000)
to space 1088K, 0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000)
tenured generation total 21888K, used 16K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000)
the space 21888K, 0% used [0x00000007beaa0000, 0x00000007beaa4010, 0x00000007beaa4200, 0x00000007c0000000)
Metaspace used 2652K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
这是因为虚拟机为线程分配空间时,会优先使用TLAB区域,又可能会在TLAB优先分配失去了晋升老年代的机会.
加上-XX:-UseTLAB就可以了
3.TLAB上分配对象
TLAB全场Thread local allocation buffer,即线程本地分配缓存.这是一个线程专用的内存分配区域.它是为了加速对象分配而生的.TLAB本身占用eden空间.默认会为每一个线程分配一块TLAB空间.
分别测试开启与关闭的情况:禁用逃逸分析,防止栈上分配,禁止后台编译
分别用下面两组参数去执行,观察执行时间,你会发现,开启Tlab,的时间会大大优于不开启.
-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis
-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis
public static void main(String[] args) {
long begin = System.currentTimeMillis();
for(int i =0 ;i< 1000000000 ;i++){
byte[] by = new byte[2];
by[0] = '1';
}
long end = System.currentTimeMillis();
System.out.println("Direct:"+(end-begin));
}
Tlab空间不会太大,如果tlab的空间已经使用了X KB,再分配一个 Y KB的对象时已经放不下了.这时候虚拟机有两种选择:
1.废弃当前tlab
2.直接在堆上分配
虚拟机内部会维护一个refill_waste的值 ,当请求对象大于它时,在堆上分配.这个阀值可以使用TLABRefillWasteFraction来调整,它表示比例,默认64,代表用1/64的tlab空间大小作为refill_waste。
默认情况,tlab和refill_waste都会在运行时不断调整.如果想要禁用自动调整-XX:-ResizeTLAB禁用自动调整,-XX:TALBSize:指定tlab大小,如果想要观察tlab打开跟踪参数:-XX:+PrintTLAB
下图是一个简单对象的分配流程
四.finalize()方法对垃圾回收的影响
finalize()是FinalizerThread线程处理的,每一个即将被回收的并且包含有finalize方法的对象都会在正式回收前加入执行队列.该队列为java.lang.ref.ReferenceQueue引用队列,内部实现为链表结构.
FinalReference中Finalizer封装了实际的回收对象,相当于一个强引用,这意味对象又变成一个可达对象.一旦出现性能问题,将导致这些垃圾对象长时间堆积在内存中,可能导致oom.
下面的程序就会导致oom.
public class LongFinalize {
private static class FinalizeF{
private byte[] b = new byte[512];
@Override
protected void finalize() throws Throwable {
try {
System.out.println(Thread.currentThread().getId());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for(int i = 0 ;i < 40000;i++){
FinalizeF f = new FinalizeF();
}
}
}
finailzeThread执行流程:
使用参数-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/zcf1/Downloads/test.dump 把具体的信息生成到dump文件中.然后通过eclipse mat ->open dump file 打开
可以看到Finalizer占用了8.7M.使用系统自带的“Finalizer overview”可以更好的查看Finalizer.
ps:垃圾回收器和新生代老年代的关系
相关推荐
本文将深入探讨内存分配的策略、内存回收的方法以及它们在实际操作系统中的应用。 首先,我们来看看内存分配。内存分配的主要目标是为进程提供所需的存储空间,以便它们能够执行其功能。它涉及以下几种主要方法: ...
- 当`stepmul`值为200时(默认值),表明垃圾收集器运行的速度是内存分配速度的两倍。这意味着垃圾收集器能够更快地清理无用对象,但这也可能导致更多的CPU使用率。 - 如果`stepmul`值小于100,可能会导致垃圾收集器...
大部分新创建的对象首先被分配到Eden区,经历第一次垃圾回收后,存活下来的对象会转移到Survivor区。 - 老年代存放生命周期较长的对象,当新生代的空间不足以容纳新对象或者Survivor区的对象达到一定年龄时,它们...
- `System.gc()` 和 `System.runFinalization()`:请求垃圾回收器执行垃圾回收和调用`finalize()` 方法。需要注意的是,虽然这些方法允许开发者显式地请求垃圾回收,但并不保证垃圾回收器会立即响应。 - `freeMemory...
CLR 垃圾回收器根据所占空间大小划分对象,大对象和小对象的处理方式有很大区别。 大对象堆和垃圾回收 在 .NET 1.0 和 2.0 中,如果一个对象的大小超过 85000byte,就认为这是一个大对象。这个数字是根据性能优化...
- **弱引用**:使用弱引用可以帮助垃圾回收器更早地回收不再使用的对象。 - **显式调用GC**:虽然不推荐常规使用,但在某些特定场景下显式调用`GC.Collect()`可以强制触发垃圾回收。 #### 五、内存泄漏检测 即使有...
Java的自动内存管理系统使得程序员无需手动管理内存,而是由JVM(Java虚拟机)自动进行垃圾回收和内存分配。以下是对这些主题的详细阐述: 一、垃圾收集器 1. **垃圾定义**:在Java中,当一个对象不再被任何引用...
在这个实验和课程设计中,我们将深入探讨动态内存分配与回收的机制,通过Java编程语言来实现四种不同的内存分配策略:First Fit(FF)、Next Fit(NF)、Best Fit(BF)以及Worst Fit(WF)。 首先,让我们详细了解...
Java垃圾回收机制是Java语言中一个非常重要的特性,它...开发者需要关注内存分配、对象生命周期、垃圾回收器的选择以及相关的JVM调优策略,以确保应用程序的高效运行,避免出现因内存问题导致的系统崩溃或性能下降。
当不再使用对象时,应当设置其引用为`Nothing`,以通知垃圾回收器释放内存。 3. **集合与数组**:VB中的集合类和数组在分配内存时会一次性分配所需空间,如果预估不准确,可能导致内存浪费。理解数组和集合的内存...
本文将详细探讨JVM的发展历程以及内存管理中的垃圾回收机制。 一、JVM的历史发展 1. **早期阶段**:1995年,Sun Microsystems发布了Java的第一个版本,JVM作为其核心组成部分,主要应用于嵌入式设备和网络应用。初...
当不再需要这些对象时,垃圾回收器会自动释放内存,这是一个自动的内存管理机制。然而,对于非托管资源,如原始数据类型或结构,程序员需要手动进行内存管理,这通常涉及到`malloc`和`free`(在C/C++中)或者`GC....
本文将探讨Java的四种主要垃圾回收器:Serial、Parallel、CMS(并发标记清除)和G1(垃圾第一),以及它们各自的特点和应用场景。 1. Serial Garbage Collector:这是一个单线程的垃圾回收器,主要针对轻量级应用或...
4. G1(Garbage-First):新一代垃圾回收器,目标是实现可预测的暂停时间模型,适用于大内存应用。 四、JVM调优参数 理解并正确设置JVM参数对于优化垃圾回收至关重要,例如: - `-Xms` 和 `-Xmx`:设置堆内存的初始...
Java的垃圾回收机制是一种自动管理内存的机制,通过垃圾回收器(Garbage Collector)来回收堆内存中的对象,从而避免内存泄露和溢出。下面是Java的垃圾回收机制的详细分析: 一、垃圾回收机制的原理 当程序创建...
在C++或C#这样的高级语言中,运行时环境通常会提供更高级别的内存管理机制,如智能指针或垃圾回收机制,以简化内存管理并避免内存泄漏等问题。 #### 总结 综上所述,程序的内存分配是操作系统中一项关键的技术,...
随着应用程序的执行,这些对象的生命周期结束,CLR 会准备再次分配内存,如果空间不够,CLR 会触发垃圾回收机制,压缩内存,以释放出更多的内存空间。 在动画演示中,我们可以看到,垃圾回收机制如何将对象从托管堆...
如果在函数内没有使用var、let或const声明变量,该变量会被视为全局变量,从而不会被垃圾回收器回收。 2. 闭包引起的内存泄漏 闭包会保持对函数内部变量的引用,如果闭包被长时间或无限期的执行,就可能造成内存...
垃圾回收的主要目标是自动化地处理程序中的内存分配和释放,确保程序可以有效地使用内存。在C/C++这样的语言中,程序员需要手动管理内存,这容易导致内存泄漏或悬挂指针等问题。因此,"著名的C/C++垃圾回收站"可能是...