三、不可变对象的好处
  GC算法在扫描存活对象时通常需要从ROOT节点开始,扫描所有存活对象的引用,构建出对象图。
  不可变对象对GC的优化,主要体现在Old Generation中。
  可以想象一下,如果存在Old Generation的对象引用了Young Generation的对象,那么在每次YoungGC的过程中,必须考虑到这种情况。
  Hotspot JVM为了提高YoungGC的性能,避免每次YoungGC都扫描Old Generation中的对象引用,采用了 卡表(Card Table) 的方式。
  简单来说,当Old Generation中的对象发生对Young Generation中的对象产生新的引用关系或释放引用时,都会在卡表中响应的标记上标记为脏(dirty),而YoungGC时,只需要扫描这些dirty的项可以了。
  可变对象对其它对象的引用关系可能会频繁变化,并且有可能在运行过程中持有越来越多的引用,特别是容器。这些都会导致对应的卡表项被频繁标记为dirty。
  而不可变对象的引用关系非常稳定,在扫描卡表时不会扫到它们对应的项了。
  注意,这里的不可变对象,不是指仅仅自身引用不可变的final对象,而是真正的Immutable Objects。
 

  四、引用置为null的传说
  早期的很多Java资料中都会提到在方法体中将一个变量置为null能够优化GC的性能,类似下面的代码:
  List<String> list =newArrayList<String>();
  // some code
  list =null;// help GC
  事实上这种做法对GC的帮助微乎其微,有时候反而会导致代码混乱。
  我记得几年前 @rednaxelafx 在HLL VM小组中详细论述过这个问题,原帖我没找到,结论基本是:
  在一个非常大的方法体内,对一个较大的对象,将其引用置为null,某种程度上可以帮助GC。
  大部分情况下,这种行为都没有任何好处。
  所以,还是早点放弃这种“优化”方式吧。
  GC比我们想象的更聪明。
 

  五、手动档的GC
  在很多Java资料上都有下面两个奇技淫巧:
  通过Thread.yield()让出CPU资源给其它线程。
  通过System.gc()触发GC。
  事实上JVM从不保证这两件事,而System.gc()在JVM启动参数中如果允许显式GC,则会触发FullGC,对于响应敏感的应用来说,几乎等同于自杀。
  So,让我们牢记两点:
  Never use Thread.yield()。
  Never use System.gc()。除非你真的需要回收Native Memory。
  第二点有个Native Memory的例外,如果你在以下场景:
  · 使用了NIO或者NIO框架(Mina/Netty)
  · 使用了DirectByteBuffer分配字节缓冲区
  · 使用了MappedByteBuffer做内存映射
  由于Native Memory只能通过FullGC(或是CMS GC)回收,所以除非你非常清楚这时真的有必要,否则不要轻易调用System.gc(),且行且珍惜。
  另外为了防止某些框架中的System.gc调用(例如NIO框架、Java RMI),建议在启动参数中加上-XX:+DisableExplicitGC来禁用显式GC。
  这个参数有个巨大的坑,如果你禁用了System.gc(),那么上面的3种场景下的内存无法回收,可能造成OOM,如果你使用了CMS GC,那么可以用这个参数替代:-XX:+ExplicitGCInvokesConcurrent。
  关于System.gc(),可以参考 @bluedavy 的几篇文章:
  · CMS GC会不会回收Direct ByteBuffer的内存
  · 说说在Java启动参数上我犯的错
  · java.lang.OutOfMemoryError:Map failed
 

  六、指定容器初始化大小
  Java容器的一个特点是可以动态扩展,所以通常我们都不会去考虑初始大小的设置,不够了反正会自动扩容呗。
  但是扩容不意味着没有代价,甚至是很高的代价。
  例如一些基于数组的数据结构,例如StringBuilder、StringBuffer、ArrayList、HashMap等等,在扩容的时候都需要做ArrayCopy,对于不断增长的结构来说,经过若干次扩容,会存在大量无用的老数组,而回收这些数组的压力,全都会加在GC身上。
  这些容器的构造函数中通常都有一个可以指定大小的参数,如果对于某些大小可以预估的容器,建议加上这个参数。
  可是因为容器的扩容并不是等到容器满了才扩容,而是有一定的比例,例如HashMap的扩容阈值和负载因子(loadFactor)相关。
  Google Guava框架对于容器的初始容量提供了非常便捷的工具方法,例如:
  Lists.newArrayListWithCapacity(initialArraySize);
  Lists.newArrayListWithExpectedSize(estimatedSize);
  Sets.newHashSetWithExpectedSize(expectedSize);
  Maps.newHashMapWithExpectedSize(expectedSize);
  这样我们只要传入预估的大小即可,容量的计算交给Guava来做吧。
  反例:
  如果采用默认无参构造函数,创建一个ArrayList,不断增加元素直到OOM,那么在此过程中会导致:
  多次数组扩容,重新分配更大空间的数组
  多次数组拷贝
  内存碎片