Thursday, October 15, 2009
Saturday, November 15, 2008
Java SE 6 Hotspot [TM] 虚拟机垃圾回收调优
Java SE 6 Hotspot [TM] 虚拟机垃圾回收调优
来源:http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html
王旭 [ gnawux (at) gmail.com ; http://www.wangxu.me ] 于2008年11月译
译者按:有些术语实在很别扭,不过译者文采有限,没法找到合适的中文词汇,不太影响理解,凑合看吧。
1. 概述
Java 平台标准版(Java SE™)被广泛应用于各种应用,从桌面上的小小的 applet 到大型服务器上的 Web Service 无处不在。为了支持各种不同的部署场景,Java HotSpot™ 虚拟机提供了多种垃圾回收器,每种都为满足不同的需求而设定。这是也为了满足大大小小不同应用需求的一部分。不过,那些需要高性能应用的用户、开发者和管理员们也被选择适合他们应用的恰当的垃圾回收器的繁琐困扰着。取消这些额外操作的重要一步是在 J2SE™ 5.0 中作出的:垃圾回收器会根据应用运行的计算机类型而作出选择。
这个垃圾回收器的“更好的选择”总的说是一种进步,不过,这并不意味着对所有的应用这都是最好的选择。对于有极端的性能或其他需求的用户,仍需要显式地指定垃圾回收器,并调优某些参数,以达到满意的性能。本文就为这些需求提供了一些相关信息。首先,本文会基于串行的 stop-the-world 垃圾回收器来介绍垃圾回收器的一般性特征和基本调优开关。接下来会介绍其他垃圾回收器的特点和如何选择一个垃圾回收器。
何时选择垃圾回收器?对于一些应用,这个答案可能是“永远不”。也就是说,在有低频率、短时的垃圾收集器造成的停顿的情况下,大部分程序都运行良好。不过,这并不适用于很多程序,特别是那些处理大量数据(若干GB)、很多线程和需要处理很多事务的情况。
Amdahl 观察到,大部分工作负载并不能被很好的并行化;有部分情况下总是会被顺序执行,无法从并行化中获益。这对 Java™ 平台也是如此。特别的,在 J2SE 1.4 以前,Sun Java 平台的虚拟机并不支持并行垃圾回收,这样,在多处理器系统中,垃圾回收会对并行应用产生严重影响。
下图显示了一个除了垃圾回收以外均为完美可伸缩的理想系统的性能曲线。红色曲线是一个在但处理器系统中会花费 1% 的时间在垃圾回收上的程序。它在 32 处理器的系统中,将损失 20% 的吞吐量。而一个花费 10% 时间在垃圾回收上的应用(不考虑单处理器系统中额外的垃圾回收时间)在系统扩张到 32 处理器系统中时,会损失超过 75% 的吞吐量 。
这意味着在小型开发系统中微不足道的速度问题当扩张到大规模系统中就可能成为严重的性能瓶颈。从另一个角度看,减少这样的性能瓶颈的小改动就可以获得很大的性能收益。对足够大规模的系统,选择合适的垃圾收集器并进行必要调优是绝对值得的。
对于大多数“小”应用(在现代处理器上大约需要100MB堆内存的应用)来说通常是足够的。其他垃圾收集器会带来额外的负载或复杂性,这回让系统的某些行为付出一定的代价。如果一个应用不需要一个垃圾收集器的某个功能。那么就使用串行的垃圾收集器好了。一个不应该使用串行垃圾收集器场景是一个超多线程的大程序运行在一个大型的、有大量内存和两个或多个处理器的系统中。当应用运行在这些服务器级的计算机上的时候,并行垃圾收集器会被缺省选择(参见下面的功效学 )。
本文以 Solaris™ 操作系统(SPARC(R) 平台版本)中的 Java SE 6 作为参考。不过,文中所述的概念和建议适用于所有支持的平台,包括 Linux, Microsoft Windows 和 Solaris 操作系统(x86 平台版本)。此外,文中的命令行参数也对所有平台有效,虽然它们的缺省值在各个平台可能有所不同。
2. 功效学(Ergonomics)
“功效学”是一个 J2SE 5.0 引入的概念。引入功效学概念是为了通过不设置或设置很少的几个命令行参数的情况下提供更好的性能,这些参数包括:
垃圾收集器,
堆尺寸,
和运行时编译器
这里的参数选择假定应用所运行的主机类型和应用的类型一致(也就是说,大型应用运行在大型的机器上)。这些选项简化了垃圾回收的调优。选择并行垃圾回收器,用户可以指定应用的最大中断时间和希望的吞吐量。这和指定堆大小来调优性能是相对应的。最常用的功效学相关的内容在可以参考 “Ergonomics in the 5.0 Java Virtual Machine” 这篇文章。建议在尝试本文提到的细节配置之前尝试该文章中介绍的功效学手段。
本文中的功效学特性被作为并行垃圾回收器的自适应尺寸策略的一部分。这包括指定垃圾回收性能的目标和性能调优的一些附加选项。
3.代
J2SE 平台的优势之一是它将内存分配、垃圾回收这些繁复的细节屏蔽了起来。然而,一旦垃圾回收成为主要的瓶颈,那么理解一下这些隐藏在背后的细节就变得有必要了。垃圾回收器对应用程序对对象的使用方式进行判断,这个判断会反映在可调优参数中,他们可以被调整,以提高性能而不牺牲掉抽象性。
当一个对象不再可能被从其他任何地方访问到的时候就会被认为是垃圾了。最直接的垃圾回收算法就是简单地迭代所有可找到的对象。任何没有被跌带到的对象都可以被认为是垃圾了。这个方法的用时和活着的对象数量成正比,这对于那些维护着大量活数据的程序来说是不可接受的。
从 J2SE 1.2 开始,虚拟机就引入了各种不同的垃圾回收算法,这些算法都使用分代垃圾收集。尽管原生的垃圾回收会检查堆中的所有活着的对象,分代垃圾收集采用了很多观测到的大部分应用程序的经验特征,用来最小化发现废弃的对象的工作量。最重要的经验特征是 weak generational 假设,该假设认为大部分对象都只存活一少段时间。
下图中的蓝色区域是对象生存期的典型分布。横轴是对象被分配后的生存期。纵轴方向计算的字节数是相应生存期的对象的总字节数。左侧的尖峰表明,对象在分配之后不久就被废弃了。比如,迭代器对象常常只会在一个循环中被用到。
当然,有些对象确实活得要长一些,于是,分布曲线延伸到了右边。比如,典型情况下,有些对象在初始化的时候被创建,并一直存活到进程结束。在这两种极限情况之间,那些对象活的时间也是中等的,在图中表现初来的就是从开始的峰值泄漏初来的蓝色区域。有些应用可能会有看起来十分不同的分布曲线,不过绝大多数的进程都是这个常见的形状。大部分对象都会“英年早逝”这个事实让高效的垃圾收集变得具有可能性了。
为了为这样的应用环境优化,内存被按照“代” (generation)进行管理,或者说,内存池中存放不同年龄的对象。当一个年龄断被填满后,就对该代的垃圾进行回收。在内存池中的大部分对象都是年轻的对象(年轻的代),而大部分对象也会在年轻的时候就成为垃圾。当年轻代被填满的时候,会导致一次“小回收”(译注:原文minor,似乎“未成年”更贴切一些,不过咱们读起来会很别扭),这里只有年轻代的对象惠北回收,而其他年龄断的垃圾则不与理会。该回收算法的成本是,一阶情况下,正比于被回收的活的对象的数量;年轻代因为满是死对象,所以回收非常迅速。而在“小回收”中存活下来的对象于是乎就会被转移到所谓的年老代(tenured generation)。最终,当年老代被填满而需要回收的时候,就会导致一次主回收,这时整个堆都会被回收。主回收通常会运行锝比小回收慢很多,因为大量的对象都会被处理。
如上文记述,对不同的应用,“工效学”会动态选择垃圾收集器来提供较好的性能。串行垃圾收集器用于哪些数据量比较小的程序,而且它的缺省参数也让大多数小程序能够高效工作。而大吞吐量垃圾收集器用于那些有中到大数据量的数据集。工效学选择的堆尺寸参数和自适应尺寸策略用于为服务器提供更好的性能。这些选择的大多数而不是所有的情况下工作得很不错。这就引出了本文的核心宗旨:
如果垃圾收集器成为了瓶颈,你可能不得不调整整个堆的大小乃至每个代的尺寸。检查垃圾收集器的详细输出,然后检查垃圾收集器对你关注的各个性能指标的影响。
(并行垃圾收集器之外的)缺省的代排布大概就是这样的。
初始化的时候,最大的地址空间虚拟地保留住而没有分配出去,直到真的需要的时候为止。整个保留的对象地址空间被分给了年轻的和年老的代。
年轻代包括“伊甸园”和两个幸存者空间。大部分对象最初在伊甸园里被分配出来。一个幸存者空间在任意时刻都是空的,作为伊甸园中的活对象的目的地,另一个是用于下一次收集。对象在幸存者空间之间停留到足够老之后,就会被复制到年老代去了。
另一个和年老代有密切关系的代是永久的(permanent)代,这里保存着虚拟机需要的用来描述那些 Java 语言层面没有等价物的对象。比如,那些描述类和方法的的对象就存放在永久代。
3.1 性能考虑
对于垃圾回收的性能,主要有两种量度方法:
吞吐量。吞吐量是在一段足够长的时间中,没有花费在垃圾回收上的时间占总时间的百分比。吞吐量包含了花在空间费配上的时间(不过空间分配速度的调优一般是没有必要的)。
延时。延时是由于等待垃圾回收而导致的程序没有响应的时间。
不同的用户对垃圾收集有不同的需求。比如,对于一个web server而言,吞吐量是合理的量度,因为垃圾收集带来的短时时延是可以容忍的,或者说是很容易就被网络时延所掩盖了。不过,对于交互的图形界面程序而言,极短的停顿都会影响用户的使用体验。
有些用户对其他的因素很敏感。Footprint是一个进程的工作集,由页和cache line来量度。对于内存相对于进程数量很有限的系统而言。Footprint会影响到程序的可伸缩性。Promptness是对象死掉和该块内存重新可用之间的时间间隔的量度,这是分布式系统的一个重要考虑因素,包括远程方法调用(RMI)。
总的说,一个特定的代的尺寸选择是上述这些因素之间的权衡的结果。比如,一个非常大的年轻代的大小可以最大化吞吐律,但会以Footprint、Promptness和延时作为代价。而年轻代延时可以通过缩小该代的大小来达到最小化,但同样会损失吞吐量。近似地,调整一个代的尺寸不会影响到其他代的垃圾收集频率和时延。
没有一个简单的方法来设置代的尺寸。最好的选择由程序使用内存的方式和用户的需求来决定。这样,虚拟机对垃圾收集器的选择并不总是最优的,而且可以通过后面介绍的命令行参数来调整。
3.2 测量
使用应用特定的量度,吞吐量和footprint很容易被测量。例如,web服务器的吞吐量可以使用一个客户端负载生成器来测量,而该服务器的 footprint 则可以在 Solaris 操作系统中使用 pmap 命令来测量。另一方面,垃圾收集导致的时延可以方便地通过监测虚拟机自己的诊断输出来估算出来。
命令行参数 -verbos:gc 可以送出每一次垃圾收集时的堆和垃圾收集信息。比如,这是一个大型服务器应用的输出:
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
这里是两次小回收和之后的一次主回收。箭头前后的数字(比如第一行的325407K->83000K)分别指垃圾回收前后的所有活着的对象占用的空间。在小回收之后,这个尺寸之中仍然包含一些没有被回收的垃圾(死掉的对象)。这些对象要么存在在年老代中,要么被年老或永久代中的对象所引用。
后面的括号中的数字(比如第一行中的 (776768K))是全部提交的堆大小,也就是虚拟己不向操作系统申请内存的情况下,全部 java 对象可用的存储空间。注意,这个数字不包括幸存者空间中的一个,因为幸存者空间在一个给定时间只有一个可用,同时也不包括永久代的空间,这里面是虚拟机使用的元数据。
最后一个数字(比如 0.2300771 secs)是垃圾收集所用的时间;这个例子里大约是四分之一秒。
第三行中主垃圾回收的格式也是类似的。
-verbos:gc 输出的格式可能在将来的版本里有所改变。
通过-XX:+PrintGCDetails参数可以查看更多垃圾回收相关的信息。下面是串行垃圾收集器使用该参数打印出来的信息。
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]
这个信息显示,这次小回收收回了 98% 的 DefNew 年轻代的数据,64575K->959K(64576K) 并在其上消耗了 0.0457646(大约45毫秒)。
secs
整个堆的占用率下降了大约51% 196016K->133633K(261184K),而且通过最终的时间 0.0459067 secs 显示在垃圾收集中有轻微的开销(在年轻代之外的时间)。
选项-XX:+PrintGCTimeStamps会提供每次回收开始时间的时间戳。这对于查看垃圾回收频率非常有用。
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505
secs] 111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]如上,垃圾回收在程序运行后111秒开始。小回收同时启动。信息中还显示了主回收中的年老代的垃圾回收信息。年老代的空间使用率下降了大约 10% 18154K->2311K(24576K)
,用时 0.1290354(大约130毫秒)。
和
-verbose:gc 一样,-XX:+PrintGCDetails 的输出格式在将来的版本里也可能会有所变动。4. 代的尺寸
很多参数会应想到代的尺寸。下图是堆中的提交空间和虚拟空间的差别。虚拟机初始化的时候,整个堆空间都是保留的。保留空间可以通过参数-Xmx 指定。如果-Xms参数小于-Xmx参数,那么不是所有的保留空间都会立刻提交到虚拟机之中。未提交的空间在途中标记为 virtual。堆的不同部分(永久时间段、年老时间段和年轻时间段)可以按需生长到虚拟空间的限制为止。一些参数可以调整堆的不同部分的比例,比如参数NewRatio指定年老代对年轻代的比例。这些参数将在下面讨论。
4.1 全部堆
注意,下面的关于堆的生长、收缩和缺省堆大小都不适用于并行垃圾收集器,并行垃圾收集器请参考相关章节。不过,用于控制整个堆大小和代尺寸的参数对并行垃圾收集器都是适用的。因为垃圾收集是发生在代被填满的时候,所以,吞吐量反比于可用此内存数量。总可用内存数是影响垃圾收集性能的最重要因素。
缺省情况下,虚拟己在每次垃圾收集后增加或减少堆尺寸,来尽量保持可用空间对活的对象之间的比例在一个区间之内。这个目标区间通过参数-XX:MinHeapFreeRatio=<minimum>和-XX:MaxHeapFreeRatio=<maximum>来设置,而总的堆大小的界限由-Xms<min>和-Xmx<max>来确定。这些参数在 32 位 Solaris 系统(SPARC 平台版本)中的缺省值如下表所示:
Parameter | Default Value |
|---|---|
MinHeapFreeRatio | 40 |
MaxHeapFreeRatio | 70 |
-Xms | 3670k |
-Xmx | 64m |
64位系统中的堆尺寸的参数会大 30% 左右,这个增长用来补偿64位系统中更大的对象所带来的开销。
通过设置这些参数,当一个代的可用空间低于 40%,虚拟机就会把可用内存扩展到 40%,直到代的最大尺寸。同样的,如果可用空间超过 70%,代就会被缩小,使得只有 70% 可用空间,直到达到代最小的空间为止。
除非遇到了时延问题,给虚拟机尽量多的内存。缺省尺寸(64MB)通常都太小了。
把-Xms 和 -Xmx 设置成相同的值,把最重要的尺寸决定从虚拟机收回来,从而增强可预见性。- 一般地,随着处理器数量的增加而增加内存,因为内存分配可以被并行化。
4.2 年轻代
影响位居次席的是用于年轻代的堆比例。年轻代越大,小回收的次数也就越少。不过,在一定的堆大小的情况下,年轻代越大,年老代也就越小,这就增加了主回收的频率。最佳选择依赖于应用中分配的对象的生存期分布。
缺省的,年轻代的尺寸由 NewRatio 控制。比如,设置-XX:NewRatio=3意味着年轻代和年老代的比例是1:3。换句话说, eden 和幸存者空间的总和是整个堆大小的四分之一。
参数 NewSize 和 MaxNewSize 约束了年轻代的上下界限。可以把这两个参数设成相同的值来固定年轻代的大小,设置 -Xms 和 -Xmx 一样来设置堆大小为固定值。这样可以比使用NewRatio更细粒度地调整年轻代的大小。
4.2.1 幸存者空间
如果需要,SurvivorRatio 可以用来调整幸存者空间的大小,不过这对于性能一般影响不大。比如,-XX:SurvivorRatio=6 会设置幸存者空间和eden的比例是 1:6。换句话说,每个幸存者空间将是 eden 的六分之一,是整个年轻代空间的八分之一(不是七分之一,因为一共有两个幸存者空间)。
-XX:+PrintTenuringDistribution 可以显示这个门限和年轻代中对象的年龄。这对于观测应用中对象的生存期分布也是有用的。下面是 SPARC 上的 32 位 Solaris 的各个参数的缺省值,在其他平台上可能有所差异。
Default Value | ||
|---|---|---|
Parameter | Client JVM | Server JVM |
NewRatio | 8 | 2 |
NewSize | 2228K | 2228K |
MaxNewSize | not limited | not limited |
SurvivorRatio | 32 | 32 |
年轻代的最大尺寸通过最大堆尺寸和 NewRatio 计算而得。所谓的“无限制”的缺省值是说这个计算的值不会受到 MaxNewSize 的约束,除非命令行中指定了这个值。
服务应用的设置准则是:
- 首先确定可以提供给虚拟己的最大堆尺寸。然后根据性能需求来确定年轻代的尺寸,来找到最佳设置。
- 注意:最大堆尺寸一定要小于系统中的内存数量,以防止过多的缺页错误和换页。
- 注意:最大堆尺寸一定要小于系统中的内存数量,以防止过多的缺页错误和换页。
- 如果总的堆尺寸是确定的,增加年轻代的尺寸就会减少年老代的尺寸。一定要保证年老代的尺寸,使之可以容纳所有在应用全程都要用到的活对象,并留有一定裕量(10-20%或更多)。
- 依照上述年老代的约束:
- 给年轻代分配足够的内存。
- 如果有多个处理器,那么分配更多的内存给年轻代,因为内存分配可以并行化。
- 给年轻代分配足够的内存。
5. 可用的垃圾收集器
到目前为止,我们讨论的还都是串行垃圾收集器。不过 Java HotSpot 虚拟机一共支持了三种不同的收集器,每种提供不同的性能特性。- 串行垃圾收集器使用单线程进行所有垃圾收集工作,因为没有线程间通信的开销,串行垃圾收集器相当高效。串行垃圾收集器最适合于单处理器系统,因为它不会从多处理器硬件中获益,尽管在小数据量的应用中(不大于100MB的),它对于多处理器系统也是游泳的。串行垃圾收集器在一定的硬件和操作系统的配置时会缺省使用,也可以显式地用
-XX:+UseSerialGC参数来指定。 - 并行垃圾收集器(或吞吐垃圾收集器)并行进行小垃圾收集,这会显著减少垃圾收集的的开销。它适用于中等或大尺寸数据的运行在多处理器或多线程硬件上的应用。并行垃圾收集器也会在一定的硬件和操作系统配置下被缺省使用,同时,也可以使用
-XX:+UseParallelGC参数来指定。- 更新:“并行压缩”是 J2SE 5.0 update 6 以上版本的新特性,并在 Java SE 6 之中得到加强,该特性允许主回收也并行收集。如果不使用并行压缩,主回收仍然会单线程运行,这会严重限制系统的可伸缩性。并行压缩可以使用命令行参数
-XX:+UseParallelOldGC来打开。
- 更新:“并行压缩”是 J2SE 5.0 update 6 以上版本的新特性,并在 Java SE 6 之中得到加强,该特性允许主回收也并行收集。如果不使用并行压缩,主回收仍然会单线程运行,这会严重限制系统的可伸缩性。并行压缩可以使用命令行参数
- 并发垃圾收集器并发地进行大部分垃圾收集工作(也就是在应用运行当中进行)来尽可能煎炒垃圾收集带来的应用停顿。它是为哪些拥有中到大量数据的、对响应时间要求高于吞吐量要求的应用,因为最小化时延的技术会让吞吐能力付出代价。并发垃圾收集器通过
-XX:+UseConcMarkSweepGC参数来启用。
5.1 选择垃圾收集器
除非你的应用有非常严酷的时延要求,那么就运行你的应用,并让系统自己选择垃圾收集器好了。如果有必要的话,就调整堆的大小来增进性能。如果性能仍然无法达到你的目标,那就按照如下设置来选择一个垃圾收集器。- 如果应用的数据很少(大约不超过100MB),那么
使用-XX:+UseSerialGC选择串行垃圾收集器。
如果应用运行在单处理器系统中,并且没有什么时延要求,那么- 让虚拟机选择垃圾收集器,或者
使用-XX:+UseSerialGC选择串行垃圾收集器。
- 如果(a)程序峰值性能是第一位的,并且(b)没有时延要求,或时延要求是一两秒或更长,那么
让虚拟机选择垃圾收集器,或者- 使用
-XX:+UseParallelGC选择并行垃圾收集器,乃至(可选)通过-XX:+UseParallelOldGC启用并行压缩。
- 如果响应时间比总体吞吐量更为重要,并且垃圾收集时延需要控制在1秒以内,那么
select the concurrent collector with
-XX:+UseConcMarkSweepGC. If only one or two
processors are available, consider using
incremental mode,
described below.
- 通过
-XX:+UseConcMarkSweepGC 参数启用并发垃圾收集器。进当你有一个或两个处理器可用的时候,考虑使用下文将要介绍的“增量模式”。
这些指导意见仅仅是选择垃圾收集器的起点,因为性能依赖于堆的尺寸、应用中活数据的数量,以及处理器的数量和速度。时延参数对这些因素尤为敏感,所以,所谓的1秒门限值只是个大致数值:在很多硬件和数据量的组合情况下,并行垃圾收集器可能会导致停顿时间超过1秒;同样,在某些组合下,并发垃圾收集器也不能保证停顿小于1秒。
如果推荐的垃圾收集器没有达到期望的性能,首先应该尝试堆和代的尺寸,以期达到目标。如果仍然不成功的话,尝试更换一个垃圾收集器:使用并发垃圾收集器来减少停顿时间,使用并行垃圾收集器来增加多处理器系统中的吞吐量。
6. 并行垃圾收集器
并行垃圾收集器(也被称为吞吐量收集器)和串行收集器类似,也是一种分代垃圾收集器;其最大的不同在于它使用了多线程来加快垃圾收集的过程。并行垃圾收集器可以通过参数 -XX:+UseParallelGC 指定。缺省的,只有小回收会并行运行,主回收仍然单线程运行。不过,通过参数-XX:+UseParallelOldGC启动并行压缩可以让主回收和小回收都并行运行,从而进一步减少垃圾收集开销。
在一个有N个处理器的计算机上,并行垃圾收集器使用N个垃圾收集器线程。不过,这个数量可以在命令行参数里指定(参见下文)。在一台单处理器的计算机上,由于线程开销(比如同步),并行垃圾收集器的性能应该不如串行垃圾收集器。然而,当应用程序有中等或大尺寸的堆的时候,它在一个双处理器的机器上就会略优于串行垃圾收集器,而如果有多于两个处理器的话,它就能远胜于串行垃圾收集器。
垃圾收集器线程数的多少可以用-XX:ParallelGCThreads=<N>参数来控制。如果要使用命令行参数显式调整了堆的尺寸,使用并行垃圾收集器的情况下需要的堆的尺寸和使用串行垃圾收集器情况下的堆的尺寸是一阶相等的。使用并行垃圾收集器仅仅是让小回收造成的停顿更短一些。因为有多个垃圾收集器线程参与小回收的过程,有极少的可能性可能会在将年轻代移动到年老代的过程中造成一些碎片。每个垃圾收集线程都有一块专属的年老代的空间,用于年轻代向年老代的移动,将年老代的可用空间划分为“移动缓冲”(promotion buffer)的过程可能会造成一定的碎片效应。减少垃圾收集器线程的数量可以减少碎片、增加年老代的空间。
6.1 代
正如上面提到的,并行垃圾收集器的代的排布方式和串行垃圾收集器略有不同。其分布如下图所示。6.2 功效学
自 J2SE 5.0 以来,并行垃圾收集器成为了server级机器的缺省垃圾收集器,详细资料可以参考“Garbage CollectorErgonomics”。此外,并行垃圾收集器使用一种自动调整机制来指定期望的行为而不是指定代的大小和其他底层调整细节。这些行为包括:
- 最大垃圾收集停顿时间
- 吞吐量
- Footprint (也就是堆尺寸)
最大停顿时间的目标由参数-XX:MaxGCPauseMillis=<N>来指定。这个参数被解释为指定停顿时间不得大于<N>毫秒;缺省情况下没有最大停顿时间目标。如果指定了一个停顿时间目标,堆尺寸和其他垃圾回收相关参数就会被相应调整,以便保持垃圾回收时间小于指定的值。注意,这些调整可能会导致总体吞吐量的降低,而且,在某些情况下,要求的停顿时间目标可能无法达到。
吞吐量目标测量垃圾回收时间和非垃圾回收时间(也就是应用时间)的比例。这个目标时间可以用命令行参数-XX:GCTimeRatio=<N>来指定,这样,垃圾回收时间和应用时间的比例将是1 / (1 + <N>)。例如,-XX:GCTimeRatio=19设置1/20活5%的时间用于垃圾回收。缺省值是99,目标是1%的时间用于垃圾回收。
最大堆footprint使用已经存在的 -Xmx<N> 参数。此外,如果没有其他的优化目标的话,垃圾收集器有一个隐式的最小化堆尺寸的目标。
目标的优先级顺序如下:
- 最大停顿时间目标
- 吞吐量目标
- 最小堆尺寸目标
最大停顿时间目标会被首先满足。仅当最大停顿目标被满足的情况下,才会去满足吞吐量目标。类似的,仅当前两个目标都会满足的情况下,才会考虑去满足footprint目标。
6.2.2 时间段尺寸调整
每次垃圾收集结束的时候,垃圾收集器都会更新其保存的平均停顿时间之类的统计参量。同时它会检查各个目标是否被满足了,是否有调整代尺寸的需要。这之中的意外情况就是显式的垃圾收集(比如调用 System.gc())会在统计和调整判断中被忽略掉。
增加和缩小一个代的大小是通过增加活缩小一个固定的百分比来达到的,这样一个代要分步来达到需要的尺寸。增加活所见是以不同的比率来进行的。缺省情况下,一次增加 20% 活减少 5%。年轻代和年老代增量的比例分别通过命令行参数 -XX:YoungGenerationSizeIncrement=<Y>和-XX:TenuredGenerationSizeIncrement=<T>来设定。而缩小比例的要通过-XX:AdaptiveSizeDecrementScaleFactor=<D>参数来设定。如果增量是X%,那么每次减小量就是(X/D)%。
如果垃圾收集器决定在启动的时候增加一个代的大小,会有一个额外的百分比的增量。这个附加的增量随着收集的次数而减少,不会长期影响。这个额外增量意在提高启动速度。缩小代的尺寸是没有这个额外的增量。
如果最大停顿时间目标没有达到,会有且仅有一个代的大小被缩小。如果两个代都在目标之上,停顿时间较大的那个代会首先被缩小。
如果总体吞吐量目标没有达到,那么两个代的大小都会增加。每个都按照各自对垃圾回收时间的贡献比例分别增加。比如,如果年轻代的垃圾回收时间占去了25%的总垃圾回收时间,并且年轻代的全部增量应该是20%,那么这时它的增量就是5%。
6.2.3 缺省堆尺寸
如果没有在命令行中进行设置,初始和最大堆尺寸会通过计算机内存计算而得。如下表所示,对大小占用的内存的比例是由参数DefaultInitialRAMFraction和DefaultMaxRAMFraction来控制的。(表中的 memory 代表计算机的系统内存数量。)
| Formula | Default | |
|---|---|---|
initial heap size | memory / DefaultInitialRAMFraction | memory / 64 |
maximum heap size | MIN(memory / DefaultMaxRAMFraction, 1GB) | MIN(memory / 4, 1GB) |
注意,缺省的最大堆尺寸不会超过1GB,不论系统中到底有多少内存。
6.3 过多的GC时间和OutOfMemory错误
当有过多的时间花费在垃圾收集上的时候,并行垃圾收集器会跑出 OutOfMemoryError 错误:如果超过 98% 的时间花费在垃圾收集上并且只有 2% 的堆被释放的话,就会抛出一个 OutOfMemory。这个功能是用来防止堆太小导致程序长时间无法正常工作而设计的。如果必要,这个功能可以使用命令行参数-XX:-UseGCOverheadLimit来关闭。6.4 测量
并行垃圾收集器的垃圾收集器详细输出和串行垃圾收集器是一样的。7. 并发垃圾收集器
并发垃圾收集器适用于那些需要更短的垃圾收集停顿,并能为此付出程序运行期处理器资源的应用。典型情况下,那些拥有较多长期存在的对象(年老代比较大),并且运行在拥有两个活更多处理器的应用可能会因此获益。不过,在任何要求很低停顿时间的应用都应该考虑这个垃圾收集器;比如,拥有较小年老代的交互程序在但处理器上使用并发垃圾收集器就可以收到明显的好处,特别是使用增量模式的时候。并发垃圾收集器可以通过命令行参数-XX:+UseConcMarkSweepGC来启动。
并发垃圾收集器的基本算法在技术报告
A Generational
Mostly-concurrent Garbage Collector里有介绍。主义,实际的实现细节在不同版本里手有细微的变化的,因为垃圾收集器也在一直进步。
7.1 并发性的开销
并发垃圾收集器的短主回收停顿时间是以处理器资源作为代价的(这些资源如果不用在收集器上肯定就要用在应用上了)。最明显的开销就是并发地使用了一个或多个处理器资源。在N处理器系统中,垃圾收集的并发部分会使用K/N的可用处理器,其中 1<=K<=ceiling{N/4}。(注意,K值的上限将来可能会有变化。)并发垃圾收集器不仅在并发阶段使用处理器,还引入了其他的开销。所以,尽管并发垃圾收集器显著减少了程序的停顿,但和其他垃圾收集器相比,应用的总体吞吐量会受到轻微的影响。
因为在并发阶段至少有一个处理器用于了垃圾收集,所以在单处理器(单核)系统中,并发垃圾收集器一般不会带来什么好处。不过,并发垃圾收集有一个分离模式可以在单处理器或双处理器系统中显著减少停顿时间;后面的增量模式中将会进一步介绍其细节。
7.2 并发模式失败
并发垃圾收集器使用一个或多个垃圾收集线程在应用线程执行的同时运行,从而在年老代和永久代变满之前就完成垃圾收集。如前文所述,在一般的操作中,并发垃圾收集器的大部分跟踪与清理工作是在程序运行的同时进行的,所以,程序线程只有极少的停顿。但是,如果并发垃圾收集器在年老代变满的时候仍没有完成垃圾清除工作,或是年老代中的可用空间无法满足一次分配操作的需要的时候,应用就不得不被暂停下来以等待应用线程结束了。这种无法并发地完成垃圾收集的情况被称为“并发模式失败”,这就需要对并发垃圾收集器的参数进行调整了。7.3 过多的GC时间和OutOfMemory错误
并发垃圾收集器会在垃圾收集消耗时间过多的时候抛出 OutOfMemoryError 错误:如果多于 98% 的时间被花费在了垃圾手机上,并且仅有少于 2% 的堆被回收的话,就会抛出 OutOfMemoryError。这个功能是用来防止堆太小导致程序长时间无法正常工作而设计的。如果必要,这个功能可以使用命令行参数
-XX:-UseGCOverheadLimit来关闭。这个策略和并行垃圾收集器是基本一致的,惟一的区别就是并发的垃圾收集时间并未计算在内。也就是说,只有哪些程序停顿下来进行垃圾收集的时间才被计算在内了。这些垃圾收集常常是由于并发模式失败或是显式垃圾收集请求(如调用 System.gc())导致的。
7.4 浮动垃圾
并发垃圾收集器与 HotSpot 中的其他垃圾收集器一样,是一种识别至少所有在堆中可以被访问到的对象的跟踪收集器。按照Jones and Lins的说法,是一种增量更新(Incremental Update)垃圾收集器。因为应用现成和垃圾收集器线程在主回收过程中并发执行,那么那些垃圾收集器跟踪的对象就可能在垃圾收集完成之后变成垃圾这些无法访问却还没有被回收的对象被称为浮动垃圾(floating garbage)。浮动垃圾的数量取决于垃圾收集周期的长度和程序中引用更新的频率,也被称为转化率(mutation)。而且,另一个原因是年轻代和年老代的收集是独立的,彼此都是对方的根。一个粗略的配置规则是为年老代的浮动垃圾多预留出20%的空间来。一个垃圾回收周期中的堆中的浮动垃圾会在下一个垃圾回收周期中被回收。7.5 时延(停顿)
并发垃圾收集器在一个并发回收周期中会两次暂停应用。第一次会从根从根(比如从对象线程栈和寄存器、静态对象等的引用)和堆的其他部分(如年轻代)开始标记所有直接可达的活的对象。第一次停顿被称为“初始标记停顿”(initial mark pause)。第二次停顿发生在并发跟踪阶段末尾,用来发现由于在垃圾收集线程跟踪完一个对象之后又被应用线程更新了其引用而没有被并发跟踪到的对象。这次停顿被称为“重标记停顿”(remark pause)。7.6 并发阶段
可达对象的并发跟踪图发生在初始标记停顿和重标记停顿之间。在并发跟踪阶段中,一个或多个并发垃圾收集器线程会使用那些本来可能会被应用使用的处理器资源,所以尽管不会停顿,计算密集型应用可能会在此阶段和其他并发阶段受到相当的吞吐量损失。在重标记停顿之后,还有一个并发清理阶段,会收集所有标记为不可达的对象。一旦手机周期结束了,并发收集器就会进入等待阶段,这时就基本不会消耗任何计算资源了,直到下一个主回收周期开始为止。7.7 开始并发收集周期
在串行收集器中,每当年老代满了的时候都会引发一次主回收,所有应用现成都会在主回收期间暂停运行。并发垃圾收集器与之不同,它需要在足够早的时间开始垃圾收集,以便能在年老代变满之前完成垃圾收集;否则的话就会因为并发模式失败而导致较长的时延。有很多种条件可以触发并发垃圾收集器启动。基于最近的历史记录,并发垃圾收集器维护了一个年老代变满的预期剩余时间和一个垃圾收集周期的预期时间。基于这些动态估计,并发垃圾收集周期会以让垃圾收集周期在年老代变满之前完成为目标开始并发垃圾收集周期。因为并发模式失败的代价非常惨重,这些估值都流出了安全裕量。
并发垃圾收集在年老代的已用百分比超出了一个初始占有率值(initiating occupancy)的时候也会启动。这个初始占有率阈值的缺省值大约是 92%,不过这个值可能在不同版本中略有不同。它也可以通过命令行参数-XX:CMSInitiatingOccupancyFraction=<N> 来手工设置,其中N是一个0-100的整数,代表年老代的占用百分比。
7.8 调度中断
年轻代和年老代的垃圾收集的停顿发生彼此间是独立的。他们不会重合,但可能会连续发生,这样也就让一个垃圾收集的停顿连上下一个垃圾收集的停顿了,从外界来看就是一个长停顿了。为了避免这种情况,并发垃圾收集器会调度重标记停顿的时间,使之发生在前后两个年轻代停顿之间。这个调度目前还不应用于初始标记停顿,因为它通常会比重标记停顿短很多。7.9 增量模式
并发垃圾收集器可以在这样一种模式下工作:并发阶段以增量的方式进行。回忆一下,在并发阶段,垃圾回收线程会使用一个或多个处理器。所谓增量模式是指减少长并发阶段的影响,周期性中断并发阶段,将处理器资源还给应用程序。这种模式又称为“i-cms”,将垃圾收集器的并发工作划分到小块时间,在年轻代垃圾收集之间进行。这个功能对于那些工作在没那么多处理器的机器上(1或2个处理器的)需要并发垃圾收集器的低时延应用非常有用。
并发垃圾收集周期通常包括如下几步:
停止所有的应用线程,标记从根开始可达的对象集,然后继续所有的应用线程- 在应用线程运行的同时,使用一个或更多的处理器,并发跟踪可达的对象图
- 使用一个处理器,并发跟踪对象图中在上一步开始之后的各个改动的部分
- 停止所有的应用线程,重新跟踪根和对象图中自从上次检查开始发生了变化的部分,然后继续运行线程
- 使用一个处理器,并发地把不可达对象清理到用于分配空间的 free list 上面去。
- 使用一个处理器并发地调整堆的大小,准备下一个回收周期所需的数据结构
正常情况下,并发垃圾收集器在并发跟踪阶段使用一个或多个处理器,不会让出它们。类似的,在清理阶段也会始终独占地使用一个处理器。这对于对于一个程序的响应时间可能是个不小的影响,特别是系统中只有一两个CPU的时候。增量模式通过将并发阶段分解为一系列的突发行为来降低这一影响,这些突发行为会散布在小回收之间。
7.9.1 命令行参数
下面是控制 i-cms的命令行参数(参考下文的初始设置建议):
| 参数 | 描述 | 缺省值 | |
|---|---|---|---|
| J2SE 5.0 及以前 | Java SE 6 及以后 | ||
| -XX:+CMSIncrementalMode | 启动增量模式。注意,并发垃圾收集器必须也被选择(-XX:+UseConcMarkSweepGC) ,否则此参数无效。 | disabled | disabled |
| -XX:+CMSIncrementalPacing | 打开自动步长,这样,增量模式占空比将根据JVM统计到的信息自动调整。 | disabled | enabled |
| -XX:CMSIncrementalDutyCycle=<N> | 两次小回收之间的允许并发收集器运行的时间的百分比(0-100)。如果打开自动步长,那么这个值就是初始值。 | 50 | 10 |
| -XX:CMSIncrementalDutyCycleMin=<N> | 自动步长打开后,占空比值的下限 (0-100)。 | 10 | 0 |
| -XX:CMSIncrementalSafetyFactor=<N> | 计算占空比值时使用的一个裕量(0-100) | 10 | 10 |
| -XX:CMSIncrementalOffset=<N> | 在小回收之间,增量模式中占空比开始的时间,或说是向右的平移量(0-100) | 0 | 0 |
| -XX:CMSExpAvgFactor=<N> | 当进行并发回收统计,计算指数平均值时,当前采样所用的权值(0-100) | 25 | 25 |
7.9.2 建议参数
要在 Java SE 6 里使用 i-cms,需要使用如下命令行参数
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \前两个参数分别启动并发垃圾收集器和 i-cms。后两个参数不是必须的,它们只是要求垃圾收集器将诊断信息打印到标准输出,这样,垃圾收集器的行为就可以被看到并用于以后分析了。
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
注意,对于 J2SE 5.0 和之前的版本,我们建议 i-cms 使用如下的初始命令行参数:
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps \
-XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10
这样,就是用了和 Java SE 6 一致的参数了,多出的三个参数用于自动调整占空比。这些多余的参数值完全是使用的 Java SE 6 的缺省值。
7.9.3 基本问题处理
i-cms 的自动占空比计算模式使用了程序运行时收集到的统计信息进行占空比计算,以保证并发垃圾收集器可以在堆占满之前完成。不过,使用过去的行为预测将来的变化的估计方式可能并不总是足够准确,可能在某些情况下无法阻止堆用满。如果需要收集的垃圾太多,可以尝试下面这些步骤,一次使用一个:
| Step | Options |
|---|---|
1. 增加保险系数 | -XX:CMSIncrementalSafetyFactor=<N> |
2. 增加最小占空比 | -XX:CMSIncrementalDutyCycleMin=<N> |
3. 关闭自动占空比计算,使用固定占空比 | -XX:-CMSIncrementalPacing -XX:CMSIncrementalDutyCycle=<N> |
7.10 测量
下面是使用-verbose:gc和-XX:+PrintGCDetails参数时,并发垃圾收集器的输出,一些小细节已经被去掉了。注意,并发垃圾收集器的输出里掺杂着小回收的输出;典型情况下,很多小回收会发生在并发收集周期之中。其中的CMS-initial-mark表征了一个并发垃圾回收周期的开始。CMS-concurrent-mark:
标志着并发标记阶段的完成,而CMS-concurrent-sweep则标志着并发清除阶段的完成。之前没有提到过的预清除阶段以CMS-concurrent-preclean为标志。预清除可以和重标记阶段CMS-remark的准备工作同时运行。最后一个阶段是CMS-concurrent-reset,这是下一个并发收集周期的准备工作。
[GC [1 CMS-initial-mark: 13991K(20288K)] 14103K(22400K), 0.0023781 secs]
[GC [DefNew: 2112K->64K(2112K), 0.0837052 secs] 16103K->15476K(22400K), 0.0838519 secs]
...
[GC [DefNew: 2077K->63K(2112K), 0.0126205 secs] 17552K->15855K(22400K), 0.0127482 secs]
[CMS-concurrent-mark: 0.267/0.374 secs]
[GC [DefNew: 2111K->64K(2112K), 0.0190851 secs] 17903K->16154K(22400K), 0.0191903 secs]
[CMS-concurrent-preclean: 0.044/0.064 secs]
[GC [1 CMS-remark: 16090K(20288K)] 17242K(22400K), 0.0210460 secs]
[GC [DefNew: 2112K->63K(2112K), 0.0716116 secs] 18177K->17382K(22400K), 0.0718204 secs]
[GC [DefNew: 2111K->63K(2112K), 0.0830392 secs] 19363K->18757K(22400K), 0.0832943 secs]
...
[GC [DefNew: 2111K->0K(2112K), 0.0035190 secs] 17527K->15479K(22400K), 0.0036052 secs]
[CMS-concurrent-sweep: 0.291/0.662 secs]
[GC [DefNew: 2048K->0K(2112K), 0.0013347 secs] 17527K->15479K(27912K), 0.0014231 secs]
[CMS-concurrent-reset: 0.016/0.016 secs]
[GC [DefNew: 2048K->1K(2112K), 0.0013936 secs] 17527K->15479K(27912K), 0.0014814 secs]
初始标记停顿在典型情况下比小回收的停顿时间还要小。而如上例所示,并发阶段(并发标记、并发预清除和并发清除)通常会比小回收长很多。不过注意,应用并没有在这些并发阶段中停顿下来。重标记停顿通常和一个小回收的长度相当。重标记停顿挥手道应用的某些特征(如高对象修改频率可能会增加这个停顿)和上一次小回收的时间(即,更多的年轻代对象可能会增加这个停顿)的影响。
8. 其他考虑
8.1 永久代尺寸
在大部分应用中,永久代对于垃圾回收性能没有显著的影响。不过,一些应用会动态的生成与加载很多类;比如,一些 JavaServer Pages(JSP)页面的实现。这些应用可能需要很大的永久代去存放一些多余的类。如果这样的话,最大永久代的尺寸可以用命令行参数-XX:MaxPermSize=<N>来增大。
8.2 Finalization; Weak, Soft and Phantom References
一些应用使用 finalization 和 weak, soft, phantom 引用与垃圾收集器交互。这些特征可以 Java 语言层带来性能影响。一个例子是通过 finalization 来关闭文件描述符,这会导致一个外部资源依赖于垃圾收集器。以来垃圾收集器来管理内存之外的资源是个坏主意。参考资料章节中的文章深度讨论了一些finalization的常见错误和用来避免这些错误的技术。
8.3 显式垃圾回收
应用程序和垃圾回收器的另一个交互途径是显式调用 System.gc() 进行完整的垃圾回收。这回强制进行一次主回收,即使没有必要(也就是说一次小回收可能就足够了),所以应该避免这种情况。显式垃圾回收对性能的影响可以通过使用 -XX:+DisableExplicitGC 进行比较来进行测量,这样虚拟机会无视 System.gc() 的。java -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 ...
这里指定了垃圾回收每小时运行一次,而不是缺省的每分钟一次。不过,这可能会导致某些对象的清除消耗太长时间。这些参数可以被设置到高达
Long.MAX_VALUE来让显式垃圾回收的间隔时间无限长,如果没有合适的DGC上限时间的话。8.4 Soft References
Soft reference在虚拟机中比在客户集中存活的更长一些。其清除频率可以用命令行参数
-XX:SoftRefLRUPolicyMSPerMB=<N>来控制,这可以指定每兆堆空闲空间的 soft reference 保持存活(一旦它不强可达了)的毫秒数,这意味着每兆堆中的空闲空间中的 soft reference 会(在最后一个强引用被回收之后)存活1秒钟。注意,这是一个近似的值,因为 soft reference 只会在垃圾回收时才会被清除,而垃圾回收并不总在发生。
8.5 Solaris 8 替换 libthread
The Solaris 8 Operating System supports an alternate version of the threading
library, libthread, that binds threads to light-weight processes (LWPs)
directly. Some applications can benefit greatly from the use of this alternate
libthread and it is a potential benefit for any threaded application. The
following commands will load the alternate libthread for java (Bourne shell
syntax is shown):
Solaris 8 操作系统提供了一个替代的线程库,libthread, 它将线程直接绑定成了轻量级进程(LWP)。有些应用能够从中极大获益,并潜在的对所有多线程应用都或多或少的有好处。下面的命令会为 java 启用替换的 libthread(BASH 格式)
LD_PRELOAD=/usr/lib/lwp/libthread.so.1
export LD_PRELOAD
java ...
这个方法仅对 Solaris 8 适用,因为对 Solaris 9 操作系统来说,这是缺省的,而 Solaris 10 中,这是惟一的线程库。
9. 相关资源
HotSpot VM Frequently
Asked Questions (FAQ)
GC output
examples 介绍了如何解释不同垃圾收集器的输出。
How to Handle
Java Finalization's Memory-Retention Issues 介绍了一些容易犯的错误和避免他们的方法。
Richard Jones and Rafael Lins, Garbage Collection: Algorithms
for Automated Dynamic Memory Management, Wiley and Sons
(1996), ISBN 0-471-94148-4
在本网站中,名词“Java Virtual Machine” 和“JVM” 都代表 Java 平台虚拟机。
Sunday, July 01, 2007
(内容并不超出 aptitude 文档的范围,这里用以提示给没有阅读完整文档的用户)
在使用 aptitude 安装、删除、升级软件包的时候,有时会遇到依赖关系问题,通常可以利用 aptitude 的自动解决功能,但有的时候,我们希望能手工选择一下如何处理依赖关系。
当在软件包界面进行选择时,在出现依赖关系问题时,会在界面下方得到红色提示,这时,按 e ,会得到依赖关系解决方案界面,里面列出了一个解决方案,包含安装、升级、降级、保持、删除若干个软件包,上下移动光标,会发现下方的区域会列出每个软件包可以用来解决该问题的几种方案。
这时可以用 ``.'' 和 ``,'' 前后翻阅不同的解决方案,翻到哪,再按 g 就会执行哪个方案了。
当然,我们还可以更精确的选择,解决方案界面,对哪个软件包按 a 就表示接受对这个软件包的这种处理方式,这样,就极大地限制了备选方案的数量,同理,对软件包按 r 就可以拒绝一个软件包的处理方式。结合 a/r/./, 可以更快地找到合适的解决方案。
Sunday, June 17, 2007
[近期译文] GIT 简要教程
git 简要教程 (适用于 1.5.1 或更新版本)
原文链接: http://www.kernel.org/pub/software/scm/git/docs/tutorial.html
这个教程将介绍如何将一个新的项目导入到 git 之中,如何修改项目并如何将这些变更与其他开发者分享。
如果你更感兴趣如何用 git 取出一个项目,比如,测试软件的最新版本,你可能更应该看看 The Git User's Manual 的前两章。
首先,记住你可以用 man 来获取 git 的文档,比如 "git diff" 的文档可以用如下命令察看:
$ man git-diff
在做任何改动之前,最好把自己的名字和 email 地址介绍给大家,最简单的方法就是:
$ git config --global user.name "Your Name Comes Here"
$ git config --global user.email you@yourdomain.example.com
导入一个新项目
假设你有一个名为 project.tar.gz 的 tarball 作为项目的初始内容。你可以如下操作来把它至于 git 版本控制之下。
$ tar xzf project.tar.gz
$ cd project
$ git init
Git 将会如下回复:
Initialized empty Git repository in .git/
现在,你已经初始化了工作目录——你可能已经注意到了名为 ".git" 的一个新目录了。下一步就是使用 git-add(1) 命令告诉 git 当前目录的所有文件全是项目的一个快照:
$ git add .
这个快照目前存放在一个临时区域之中,在 git 中称为 "index"。使用 git-commit 命令,你可以把 index 的所有内容永久性地存放到软件仓库之中:
$ git commit
这条命令会向你提示输入版本变更信息。这样,你的项目的第一个版本就已经存入 git 之中了。
进行修改
修改一些文件之后,你可以将
更新这些内容到 index 之中:
$ git add file1 file2 file3
现在,你已经准备就绪,可以提交了。现在你可以使用 git-diff(1) 命令的 --cache 参数
来查看都有哪些内容将会被提交:
$ git diff --cached
(如果不使用 --cached 参数,git-diff(1) 会显示所有还没添加进 index 的已经做出的改动。) 你也可以使用 git-status (1) 来获得一些当前状况的概要信息:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: file1
# modified: file2
# modified: file3
#
如果你需要进行更多改动,现在就可以进行,然后可以添加到 index 之中。最后,使用如下命令提交改动:
$ git commit
这将再次要求你输入关于这次改动内容的描述性信息,之后记录下
的项目新版本。此外,如果想省掉提交之前的 git add 命令,你可以直接用
$ git commit -a
这样会自动检测所有修改过的文件 (不包括新文件) ,并一气呵成地将它们添到 index 之中,并提交。
关于提交的描述信息: 虽然这个信息不是必须的,但提交信息描述最好以一行不超过50个字符的概要性信息来开头,在一个空行之后再进行更多的描述。比如那些将 commit 转化为 email 的工具就会把这个第一行作为邮件标题,其余的提交内容则放在邮件内部。
Git 跟踪内容而不是文件
很多版本控制系统提供了一个 "add" 命令用来记录一个新文件。而 git 的 "add" 命令更加简单也更加强大: git-add 既用于新文件也用于新近改动的文件,在所有这些情况下,它在 index 中对所有的文件与状态进行一次快照,这样就可以在下一次 commit 命令中进行提交。
查看项目历史
在任何时候,你都可以如下查看所有你进行过的改动
$ git log
你可能还想看到每一步改进中的所有完整的 diff ,这可以使用如下命令
$ git log -p
浏览改动的概要对于获得每一步修改的情况常常是比较有用的,可以使用如下命令
$ git log --stat --summary
管理分支
一个 git 仓库可以包含多个开发分支。使用如下命令可以建立一个称为 "experimental" 的新分支
$ git branch experimental
如果你运行命令
$ git branch
你将可以得到类似下面的已有分支的列表
experimental
* master
"experimental" 就是你刚刚建立的那个分支,而 "master" 分支则是建立仓库的时候自动创建的缺省分支,里面的星号表示你当前所在的分支;输入命令
$ git checkout experimental
就可以切换到 experimental 分支。现在修改一个文件,并提交改变,然后重新回到 master 分支:
(edit file)
$ git commit -a
$ git checkout master
你会发现,刚才的变更已经不可见了,这是因为这个改变是发生于 experimental 分支的,而你现在已经回到 master 分支了。
(edit file)
$ git commit -a
这里,两个分支已经产生不同了,每个分支上都发生了不同的改动。要把 experimental 中的改变也合并到 master 之中,运行命令
$ git merge experimental
如果两者的改变并不冲突,那么就算是完成了。而如果这里有冲突,有问题的文件左边会显示出标记,以表明这个文件发生了冲突;
$ git diff
上述命令将会列出具体的冲突。一旦你编辑文件解决了冲突,
$ git commit -a
这个命令将把合并的结果提交。最终,
$ gitk
会显示出漂亮的图标以展示历史变革。
这里你可以使用如下命令删除 experimental 分支。
$ git branch -d experimental
这个命令会确定 experimental 中的所有改动已经在当前分支当中了。
$ git branch -D crazy-idea
分支操作十分简单而且代价低廉,所以适合于尝试一些东西。
使用 git 进行协作
假设 Alice 在 /home/alice/project 中的 git 仓库启动了一个新项目,而在本机中也拥有 home 目录的 Bob 想要贡献一些代码。
他可以以如下工作开始:
$ git clone /home/alice/project myrepo
这会新建一个名为 "myrepo" 的目录,里面包含了 Alice 的仓库的一份克隆。这份克隆与原始项目完全一致,可以处理自己的一份原始项目历史。
之后,Bob 进行了一些变更并提交了这些变动:
(edit files)
$ git commit -a
(repeat as necessary)
当他完成的时候,他告诉 Alice 将 /home/bob/myrepo 之中的变动导入到原始仓库之中。她使用如下命令来完成这一工作:
$ cd /home/alice/project
$ git pull /home/bob/myrepo master
这回合并 Bob 的 "master" 分支到 Alice 的当前分支。如果 Alice 也已经修改了某些内容,她需要手工修复冲突。(注意,"master" 参数实际上并不是必要的,因为这是缺省分支。) "pull" 命令包括两个操作: 从远端分支中取出改动,然后合并到当前分支之中。
当你只在一个很小的小组里工作的时候,通常不会频繁地访问同一个仓库。通过定义仓库的快捷方式,可以让访问远程仓库更方便一些:
$ git remote add bob /home/bob/myrepo
这样,你可以如下用 "git fetch" 命令仅取出改动,而不把它们合并到当前分支之中:
$ git fetch bob
和长格式不同,当 Alice 使用 git remote 设置的快捷方式从 Bob 的仓库中获取内容的时候,取出的内容存储在一个 remote tracking 分支之中,在本例中是 bob/master。所以,如下操作
$ git log -p master..bob/master
将会列出从 Bob 从 Alice 的主分支中分支出去以后的所有改动。
$ git merge bob/master
这个合并也可以通过从自己的 remote tracking 分支中 pull 来做到,如
$ git pull . remotes/bob/master
注意,git pull 总是合并进当前的分支,不论命令行给出的是什么。
之后,Bob 可以如下使用 Alice 的最近改动更新自己的仓库
$ git pull
这里,他不需要给出 Alice 的仓库的位置;当 Bob 克隆了 Alice 的仓库的时候,git 在仓库设置中保存了她的仓库的位置,即 pull 所使用的位置:
$ git config --get remote.origin.url
/home/bob/myrepo
(git-clone 创建的完整配置信息可以用 "git config -l" 获得,git-config(1) 的 man page 解释了所有选项的含义。)
Git 也在 "origin/master" 分支保存了一份 Alice 的主分支的原始拷贝:
$ git branch -r
origin/master
如果其后 Bob 决定转到另一台主机上工作,他还可以通过 ssh 来克隆原始仓库:
$ git clone alice.org:/home/alice/project myrepo
此外,git 本身也有远程协议,并且可以使用 rsync 或 http,详细情况可以查看 git-pull(1) 的 man page。
Git 也可以使用类似 CVS 的工作方式,使用一个中心仓库,所有用户将改动推送到仓库之中,相关内容可以查阅 git-push(1) 的手册页或 git for CVS users 。
浏览历史
Git 的历史是通过一系列相互关联的 commit 构成的。我们已经通过 git log 命令看到了这些提交的列表。注意,每个 git log 条目的第一行是那次提交的名称:
$ git log
commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
Author: Junio C Hamano <junkio@cox.net>
Date: Tue May 16 17:18:22 2006 -0700
merge-base: Clarify the comments on post processing.
把这个名称用于 git show 命令,可以得到提交的详情。
$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
不过还有其他办法来指代这次提交。你可以只使用名称的开始部分,只要它足够长,保证在所有提交中是惟一的就行了:
$ git show c82a22c39c # the first few characters of the name are
# usually enough
$ git show HEAD # the tip of the current branch
$ git show experimental # the tip of the "experimental" branch
每一次提交通常都有一次提交作为 "parent" ,它是项目的前一个状态:
$ git show HEAD^ # to see the parent of HEAD
$ git show HEAD^^ # to see the grandparent of HEAD
$ git show HEAD~4 # to see the great-great grandparent of HEAD
Note that merge commits may have more than one parent:
要注意,合并提交可能会有多个 "parent":
$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
$ git show HEAD^2 # show the second parent of HEAD
你还可以给你的提交一个名字; 命令
$ git-tag v2.5 1b2e1d63ff
让你可以使用 "v2.5" 来指代 1b2e1d63ff。如果你想把这个名字与他人共享 (比如标记一个发布版本),你应该建立一个 "tag" 对象,可能还需要签署它; 详情请查看 git-tag(1) 的 man page。
任何 git 命令都可以使用上述任何一种名字。比如:
$ git diff v2.5 HEAD # compare the current HEAD to v2.5
$ git branch stable v2.5 # start a new branch named "stable" based
# at v2.5
$ git reset --hard HEAD^ # reset your current branch and working
# directory to its state at HEAD^
小心使用上述最后一个命令: 这将丢失工作目录中的所有改动,他还会清除本分支内随后的所有提交。如果这个分支是包含这些提交的惟一分支,它们将永远地丢失了。此外,不要对一个公众可见的、有其他开发者从中 pull 内容的分支使用 "git reset" 命令,这将导致一些不必要的合并来清除其他开发者的历史信息。如果你需要取消已经推送的改动,可以使用 git-revert(1) 命令。
git grep 可以在项目的所有版本历史中寻找字符串,而如下命令
$ git grep "hello" v2.5
会在版本 v2.5 中寻找所有 "hello" 的踪迹。
如果你不提供 commit 名称,git grep 会在你当前的目录中搜索所有由 git 管理的文件。于是,如下命令
$ git grep "hello"
是搜索 git 跟踪的所有文件的便捷的方式。
很多 git 命令可以处理一组提交,可以通过多种方式来指定版本。这里是一些 git log 的例子:
$ git log v2.5..v2.6 # commits between v2.5 and v2.6
$ git log v2.5.. # commits since v2.5
$ git log --since="2 weeks ago" # commits from the last 2 weeks
$ git log v2.5.. Makefile # commits since v2.5 which modify
# Makefile
你给出的范围的起始值不一定比终止值更老,比如,"stable-release" 分支可能会在 "master" 分支之后相当长一段时间才会引入同一个提交内容,这样
$ git log stable..experimental
将会列出 expermental 分支之中已经有的,而 stable 分支却还没有的提交,而命令
$ git log experimental..stable
将会列出 stable 中已有、但 experimental 却没有的提交。
"git log" 命令有一个弱点:必须将所有提交在一个列表中呈现出来。当项目历史中有多个不同开发分支并最终合并到一起时,"git log" 中呈现出来的顺序可能没什么意义。
大部分有大量开发者的项目 (比如 linux kernel 或 git 本身) 都经常合并分支,gitk 可以更好地将这些合并变化展示出来。比如,
$ gitk --since="2 weeks ago" drivers/
这个命令允许你浏览过去两个星期中在 "drivers" 目录之中的任意提交。(注意: 你可以按住 ctrl 键然后用 "-" 和 "+" 来调整 gitk 的字体大小。)
最后,大部分命令可以带有文件名,这可以用于指定某次提交中的某个文件,从而指定某个文件的某个版本:
$ git diff v2.5:Makefile HEAD:Makefile.in
你还可以用 "git show" 命令去查看任意文件的任意版本:
$ git show v2.5:Makefile
下一步学习
这个教程应该足够你的项目进行基本的项目发布版本管理。不过,要完全深入地理解 git 的强大功能可能还需要理解两个简单的概念:
- 对象数据库是个相当阳春的系统,用于存储你的项目的历史,包括文件、目录以及各次提交。
- 索引文件是目录树的状态的缓存,用于建立提交、取出工作目录并保存一次合并中包含的不同的目录树。
本教程的第二部分 解释了对象数据库、索引文件以及一些其他你在使用 git 中所需要了解的零七八碎的概念。
如果你不想在这条路上继续下去,还有一些细枝末节的东西可能十分有趣:
-
git-format-patch(1), git-am(1): 用于将一系列 git 提交转化成 email 发送的补丁或反之,对于 linux 内核这样的高度依赖于邮件发送的补丁的项目来说十分有用。
- git-bisect(1): 当你的项目发生倒退的时候,一个跟踪发现错误的方法就是通过历史发现那个罪魁祸首的提交。Git bisect 可以帮助你进行二分查找发现那个提交。它能在一个有很多分支合并的具有复杂非线性历史的项目中十分灵巧地进行接近最优的搜索。
原文最后更新于: 11-Jun-2007 01:21:27 UTC
Sunday, June 03, 2007
智力竞赛节目的致命伤
遥想当年, 星星擂台主持人旭东的水平似乎还是不错的, 至少在我的水平之内没看出什么问题来, 不过另两个节目却难以恭维. 那年看"三星智力快车", 一个同学把"忽必烈"答成"成吉思汗"了, 主持人在判断正误之余, 卖弄了一回"知识", 居然告诉该同学, 两者是父子关系, 从此, 我再也不看这个节目了...... 通过这件事, 我有一个明确的认识, 主持人不知道就不应该多说
不过今天, 我发现, 主持人需要控制的不只是自己, 今天一边早点一边看"SK 状元榜", 一道题要从三个图片中区分出"歼-10" 战斗机, 一位回答正确的同学解释的时候漏洞百出, 称 Su-27 为 Su-35, 称 J-10 的鸭翼为前掠翼, 主持人居然还称赞他太专业了.......
真不知道这样录制的节目怎么最终还播出了...
Monday, May 28, 2007
Friday, May 25, 2007
我的致谢
感谢赐予我生活的世界;
感谢赠予我帮助的朋友;
感谢给予我爱情的女友。
当人生失意的时候,你会明白什么是值得珍惜的,什么是不得不放弃的,什么是应该铭记的;而当人生得意的时候,你会学到的只有一个词—— “感谢”。
当我结束五年硕士与博士研究生生涯的时候,我必须要感谢我的师长们,不论是给我高屋建瓴的言传身教张平老师,还是约束、关心我们一言一行的刘宝玲老师,更有亦师亦友的纪阳老师、唐晓晟老师、李亦农老师、冯智勇老师,你们或激昂、或平易、或严谨、或理性的言行无时无刻不在引导我前进。我也会感谢无线新技术研究所给我这片成长的土壤、这个挥洒的舞台,我从这里获得了很多、赢得了很多,也留下了一份永久的美好印象。
我当然还要感谢曾经的师兄师姐们,张新的领导风范、柴慧娟的古道热肠、王黎敏的热忱执着都在影响着我,永远也不会忘记他们所给我的教诲。还有同一级的挚友们:小白、大黄、小毕、老战、阿呆、罗总、胡峥……永远怀念我们一同拼搏的日子。
还不能忘记给我快乐、给我支持的师弟师妹们,也祝福你们。
当这份论文完成的时候,我不能不感谢帮助我完成整篇论文的那些开源软件的作者和贡献者们,他们包括但不限于:
- Linus Torvalds, FSF 和其他无数贡献者开发的GNU/Linux 操作系统,是我完成整个论文的基础平台;
- D. Knuth 教授和Leslie Lamport 的杰出发明:TEX 和LATEX 这是我所使用的排版系统,论文完成后,作者为论文所开发的模板将会留下来供其他人使用,这个模板还参考并部分使用了清华的王磊设计的清华大学博士论文模板的内容;
- Werner Lamberg 的CJK是论文中文排版的核心支持;
- 张林波老师的CCT 中文TEX 排版支持中的很多工具都被用在论文档中了,还要感谢oseen的打包工作;
- NS-2 的开发者和贡献者们,以John W. Eaton 为首的Octave 的开发者,还有Maxima 的开发者们,他们提供的工具分别是论文中进行离散事件仿真、数值分析和符号运算所使用的工具。
- Thomas Williams, Colin Kelley 和其他很多贡献者开发的Gnuplot,所有曲线、直方图、三位曲面图的绘制都由Gnuplot 完成,而精美的状态图、示意图大多由John Hobby 开发的Metapost 完成,此外其他的图片制造工具还有Dia, LATEX msc宏包等。
- Bram Moolenaar等开发的VIM是本论文所用的编辑器,还有它的扩展LATEXsuit
最后,我更要感谢这位伴我度过三年博士生涯的女孩—— 王海娟,她即将成为我的新娘,三年之中我们一起走过,不论是一同徜徉于校园还是天各一方、远隔重洋,我们的心间总有剪不断的红线。患难见真情,当我受尽挫折、接近放弃的时候,是她的坚定和果敢感染了我,让我义无反顾地向前冲,去挑战自我的顽疾,去战胜人性的弱点,她不仅是我疼爱的对象,更是我一同战斗一生的坚定伙伴。
Saturday, May 05, 2007
忙里偷闲解决了一个笔记本温度传感器的问题
后经过检查,认定是 ACPI DSDT 中 SMBL 字段导致的问题 [2],PCI 总线分配的 SMBus IO 空间为 0xe800 - 0xe81f 共 32 字节,而 ACPI BIOS DSDT 表中却有
Name (SMBS, 0xE800)
Name (SMBL, 0x10)
的字段,即,SMBus IO 空间长度 16 字节,使用 Intel ASL 编译器 (IASL) [3] 修改 DSDT 中 SMBL 为 0x20 并使用此定制 DSDT 后,问题被解决。[2]
此 问题刚刚初现的原因是 [4] 中补丁进入 Linux 内核之前,驱动申请该该 IO 区域长度是固定为 16 字节的,无论 PCI 总线给予多大空间,这样尽管 DSDT 和 PCI 总线不一致,但没有暴露出冲突,不过,自从新的内核采用了更灵活的地址空间确定方式后,此问题被暴露出来 [5]。这对于一般 的 ACPI BIOS 来说,不是一个常见问题,所以没有其他人报告过。
我已经将自己修改过的对应华硕 M2Ne 笔记本(M2400Ne, Intel Dophin Pentium M CPU, 855GM/ICH4) 0204 版本 BIOS 的 DSDT 上传至 Linux ACPI 网站 [6]
[1] http://lists.lm-sensors.org/pipermail/i2c/2007-May/001118.html
[2] http://lists.lm-sensors.org/pipermail/i2c/2007-May/001129.html
[3] http://www.intel.com/technology/IAPC/acpi/downloads.htm
[4] http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=6dcc19dfbc84b2ea5428711b7a47146b5b1788bb
[5] http://lists.lm-sensors.org/pipermail/i2c/2007-May/001134.html
[6] http://acpi.sourceforge.net/dsdt/view.php?id=787
Friday, March 16, 2007
[译文] 使用 Cscope 和 SilentBob 分析源代码
| Title | | Using Cscope and SilentBob to analyze source code |
| Date | | 2007.03.09 4:01 |
| Author | | StoneLion |
| Topic | | |
当你开始研究一个不熟悉的项目的源代码的时候,对源代码的结构、函数和类的名称的含义等都不甚了解。这时,虽然我们可以使用 tags 来查看他们的定义,但是却很难通过一个一个看这些定义来得到全句的信息。Cscope 和 SilentBob 就是两个可以帮助我们分析不熟悉的源代码的工具。他们可以帮助你查找符号的定义,判断某个函数在哪里被调用了,发现某个给定的函数调用了哪些其他函数,以及在源代码中进行字符串模式匹配。使用这两个工具,你可以通过高速、有目的性的阅读代码来节省大量的时间,而不需要去手工 grep 所有代码文件。
使用 Cscope
Cscope 是一个非常流行的实用工具,大部分现代发行版都包含它。虽然 Cscope 最初被设计用来分析 C 代码,不过实际上,它在分析其他语言,诸如 C++ 和 Java 的时候同样好用。Cscope 拥有一个基于 ncurses 的界面,不过,它同样支持命令行模式,以便和其他前端程序一同工作,这些前端包含很多主流编辑器,如 Emacs 和 Vim。
当你调用 Cscope 的时候,它自动扫描当前目录之中的源文件,并把收集到的信息存储在一个内部数据库里面。如果需要递归扫描子目录,可以使用 -R 开关。如果你不需要 Cscope 的界面,只想从其它程序里查询其数据库,可以使用 -b 参数。如果你需要在一个大型或系统相关项目中使用,可以通过参考链接中的指南来查询一些优化参数,以提高速度。
在下面的面版上,可以使用方向键切换不同的查找域。使用这个面板,你可以:
- 查找某特定符号的出现位置;
- 查找全局定义 (如果找到的话,自动打开编辑器)
- 查找特定函数调用过的函数
- 查找调用某个特定函数的函数
- 查找文本字符串
- 替换字符串
- 查找一个 egrep 正则表达式
- 在编辑器里打开一个特定文件
- 查找 #include 了某个文件的文件
每当你进行一次查询,Cscope 会把查找结果进行编号,和文件名、函数名 (如果有的话)、行号以及代码行本身一起显示出来。如果使用方向键选择了一个查询结果并敲回车或是直接敲对应的数字键,Cscope 会启动系统缺省的编辑器 (由 EDITOR 环境变量确定) 打开该文件,并把光标定位到该行 (某些编辑器可能不支持定位到行,不过至少 Emacs 和 Vim 没有问题)。
使用 (X)Emacs 作为前端
在 Emacs 中使用 Cscope 非常容易。如果你没有运行着 cscope 开头的程序,而 Emacs 中已经有 Cscope 菜单了,你可以通过安装 xcscope.el 在 Emacs 中集成 Cscope。xcscope.el 可以在 Cscope 源码包中的 contrib/xcscope 子目录中得到。安装的方法是把 cscope-indexer 脚本放到 $PATH 中的路径之中,并把 xcscope.el 放到 Emacs 的脚本路径 (路径相关帮助可以在 Emacs 里面运行 ``C-h v load-path`` 来查询 load-path 相关说明)。先在,把 (require 'xcscope') 这行放到 ~/.emace 或 ~/emacs.d/init.el 当中就可以了。xcscope.el 文件的开头部分的注释可以作为文档。
这个包把所有 Cscope 的查找命令添加导了 Emacs 菜单中的 Cscope 子菜单当中,并且可以在你编辑文件的时候使用快捷键调用。比如,如果你想查找一个符号,可以直接在菜单中选择 Cscope -> Symbol 或是输入 M-x cscope-find-this-symbol,或按快捷键 C-c s s,之后输入符号的名字 (如果什么都不输入,就使用光标下的名字)。搜索的结果会显示在 *cscope* buffer 之中, 按照文件名分组,并以 <函数名>[行号] <整行内容> 的形式显示。把光标移动到想查看的搜索结果上,按空格键,将会在另一个 buffer 中打开该文件,并定位光标到搜索的行。如果你按的是回车而不是空格,你将会直接从 *cscope* buffer 中跳到相应的 buffer (鼠标点击搜索结果也可以)。你可以使用 n 和 p 来选择下一个或上一个搜索结果 (当 *cscope* buffer 不是当前 buffer 的时候可以用 C-c s n 和 C-c s p)。N 或 P 可以用于选择下一个和上一个文件 (或 C-c s N 和 C-c s P)。
在 Vim 中使用 Cscope
如果你更喜欢用 Vim,你还是可以用 Cscope。首先,你的 Vim 应该是使用 --enable-cscope 开关编译的。大部分二进制形式的 Linux 发布版都打开了这个开关。Gentoo 的用户则应该打开 cscope USE 标志。这里假设你使用的是 Vim 6.x 或 7.x。Vim 参考手册里包含了一篇使用 Cscope 接口的文章,你可以在 /usr/share/vim/vim&version/doc/if_cscop.txt找到它。你还可以看另一个简短的教程 (参考链接)。
在 Vim 中,你可以使用如下形式调用 Cscope: :cscope find search type search string (可以用 :cs f 来代替 :cscope find),这里的 search type 包括:
-
symbolors-- 所有引用这个符号的地方; -
globalorg--查找全局符号 -
callsorc--查找特定函数的所有调用 -
calledord--查找特定函数调用的所有函数 -
textort--查找文本 -
fileorf--打开文件 -
includeori--查找 #include 了指定文件的文件
在 Vim 之中,从 Cscope 查询中跳到一个结果和跳到任意的 tag 没有什么区别; 你可以用 Ctrl-T 条回到查找之前的地方,也可以用 :tnext 和 :tprevious 在查找结果间来回跳。
如果你想调对光标下的词进行查找,你应该安装 cscope_maps.vim 插件 (把这个文件放在 $HOME/.vim/plugin 目录即可)。这个文件里面的注释说明了它的用法。使用这个插件,你可以用 Ctrl- 代替 :cscope,用 Ctrl-space 代替 :scscope,搜索将意光标下的词作为搜索词 (比如,你把光标挪到 "initialize" 上面,输入 Ctrl- s,就可以找到所有引用 initialize 符号的地方了。
SilentBob
SilentBob 是一个用于分析源代码的新工具,目前支持 C/C++, Perl 以及 Python,不过它的插件框架 (目前尚无文档)允许用户方便地添加新语言和新功能。
你可以从该软件的官方网站获取源码包或 deb 包。安装之后,在源码目录运行三次 SilentBob:
bob --make-ctags这样会生成三个文件: tags, 一般标签表格; cfiles, C/C++ 文件列表; calltags, 调用标签表。 调用标签表用于函数调用,所以,如果你希望查找某个函数的所有调用,你可以让 Vim 使用 调用标签表 (:set tags=./call_tags) 并使用内建命令用于查找标签 (
bob --cfiles
bob -L cfiles --call-tags
:tag function-name 以及用 :tnext 和 :tprevious循环查找)。这三个索引文件可以被 SilentBob 用于构建调用树和反向调用树。对于 Perl 和 Python,只支持标签表和文件列表:
bob <--perl | --python> --make-ctags第二个命令会生成 generate a
bob <--perl | --python> --files
perl_files 或 python_files 文件。一旦生成了一个标签表,SilentBob 可以显示出一个调用树:
bob [--depth N] function
--depth 选项允许你限制调用树的深度。如果你之希望知道哪些函数被给定函数调用了,使用 --depth 0 参数即可。否则,被调用的函数会被依次列出来。
注意,你可以对 SilentBob 为 Python 和 Perl 文件生成的标签表加上这个参数。不过,使用 Exuberant Ctags 生成的表是不被支持的。
调用标签表还可以用于生成反向调用树
bob [--depth N] -u function
这将依次显示调用给定函数的函数。这时,指定 --depth 1 则只显示调用此函数的函数。SilentBob 也可以使用创建的 cfiles 文件查找 C/C++代码种的文本。它检查操作符,字符串和注释会被省略掉。
bob list of files --cgrep pieces of text, separated by comma
你可以指定 -L ./cfiles 来使用生成的文件列表。几个文本应该来自于同一个操作符,所以,如果你要找检验 T 变量的地方,可以使用:
bob -L ./cfiles --cgrep if,TSilentBob 还包含一个 tags 工具,让你可以在一个控制台查看标签的定义。调用
tags tag1 tag2 ... tagN 可以从代码中取出你感兴趣的片段 -- 函数定义、全局变量声明等等 -- 你将会看到你需要的代码片段。为什么使用 SilentBob 生成标签?
SilentBob 使用语法分析器来解析源文件,这使得它比使用正则表达式定位文件中的某行的工具,如 Exuberant Ctags 要快很多。在对 Linux 内核源文件 (2.6.19) 的测试中,Exuberant Ctags 生成标签表需要 90 秒,而 SilentBob 仅需 10 秒钟 (测试机使用 2.6GHz 的赛扬处理器)。SilentBob 还支持多线程优化。另一个不同来自于标签表的格式: Exuberant Ctags 生成的表中,使用正则表达式定位特定行。这就意味着,如果你编辑该文件,某些定义的位置可能会改变,但你不需要重新生成标签表。这对于 Exuberant Ctags 来说很理想,因为这样你就不需要频繁地重新生成表格了,不过,这也意味着如果你在浏览一个大型文件,在标签定义之间的跳转相对于记录行号的方式会非常慢。这就是 SilentBob 为什么在标签表中使用行号; 不过,这就意味着你必须在较大的编辑之后重新生成标签表了。
参考链接
- "tags" - http://applications.linux.com/article.pl?sid=07/01/22/167212&tid=13
- "Cscope" - http://cscope.sourceforge.net/
- "在大型项目中使用 Cscope" - http://cscope.sourceforge.net/large_projects.html
- "关于在 Vim 里使用 Cscope 的简短教程" - http://cscope.sourceforge.net/cscope_vim_tutorial.html
- "cscope_maps.vim" - http://cscope.sourceforge.net/cscope_maps.vim
- "SilentBob" - http://silentbob.sourceforge.net/
写了一个LDAP教程
在这里可以下载到打印版:
http://gnawux.googlepages.com/ldap.handout.pdf
用 beamer 写的。
Monday, March 12, 2007
[译文] Wiki 企业应用之演化
By Gabe Minton
Mortgage Banking
03/11/07 4:00 AM PT
http://www.linuxinsider.com/rsstory/56207.html
为避免错误的编辑操作,wiki 所引入的另一个功能是健壮性的修改控制机制和审计手段。这些机制不紧包括谁修改了、从某个页面添加或删除了什么,通常还包括获取变更历史以及为了检查而取出较早版本等。
协作效用 (Collaborative Effort)
Wikipedia 是这么定义 wiki 的: “一个允许访问者便捷地自行添加、删除、编辑、修改内容的网站,通常情况下,上述操作甚至不需要注册。交互与操作的便捷性使得 wiki 成为了一个高效的大型共同写作工具。”
该网站还记述: “Wiki 这个名词通常也用于指代支持上述网站运营的协作软件本身 (wiki 引擎),或指代某些特定的 wiki 站点,如计算机科学站点 WikiWikiWeb (最早的 wiki) 和在线百科全书 Wikipedia。”
本质上说,wiki 是一个支持内部链接 Web 服务器应用,交叉组织着一个可以不断扩充的信息集。Wiki 中一个包含某个特定主题的特定页面被称为一篇“文章 (article)”。
而使一个 Wiki 独一无二的正是网站中所包含的内容和这些内容到内部的其他文章之间的链接。
以 Wikipedia 为例,一篇关于加利福尼亚的文章会链接到该州首府萨克拉门托的一个页面。
传统的 wiki 之中,"驼峰词" (形如 UpperCamelCase) 会自动链接到另一个主题或文章。大多数情况下,拥有帐号的任何人都可以访问、修改或编辑一个页面。这是 wiki 的又一个特有的特征。
所有的内容都是用户创建的。这让 wiki 网站的内容成为“社区共有”的。这页同时允许了恶意用户来登录并篡改内容,这在 Wikipedia 上曾经发生过。
为避免错误的编辑操作,wiki 所引入的另一个功能是健壮性的修改控制机制和审计手段。这些机制不紧包括谁修改了、从某个页面添加或删除了什么,通常还包括获取变更历史以及为了检查而取出较早版本等。大多数时间,wiki 也提供强健的主题和全文搜索能力。
企业级应用 (Enterprise-Level Use)
没有设么理由会组织你把这项技术用于企业。通常情况下,你会利用 wiki 存储一些需要多个实名帐号访问更新其内容的技术文档。
Wiki 不是一个文档共享平台,不能用与诸如微软 SharePoint 等软件来合作。它也不是一个用于文档和源代码管理的版本管理系统 (诸如 CVS 或微软 Visual SourceSafe)。它是一个内容共享系统,良好地组织内容之间的关系。
如果你的组织中有多人添加、维护的协作知识库是,并且这些知识是相关的,这种情况下,你就可以考虑使用 wiki 了。
一个典型的例子就是软件公司中的开发方法。每个公司有自己的不同的软件生命周期模型和开发过程,通常这个模型还是不断演化的。对于一个放贷者,风险或贷款准则也是一个例子,其中的各种名词是相关的,并且一组业务员可能共同负责内容的修订。
Wiki 软件既有自由版本也有商业版本——其选择依赖于你想得到些什么。对于我所知道的大多数工业界的案例,诸如 JSPWiki 之类的自由软件就足够了 (包括 Mismo 的案例)。
简与繁 (Simple and Complex)
Mismo 使用 wiki 来存放、更新他们的工程守则。工程守则由所有的导师所共同创作,来成为一个工业标准。这些守则可以非常简单 (如使用全大写字母和下划线作为容器名称的命名常规),也可能相当复杂 (如 Mismo 如何使用 XML 名字空间第三版)。
这是当前 Mismo 工程守则的一个列表,有的还在起草中:
- Mismo business terminology
- XML profile
- Namespaces
- Unicode
- Class words
- Approved acronyms
- Mismo technical terminology
- Filenames-namespaces
- Attribute and element names
- Personal information meta data tag
- Document type definition (DTD) and zero-delta schema
- Implementation guides
- Document data mapping
- Embedded file profile
要访问Mismo wiki, 可以去这里 wiki.mismo.org/mismowiki.
如果你想在个人层次上研究 wiki 技术,看看 TiddlyWiki 吧。这是一个很不错的、自完备的 wiki,使用 html 来保存所有东西,非常易于迁移。我使用它来作为个人笔记和技术文档工具。
© 2007 Mortgage Banking. All rights reserved.
© 2007 ECT News Network. All rights reserved.
Monday, March 05, 2007
mutt 转发邮件的设置
set mime_forward=yes
set mime_forward_rest=yes
参考: Mutt FAQ
Mutt 中自动显示 word 文档
通过使用 wvHtml 可以把 doc 转换成 html,然后浏览,下面是 ~/.mailcap 设置
application/msword; wvHtml --charset=utf8 %s - | w3m -T text/html; nametemplate=%s.doc; copiousoutput不过,有的时候,MIME 会显示 doc 附件为 application/octet-stream ,这时可以让 mutt 进一步判断 MIME 类型,在 mutt 配置文件中写:
mime_lookup application/octet-stream就可以了。
参考,王垠的介绍,还有就是 mutt 用户手册。
Mutt 中根据编码不同,自动用不同命令显示附件内容
好在 mutt 会把 MIME 参数传送给 mailcap 的处理语句,这样,比如
Content-Type: text/html; charset=gb2312这个 charset 也会作为变量 %{charset} 传送给命令行,所以,我们在 ~/.mailcap 里面设置
text/html; w3m -I %{charset} -T text/html ; nametemplate=%s.html; copiousoutput;就可以把任意 text/html 内容转换成 utf-8,然后用 w3m 显示。
参考文献: 《Mutt 用户手册》第五章。
Sunday, February 18, 2007
新年许愿
- 改几个坏毛病, 嗯, 尤其是自己的时间管理方面, 提高一点自制力.
- 为求学生涯划上一个好看的句号.
- 为职业生涯开个好头, 找到一份好工作.
- 完成我的 linux 书.
- 来一个甜蜜幸福的婚礼.
新年, 新生活
Sunday, February 11, 2007
[译文] 使用加密工具提高系统安全性
Thursday February 08, 2007
By: Rui Lopes
多数情况下,诸如 SELinux, AppArmor, Bastille 以及 grsecurity 系统级的安全解决方案能够让你的 Linux 桌面系统相当地安全。不过,有些情况下,文件或是目录的加密还是必要的。当你需要把包含你的私人数据的文件通过可以动存储介质或是网络复制到其他什么地方的时候,有些工具能得上忙的。
这些工具大部分是命令行方式工作的,不过,它们确实很简单,大部分患有命令行恐惧正的新手在经过一定的努力之后都能使用它们。所有这些工具都很小、需要极少的依赖库 (如果确实需要的话),可以说都是高度便携的。
Bcrypt
我的 U 盘里总会放着一份 Bcrypt. 这是个极为简单易用的基本工具。一旦装上了 bcrypt, 你惟一要做的就是在命令行依次输入 bcrypt 和你要加密的文件名。之后,Bcrypt 会要求你提供一个密码,仅此而已。加密之后,Bcrypt 会给文件加上 .bfe 的扩展名。同样的命令也可以用于解密,bcrypt 仅仅通过文件的扩展名来决定是要加密还是解密。
Bcrypt 使用 Blowfish 。算法。密码通常要有 8 到 56 个字符,并被内部哈希为一个 448 位密钥以增强安全性。缺省条件下,加密文件的时候,原始文件将被删除并在原来存储文件的位置覆写 3 次,以避免通过反删除工具发现原始文件,你可以增加或减少重写的次数。
Bcrypt 缺乏灵活性和各种开关,所以可能不是你加密军火库中的首选武器,不过,简单是它的强项。根据 Bcrypt 的项目网站介绍,它可以用于 Linux 和 *BSD, 没有 Windows 版本; 不过,这个网站似乎有年头没更新过了。
Ncrypt
Ncrypt 是另一个简单的文件加密/解密程序。你可以选择三个 AES 加密标准候选加密算法: Rijndael, Twofish, 和 Serpent 中的任何一个。开发者特别注意了生成密码的安全性: 当一个文本密码被用于加密文件的时候,它首先被转换为 SHA-1 哈希值,而原文被立刻清除出内存。之后,这个哈希值被用于密钥,随后,也会被清除出内存。
Ncrypt 同时有 Unix 和 Windows 版本。它的用法很直接: 使用 ncrypt 命令,用开关选择加密算法,是要加密、解密、还是要清除文件,以及输入输出文件名。。原始文件将被从磁盘上彻底清除;Ncrypt 也有一个非常有用的文件清除功能,你可以用它安全删除文件。
Pad
与 Bcrypt 和 Ncrypt 不同,Pad 使用 on-time pad (OTP) 加密。一个文件会被分成两个乃至更多独立的"pads", 没一个都类似于白噪声。两个文件看起来只是混入了无害的随机数据,可以在用户喜欢的任何时候被重新组装起来。
OTP 有非常悠久的历史了。理论上讲,它是惟一真正不可破解的加密手段。不过,它有一些实际应用上的弱点: 每个 pad 尽可以被使用一次,而且必须是真正的随机数 (这实际上非常困难),并且它很容易受到中间人工具。这样,它不被推荐用于日常加密任务。不过,有一个场合下,OTP 被认为是有价值的: 自由语音加密。比如,如果有人希望自由分发一个机密文件,它可以将文件拆成不同的 pad,并将多个部分存放在不同的一同里,并告诉别人如何获取它们并组合成原始文件。(译注: 这段翻译的不怎么好,因为译者根本不懂什么是 OTP, 查了半天还是不怎么明白,欢迎解释。)
GnuPG对于多数人来说,GnuPG 是开源文件加密的不二选择。一些发布版还把它随发布版预装。引用其 FAQ 中的原文,"GnuPG 代表 GNU 隐私卫士,是用于安全通信与数据存储的 GNU 工具。它可以被用于数据加密和创建数字签名。它包含一个先进的密钥管理工具并与 RFC 2440 所描述的 OpenPGP Internet 标准提案相兼容。因而,它的目标是兼容 NAI 公司的 PGP。."
Bcrypt, Ncrypt 和很多其它加密工具使用对称密钥加密文件。而 GnuPG 和 PGP 一样,使用公钥密钥加密,尽管它也支持对称加密。公钥加密使得加密和签名多个文件并和其他用户交换它们变得很容易。对于对称密钥和公钥密钥的综述以及它们的工作机制,可以参考 GnuPG 手册的第二章和 FAQ 的 7.1 节。
GnuPG 可以独自地作为命令行工具使用,它有很多功能,并可以满足各种挑剔的用户。不过,它也有各种不错的前端工具可用。其中,我所用的是 KGpg, 集成在 KDE 之中,这方便了我很多日常加密工作。比如,其它我提到的加密工具只能加密文件,而不能加密目录。你可以用 KGpg 加密目录中的所有文件,或者创建一个 .tar.gz 或是 .zip 文件,并对它进行加密 。使用 KGpg, 我只要双击一个目录并告诉程序打包加密之即可。KGpg 也允许你在桌面创建一个碎纸机,用于安全删除你放在里面的文件 (而不是目录)。这非常方便,尽管我建议你阅读作者的关于破碎文件的安全说明。KGpg 的易用性非常优秀;即使对新手来说,学习曲线也非常短。
GnuPG 的开发步伐非常文鼎,而且支持多种操作系统。它在多种 Unix 系统上都可以编译,而且通过 Gpg4Win 提供了 Windows 移植版本。对于 Unix 系统的邮件加密的支持性也非常可靠: 查询 GnuPG FAQ 的 4.15 节,可以看到提供 GnuPG 集成的邮件客户端的列表 (很遗憾,支持 OpenPGP 标准的 Windows 客户端十分少见)。关于更多的邮件工具,可以看看 Aegypten 项目。
关于加密的更多信息,两个不错的起点是对应的 Wikipedia 文章和 Cryptography FAQ.
Rui Lopes 是一个葡萄牙网页设计师和独立纸片人,在技术领域有广泛兴趣。
Wednesday, February 07, 2007
发现一个兼容性问题
降级回到 Debian Sid 的 Iceweasel 2.0.0.1 (也就是 Firefox 2.0.0.1) 才解决了问题,啥都正常了。
Wednesday, January 03, 2007
应用层再分层
对很多应用层应用案例,可能可以分三个子层面,暂时按照王氏命名法,命名为
语义子层 semantics
表达子层 express
传输子层 transport
传输子层包含应用层信令, 传输内容的规格、长度等,例如 HTTP, SMTP 等
表达子层是应用层内容的基本表示形式、语法表达规范等,例如 MIME/Base64, XML 等
语义子层是应用层内容的与语义相对应的具体语法格式,例如 XHTML, RDF 等
对于传统的协议,可能有的协议会对应其中的多个子层,嗯,呵呵
偶也不知道 SOAP, SDP, WSDL 啥的到底应该怎么对应,不过,应该能对应上吧,至于那些不能对应的,呵呵,偶因为还不清楚,说不好怎么弄
Saturday, December 30, 2006
技术分类学问题
可是,伊仍然希望我们给出一个漂亮的分类学体系,然后再把知识添加进去。而实际上,我个人相信,以自组织方式,通过多次迭代,建立几组索引,进行不完全分类可能更有利,我的立足点是:
- 可类比性: 每组索引/分类尺度要使得纳入范围的知识条目具有可类比性。
- 易于发现: 如果有人希望通过索引找到需要的东西,他不需要一个完整的分类法,而需要按照他希望的思路找到一个他可以接受的分类法,从而尽快找到目标就好。