Garbage Collection 一直在执行? 还是需要时才启动? (转)

本文深入探讨了Java中垃圾回收(GC)的工作原理,揭示了GC并非仅在必要时才启动,而是作为一个独立线程持续监控内存状态。通过具体示例和Java虚拟机参数设置,展示了GC的实际运行情况。
Garbage Collection 一直在执行? 还是需要时才启动? (转)[@more@]  (作者 : 艾群科技 萧松瀛)
 
  内存对於 Java 来说,往往是非常珍贵的资源,虽然现在的内存很
便宜,随随便便几百 MB 不过几千块台币,但是如果我们能够掌握一些记
忆体的状况,那么就不会写出 memory monster 的程式,我想,如果您使
j2me 或是其他内存不够的周边,那么我想你就可以深深的体会出这
句话的意义。而 Java 与内存,我们不得不再度提到 Garbage Collect
ion ,事实上,许多人可能会以为 Garbage Collection 是有需要的时候
执行,实际上并不是这样,需要记住的是  Garbage Collection (以下
简称  GC) 是另外一个执行绪在执行的动作,也就是当你程式在执行的同
时,实际上  GC 也一直在监测目前的各种状况。
 
 想要了解这个,我们可以先从 java (java.exe) 的参数来下手。当我们
下 java -help 之後,可以看到  -verbose[:class|gc|jni] 这个参数,
没错,这个参数可以让我们看到一些更详细的状况。首先,我写了底下这
个程式 
 
  public class testGC
  {
  public static void main(String argv[])
  {
  StringBuffer sb = new StringBuffer();
  int MAX=Integer.parseInt(argv[0]);
  for(int i = 0 ; i <=MAX ; i++)
  {
  sb.append("test").append(i).append("
 test");
  if(i % (MAX/2)== 0)
  {
  System.out.println("===Starting System.gc(
 )===");
  System.gc();
  System.out.println("===Finishing System.gc
 ()===");
  }
  }
  }
  }
 
  在 Compile 结束之後,我直接用 java testGC 20000,结果如下:
 
  C:javatmp> java testGC 20000
  ===Starting System.gc()===
  ===Finishing System.gc()===
  ===Starting System.gc()===
  ===Finishing System.gc()===
  ===Starting System.gc()===
  ===Finishing System.gc()===
 
  但是现在我们加上另外一个参数  -verbose:gc ,让命令列成为 
 
  java -verbose:gc testGC 20000 结果如下:
 
  C:javatmp> java -verbose:gc testGC 20000
  ===Starting System.gc()===
 [GC[0: 367K-> 190K(1984K)][1: 190K-> 190K(1984K)], 0.0454
 848 secs]
  ===Finishing System.gc()===
  [GC[0: 1206K-> 694K(1984K)], 0.0009384 secs]
  ===Starting System.gc()===
 [GC[0: 879K-> 694K(1984K)][1: 694K-> 478K(1984K)], 0.0336
 423 secs]
  ===Finishing System.gc()===
  [GC[0: 1565K-> 1054K(1984K)], 0.0006216 secs]
  ===Starting System.gc()===
 [GC[0: 1167K-> 1054K(1984K)][1: 1054K-> 766K(1984K)], 0.0
 375078 secs]
  ===Finishing System.gc()===
 
  以 [ 开头, ] 结尾的是 GC 的执行状况,表示的确,在 System.gc()
  後,他有执行,我们可以看到他很均匀的插在我们的每一行输出中间,
没关系,我们把参数由  20000 调到 50000,再执行一次,结果如下
 
  C:javatmp> java -verbose:gc testGC 50000
  ===Starting System.gc()===
 [GC[0: 367K-> 190K(1984K)][1: 190K-> 190K(1984K)], 0.0457
 804 secs]
  ===Finishing System.gc()===
  [GC[0: 1206K-> 694K(1984K)], 0.0009297 secs]
  [GC[0: 1782K-> 1270K(1984K)], 0.0005685 secs]
  [GC[0: 1782K-> 1270K(1984K)], 0.0006135 secs]
  [GC[1: 1277K-> 766K(1984K)], 0.0353595 secs]
  ===Starting System.gc()===
 [GC[0: 2009K-> 1918K(3140K)][1: 1918K-> 1342K(3140K)], 0.
 0435416 secs]
  ===Finishing System.gc()===
  [GC[0: 1853K-> 1342K(3140K)], 0.0005931 secs]
  [GC[0: 1854K-> 1342K(3140K)], 0.0006395 secs]
  [GC[1: 1644K-> 1341K(3140K)], 0.0480494 secs]
  ===Starting System.gc()===
 [GC[0: 3881K-> 3645K(5448K)][1: 3645K-> 2493K(5448K)], 0.
 0462025 secs]
  ===Finishing System.gc()===
 
 这时候很明显的,会发现,即使不执行 System.gc(); 也会看到 [GC 开
头的字眼,这表示  GC 会随时的监测目前的状况。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752043/viewspace-988019/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10752043/viewspace-988019/

<think>我们正在讨论链接器的垃圾回收(LinkerGarbageCollection)相关的考虑因素和影响。链接器垃圾回收通常指的是在程序链接过程中,移除未被使用的代码和数据,以减少最终生成的可执行文件的大小。###链接器垃圾回收的基本原理在编译和链接过程中,编译器会生成目标文件,其中包含各种函数、变量和数据。链接器负责将这些目标文件合并成一个可执行文件或库。在这个过程中,可能会包含一些实际上未被程序使用的代码和数据(例如,库中的函数被包含但未被调用)。链接器垃圾回收(也称为“死代码消除”或“链接优化”)就是识别并移除这些未被使用的部分。####工作方式1.**起始点设置**:链接器从程序的入口点(如`main`函数)开始,标记所有直接或间接被访问的代码和数据。2.**标记过程**:通过遍历函数调用、数据引用等关系,标记所有可达的代码和数据。3.**清除阶段**:未被标记的部分被视为“垃圾”,在生成最终输出被移除。###考虑因素1.**链接器选项**:不同的链接器有不同的选项来控制垃圾回收。例如:-GNU链接器(ld)使用`--gc-sections`选项。-LLVM链接器(lld)也支持类似的选项。2.**编译器配合**:编译器需要生成必要的段信息(如将每个函数或数据放入独立的段中),以便链接器能够进行垃圾回收。例如,在GCC中,可以使用`-ffunction-sections`和`-fdata-sections`选项。3.**动态初始化的影响**:如果代码中有全局对象的构造函数,这些构造函数可能被编译器放入特定的段(如`.init_array`)。链接器需要能够识别这些段是否被使用。4.**弱符号处理**:弱符号(WeakSymbols)可能会影响垃圾回收,因为链接器在遇到弱符号不会报重复定义的错误,但可能会影响垃圾回收的决策。5.**外部引用**:如果代码中有通过指针动态调用的函数(如通过函数指针或虚函数表),链接器可能无法静态分析这些引用,从而导致误删。此需要显式地告知链接器保留这些函数(例如使用`__attribute__((used))`或`/INCLUDE`指令)。###影响1.**减少输出文件大小**:这是最直接的影响,通过移除未使用的代码和数据,可以显著减小可执行文件或库的大小。2.**提升性能**:较小的代码体积可以改善缓存利用率,从而可能提升运行性能。3.**潜在的运行错误**:如果链接器错误地移除了实际上被动态调用的代码,会导致运行错误(如函数调用失败)。因此,需要确保所有动态调用的函数都被正确保留。4.**构建间**:垃圾回收过程需要额外的分析间,可能会增加链接间。但通常,减少的代码体积也会减少后续优化和写入磁盘的间,所以总体间可能增加也可能减少。###示例:使用GCC和ld进行垃圾回收以下是一个简单的示例,展示如何启用链接器垃圾回收:```bash#编译源文件,将每个函数和数据放入独立的段gcc-c-ffunction-sections-fdata-sectionsmyfile1.cmyfile2.c#链接,并启用垃圾回收ld--gc-sections-omyprogrammyfile1.omyfile2.o```###注意事项-某些情况下,即使函数未被直接调用,也需要保留(例如中断处理函数)。可以通过链接器脚本或特定属性(如`__attribute__((used))`)来保留这些函数。-对于C++程序,全局对象的构造函数和析构函数需要特别注意,确保它们不会被错误移除。###引用在程序优化中,链接器垃圾回收是一个重要步骤,它可以有效减小程序体积[^1]。然而,对于动态特性较多的程序(如使用反射),可能需要额外的处理[^3]。§§相关问题§§1.链接器垃圾回收在哪些情况下可能会错误地移除代码?2.如何确保通过函数指针调用的函数不被链接器垃圾回收移除?3.除了垃圾回收,还有哪些方法可以减少可执行文件的大小?4.在C++程序中,链接器垃圾回收有什么特殊注意事项?</think>### 链接器垃圾回收的考虑因素与影响 #### 基本概念 链接器垃圾回收(Linker Garbage Collection)是编译链接过程中的优化技术,用于消除未使用的代码和数据段。其核心原理是: 1. **依赖分析**:从入口点(如`main()`函数)开始标记所有可达代码/数据 2. **段级消除**:移除未被标记的段(`.text`, `.data`, `.bss`等) 3. **符号解析**:处理弱符号(weak symbols)和外部引用 数学表达:若程序入口点集合为 $E$,可达代码集合 $R$ 满足 $$R = E \cup \left\{ x \mid \exists y \in R, y \rightarrow x \right\}$$ 则最终输出为 $R$ 的闭包。 #### 关键考虑因素 1. **编译器配合要求** - 需开启函数/数据分段编译(如GCC的 `-ffunction-sections -fdata-sections`) - 每个函数/变量独立成段才能被精确移除[^1] 2. **动态代码处理** ```c // 函数指针可能导致误删 void (*fp)() = condition ? funcA : funcB; fp(); // 链接器无法静态分析 ``` 需通过 `__attribute__((used))` 显式保留关键符号 3. **初始化代码陷阱** 全局构造器(`.init_array`)中的间接调用需特殊处理,否则可能导致运行崩溃 4. **调试信息影响** - 启用垃圾回收后调试符号可能不完整 - 建议分离调试信息文件(如DWARF的 `.debug` 段) #### 性能影响 | 指标 | 正向影响 | 负向影响 | |--------------|------------------------|------------------------| | 代码体积 | 减少30%-60%[^2] | - | | 内存占用 | 降低RAM使用 | - | | 链接间 | - | 增加15%-25%分析开销 | | 冷启动速度 | 提升指令缓存命中率 | 段对齐可能增加加载延迟 | #### 实践建议 1. **安全保留策略** ```lds /* 链接脚本示例:强制保留中断向量 */ KEEP(*(.isr_vector)) ``` 2. **动态库特殊处理** - 需保留所有导出符号:`--export-dynamic-symbol` - 避免使用 `-gc-sections` 与 `-Bsymbolic` 同启用 3. **验证方法** ```bash # 检查未定义符号 nm --demangle --undefined-only a.out # 分析段分布 objdump -h a.out | grep -E 'text|data|bss' ``` #### 典型问题排查 1. **运行符号丢失** - 现象:`undefined symbol: vtable for MyClass` - 解决方案:对虚表使用 `__attribute__((retain))` 2. **初始化失败** - 现象:全局构造函数未执行 - 修复:在链接脚本中KEEP `.init_array` 段 3. **跨工具链兼容** - GNU ld:`--gc-sections` - LLVM lld:`--gc-sections` - MSVC:`/OPT:REF`(需配合 `/DEBUG` 保留调试符号) > 引用说明:动态代码路径分析需结合运行特性[^3],静态链接器难以完全处理间接调用依赖关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值