千家信息网

java垃圾收集器中内存分配和回收的一些细节是怎样的

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章将为大家详细讲解有关java垃圾收集器中内存分配和回收的一些细节是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1. 禁用 System
千家信息网最后更新 2026年11月02日java垃圾收集器中内存分配和回收的一些细节是怎样的

这篇文章将为大家详细讲解有关java垃圾收集器中内存分配和回收的一些细节是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

1. 禁用 System.gc()

jvm提供了一个参数 DisableExplicitGC 来控制是否手工触发GC,如果需要禁用,可以使用以下配置:

-XX:+DisableExplicitGC

2. System.gc() 使用并发回收

在默认情况下,即使 System.gc 生效,会使用传统的 Full GC 方式回收整个堆,而忽略参数中的 UseG1GC 和 UseConcMarkSweepGC,即CMS和G1是没有并发执行的。

示例代码

public class Demo01 {    public static void main(String[] args) {        System.gc();    }}

使用-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC,日志如下:

[Full GC (System.gc()) [CMS: 0K->372K(174784K), 0.0246450 secs] 2798K->372K(253440K), [Metaspace: 2906K->2906K(1056768K)], 0.0247414 secs] [Times: user=0.02 sys=0.01, real=0.02 secs]

使用-XX:+PrintGCDetails -XX:+UseG1GC,日志如下:

[Full GC (System.gc())  1517K->368K(8192K), 0.0089949 secs]   [Eden: 2048.0K(12.0M)->0.0B(3072.0K) Survivors: 0.0B->0.0B Heap: 1517.6K(256.0M)->368.7K(8192.0K)], [Metaspace: 2906K->2906K(1056768K)] [Times: user=0.01 sys=0.00, real=0.01 secs]

显然,此时CMS和G1是没有并发执行的,因为在日志中没有任何并发相关的信息。可以使用以下参数来改变这种默认行为:

-XX:+ExplicitGCInvokesConcurrent

使用-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+ExplicitGCInvokesConcurrent,日志如下:

[GC (System.gc()) [ParNew: 2798K->398K(78656K), 0.0010206 secs] 2798K->398K(253440K), 0.0010476 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(174784K)] 1797K(253440K), 0.0001720 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-mark-start][CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-preclean-start][CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (CMS Final Remark) [YG occupancy: 1797 K (78656 K)][Rescan (parallel) , 0.0007200 secs][weak refs processing, 0.0000066 secs][class unloading, 0.0001855 secs][scrub symbol table, 0.0003697 secs][scrub string table, 0.0001424 secs][1 CMS-remark: 0K(174784K)] 1797K(253440K), 0.0014769 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-sweep-start][CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-reset-start]

使用-XX:+PrintGCDetails -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent,日志如下:

[GC pause (System.gc()) (young) (initial-mark), 0.0024614 secs]   [Parallel Time: 1.5 ms, GC Workers: 8]      [GC Worker Start (ms): Min: 100.6, Avg: 100.7, Max: 100.8, Diff: 0.2]      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 1.6]      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]      [Object Copy (ms): Min: 0.4, Avg: 0.6, Max: 1.0, Diff: 0.6, Sum: 5.1]      [Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.5, Diff: 0.5, Sum: 3.0]         [Termination Attempts: Min: 1, Avg: 3.1, Max: 8, Diff: 7, Sum: 25]      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]      [GC Worker Total (ms): Min: 1.1, Avg: 1.2, Max: 1.4, Diff: 0.3, Sum: 9.8]      [GC Worker End (ms): Min: 101.9, Avg: 101.9, Max: 102.0, Diff: 0.1]   [Code Root Fixup: 0.0 ms]   [Code Root Purge: 0.0 ms]   [Clear CT: 0.1 ms]   [Other: 0.8 ms]      [Choose CSet: 0.0 ms]      [Ref Proc: 0.6 ms]      [Ref Enq: 0.0 ms]      [Redirty Cards: 0.1 ms]      [Humongous Register: 0.0 ms]      [Humongous Reclaim: 0.0 ms]      [Free CSet: 0.0 ms]   [Eden: 2048.0K(24.0M)->0.0B(23.0M) Survivors: 0.0B->1024.0K Heap: 2009.1K(256.0M)->568.1K(256.0M)] [Times: user=0.01 sys=0.00, real=0.00 secs] [GC concurrent-root-region-scan-start][GC concurrent-root-region-scan-end, 0.0003924 secs][GC concurrent-mark-start][GC concurrent-mark-end, 0.0002255 secs][GC remark [Finalize Marking, 0.0001208 secs] [GC ref-proc, 0.0000284 secs] [Unloading, 0.0002767 secs], 0.0005331 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC cleanup 1039K->1039K(256M), 0.0003741 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

使用ExplicitGCInvokesConcurrent参数后,System.gc() 这种显式GC才会使用并发的方式进行回收,否则无论是否启用了CMS或G1,都不会进行并发回收。

3. 并行GC前额外触发的新生代GC

对于并行回收器(使用UseParallelOldGC或者UseParallelGC),在每一次FullGC之前都会伴随一次新生代GC,这和串行回收器相比,有很大的不同,示例如下:

public class Demo01 {    public static void main(String[] args) {        System.gc();    }}

使用-XX:+PrintGCDetails -XX:+UseSerialGC,gc日志如下:

[Full GC (System.gc()) [Tenured: 0K->367K(174784K), 0.0017465 secs] 2798K->367K(253440K), [Metaspace: 2903K->2903K(1056768K)], 0.0017770 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,System.gc() 触发了一次Full GC.

使用-XX:+PrintGCDetails -XX:+UseParallelOldGC,gc日志如下:

[GC (System.gc()) [PSYoungGen: 2621K->528K(76288K)] 2621K->536K(251392K), 0.0008817 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 528K->0K(76288K)] [ParOldGen: 8K->368K(175104K)] 536K->368K(251392K), [Metaspace: 2905K->2905K(1056768K)], 0.0036679 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,触发FullGC前,进行了一次新生代GC。因此,这里的Sytem.gc()实际上触发了两次GC,这样做的目的是先将新生代进行一次回收,避免将所有回收工作同时交给一次Full GC进行,从而尽可能地缩短一次停顿时间。

如果不需要这个特性,可以使用参数-XX:-ScavengeBeforeFullGC去除发生在FullGC之前的那次新生代GC. 使用-XX:+PrintGCDetails -XX:+UseParallelOldGC -XX:-ScavengeBeforeFullGC运行,gc日志如下:

[Full GC (System.gc()) [PSYoungGen: 2621K->0K(76288K)] [ParOldGen: 0K->368K(175104K)] 2621K->368K(251392K), [Metaspace: 2906K->2906K(1056768K)], 0.0032836 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,Full GC 前已经没有了新生代gc。

4. 对象何时进入老年代

  • 初创对象在eden区产生

  • 老年对象进行老年代:虚拟机提供一个参数来控制新生代对象的最大年龄:MaxTenuringThreshold,这个值默认是15,即新生代对象最多经历15次GC,就可以晋升到老年代。实际情况中,对象的实际晋升年龄是根据survivor区的使用情况动态计算得来的,而 MaxTenuringThreshold 只是表示这个年龄的最大值。可以使用参数TargetSurvivorRatio设置survivor的目标使用率,默认为50,即如果survivor区在GC后使用率超过50%,那么就很可能会使用较小的age作为晋升年龄。

  • 大对象进入老年代:如果对象很大,新生代无论是eden区还是survivor区都无法容纳这个对象,自然这个对象无法存放在新生代。jvm提供了参数PretenureSizeThreshold参数来设置对象直接晋升到老年代的阈值,单位是字节。只要对象大于该指定值,就会直接在老年代分配。这个参数只对串行回收器和ParNew有效,对于ParallelGC无效。默认下该值为0,也就是不指定最大的晋升大小,一切由运行情况决定。

大对象直接进入老年代的示例:

public class Demo04 {    public static final int _1K = 1024;    public static void main(String[] args) {        Map map = new HashMap<>();        for(int i = 0; i < 5 * _1K; i++) {            byte[] b = new byte[_1K];            map.put(i, b);        }    }}

使用-Xmx32m -Xms32m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB参数运行,结果如下:

Heap def new generation   total 9792K, used 963K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000)  eden space 8704K,  11% used [0x00000007be000000, 0x00000007be0f0f88, 0x00000007be880000)  from space 1088K,   0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000)  to   space 1088K,   0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000) tenured generation   total 21888K, used 5953K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000)   the space 21888K,  27% used [0x00000007beaa0000, 0x00000007bf070408, 0x00000007bf070600, 0x00000007c0000000) Metaspace       used 3054K, capacity 4496K, committed 4864K, reserved 1056768K  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K

可以看到,无任何gc日志输出,最终使用的空间中,老年代使用了大约5m空间。

5. 在TLAB上分配对象

TLAB 全称是Thread Local Allocation Buffer,即线程本地分配缓存。从名字上看,TLAB是一个线程专用的内存分配区域。由于对象一般分配在堆上,而堆是全局共享的,在同一时间内,可能有多个线程在堆上申请空间。因此,每一次对象分配都必须进行同步,而在竞争激烈的场合分配的效率又会进一步下降。考虑到对象分配几乎是java最常用的操作,因此java虚拟机就使用了TLAB这种线程专属的区域来避免多线程冲突,提高对象分配的效率。TLAB本身占用了eden区空间,在TLAB启用的情况下,虚拟机会为每一个java线程分配一块TLAB区域。

默认情况下,TLAB的大小是会在运行时不断调整的,使系统的运行状态达到最优。如果想禁用自动调整TLAB的大小,可以使用-XX:ResizeTLAB禁用ResizeTLAB并使用-XX:TLABSize手工指定TLAB的大小。

示例:启用TLAB与关闭TLAB时的性能差异

public class Demo05 {    public static void alloc() {        byte[] b = new byte[2];        b[0] = 1;    }    public static void main(String[] args) {        long b = System.currentTimeMillis();        for(int i = 0; i < 1000_0000; i++) {            alloc();        }        long e = System.currentTimeMillis();        System.out.println(e - b);    }}
  • 使用参数-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server运行,结果为71;

  • 使用参数-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server运行,结果为135;

从结果来看,TLAB是否启用对于对象分配的影响还是很大的。

对象的分配流程:

  • 如果开启了栈上分配,系统就会先进行栈上分配;

  • 没有开启栈上分配或者不符合条件则会进行TLAB分配;

  • 如果TLAB分配不成功,再尝试在堆上分配;

  • 如果满足了直接进入老年代的条件,就在老年代分配;

  • 否则就在eden区分配,当然,如有必要,可能会进行一次新生代GC.

6. finalize() 函数对垃圾回收的影响

java中提供了一个类似于C++析构函数的机制--finalize()函数,该函数允许在子类中被重载,用于在对象被回收时进行资源释放。目前普遍的认识是尽量不要使用finalize()函数进行资源释放,原因主要有以下几点:

  • 在finalize()函数时,可能会导致对象复活;

  • finalize()函数的执行时间是没有保障的,它完全由GC线程决定,在极端情况下,若不发生GC,finalize()将没有机会执行;

  • 一个糟糕的finalize()函数会严重影响GC性能。

虽然不推荐使用finalize()函数,但是在某些场合,使用finalize()函数可以起到双保险的作用。比如,在mysql的jdbc驱动中,com.mysql.jdbc.ConnectionImpl就实现了finalize()函数,实现代码如下:

protected void finalize() throws Throwable {    this.cleanup((Throwable)null);    super.finalize();}

也就是,当一个jdbc connection被回收时,需要进行连接的关闭,即这里的cleanup()方法。事实上,在回收前,开发人员如果正常调用了Connection.close()方法,连接就会被显式关闭,那样的话,在cleanup()方法中将什么也不做。而如果开发人员忘记显式关闭连接,而Connection对象又被回收了,则会隐式地进行连接的关闭,确保没有数据库连接泄露。此时,finalize()函数可能会被作为一种补偿措施,在正常方法出现意外时进行补偿,尽可能确保系统稳定。当然,由于其调用时间的不确定性,这不能单独作为可靠的资源回收手段。


关于java垃圾收集器中内存分配和回收的一些细节是怎样的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

对象 分配 参数 函数 新生 新生代 年代 日志 情况 线程 运行 晋升 大小 年龄 方法 时间 示例 空间 结果 内存 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司排名 科技和互联网公司 互联网下的科技发展 安徽鑫晟辉互联网科技 奇酷互联网络科技公司 亿账通互联网科技 掌上梦互联网科技 长亮科技互联网金融 阿里巴巴算科技企业还是互联网 福州新华互联网科技学校 知乎 现代互联网科技股 互联网科技项目计划书材料类 工业互联网等科技实现智能化赋能 伊宁互联网科技做什么的 张召忠谈中国互联网科技 互联网与数字科技关系 国产互联网科技公司 互联网科技企业商标注意事项 陕西彩京云互联网科技有限公司 互联网大会出现的黑科技 奇酷互联网络科技 沃里互联网科技 一五八零互联网科技 赶超互联网科技 微信多开 青岛科技大学互联网大赛研究生 互联网科技和法律行业 合肥互联网科技公司公章图片 山东大美朱楼互联网科技有限公司 互联网高科技行业有哪些企业 绵阳真晨互联网科技有限公司
0