1.Java的内存泄漏
在Java中,内存泄漏指的是存在具备下面两个特点的对象:
①这些对象是可达的,即在有向图中,存在通路可以与其相连;
②这些对象是无用的,即程序以后不会再使用这些对象。
如果对象满足这两个条件,就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,却占用着内存。对于那些不可达的对象,GC会负责回收。
我们可以通过调用System.gc()去访问GC,但是JVM并不保证垃圾收集器一定会执行。通常GC的线程优先级较低,JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。对一些特定场合,GC的执行影响应用程序的性能,例如基于WEB的实时系统、网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们要调整GC参数。
下面给出一个简单的Java 内存泄漏的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放对象引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象放入到Vector中后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v = new Vector();
for (int i = 0; i < 100; i++) {
Object obj = new Object();
v.add(obj);
obj = null;// 此时,所有的Object对象没有被释放,因为变量v引用这些对象
}
实际上无用而还被引用的对象,GC就无能为力了(事实上GC认为它还有用),这一点是导致内存泄漏最重要的原因。
2.Java内存模型
①堆内存
如上图所示,JVM内存可以划分为不同的部分,堆内存提供了大量的内存Switch,广义上,JVM堆内存可以划分为两部分:年轻代(Young Generation)和年老代(Old Generation)
a.年轻代(Young Generation)
年轻代用于存放由new所生成的对象。当年轻代空间满时,垃圾回收就会执行。这个垃圾回收我们称之为最小垃圾回收(Minor GC)。年轻代又分为三部分:一个Eden内存区(Eden Memory)和两个Surivor内存区(Surivor Memory),用来实施复制算法,每次复制就是将Eden和第一块Survior的活对象复制到第2块,然后清空Eden与第一块Survior。Eden与Survivor的比例由-XX:SurvivorRatio=设置,默认为32。Survivio大了会浪费,小了的话,会使一些年轻对象潜逃到老人区,引起老人区的不安,但这个参数对性能并不重要。
Young的大小设置挺重要的,大点就不用频繁GC,而且增大GC的间隔后,可以让多点对象自己死掉而不用复制了。但Young增大时,GC造成的停顿时间攀升得非常恐怖,
关于年轻代的重点:
- 大多数新创建的对象都放在Eden内存区;
- 当Eden区存满对象时,Minor GC将会被执行,所有存活的对象被移到其中一个Surivor区;
- Minor GC同时也会检查存活的对象并将它们移到另一个Surivor区,所以在一段时间,其中一个Surivor区总是空的;
- 存活的对象经过多次GC后,将会被移到老年代(Old Generation),通常由年轻代经多长时间转变为老年代都会设置一个阈值。
老年代内存包含那些经过多次Minor GC且存活的对象。通常当老年代内存满时垃圾回收会被执行,我们称之为Major GC,通常这个过程会花费很长的时间。
年轻代的对象如果能够挺过数次收集,就会进入老人区。老人区使用标记整理算法。因为老人区的对象都没那么容易死的,采用复制算法就要反复的复制对象,很不合算,只好采用标记清理算法,但标记清理算法其实也不轻松,每次都要遍历区域内所有对象。
-XX:MaxTenuringThreshold=设置熬过年轻代多少次收集后移入老人区,CMS中默认为0,则年轻代对象不经过Survivor区,经过第一次GC就转入直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间
Stop the World Event
所有垃圾回收都是“阻塞”事件("Stop the World" events),因为所有应用程序线程必须停止,直到垃圾回收操作完成之后才能继续。
由于年轻代总是保存着短期存活的对象,Minor GC非常快,应用程序受到的影响很小。然后Major GC由于它要检查所有存活的对象,所以需要花费很长的时间。Major GC会在垃圾回收期间使得你的应用程序没有任何响应,应最大化的减少此类GC,这也就是为什么对于那些高响应应用程序来说,GC的监控与调优显得非常必要。
小结
Young -- Mimor GC -- 复制算法
Old -- Major GC-- 标记清除/标记整理算法
c.永久代(Permanent Generation)
永久代不是Java堆内存的一部分
永久代用于存放Class和Meta的信息、运行时的常量和静态变量,默认64M
②栈内存
用于执行线程,包含方法短期存活的特征值及方法中所涉及的指向堆中其它对象的引用
③性能参数
参数 | 含义 | 说明 |
-Xms | JVM启动时堆初始大小 | |
-Xmx | 堆的最大值 | 设置-Xms和-Xmx尺寸一样,可以减少GC次数;将它们设置足够大,否则会产生out of memory异常,但又不能设置多大,过大会增加GC的工作时间 |
-Xmn | 年轻代大小 | |
-Xss | 每个线程堆栈大小 | |
-XX:MaxPerSize | 永久代的初始大小 | |
-XX:MaxPermSize | 永久代的最大尺寸 | |
-XX:SurvivorRatio | 年轻代中eden和survivor区的比值 |
设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
|
-XX:+UseParallerlGC
| 使用ParallelGC | |
-XX:ParallelGCThreads | ParallelGC的线程个数 | 与CPU个数相同,使得所有cpu都参与GC工作 |
④基本垃圾收集算法
a.复制:将堆内分成两个相同空间,从根(ThreadLocal的对象,静态对象)开始访问每一个关联的活跃对象,将空间A的活跃对象全部复制到空间B,然后一次性回收整个空间A。
因为只访问活跃对象,将所有活动对象复制走之后就清空整个空间,不用去访问死对象,所以遍历空间的成本较小,但需要巨大的复制成本和较多的内存。
b.标记清除(mark-sweep):收集器先从根开始访问所有活跃对象,标记为活跃对象。然后再遍历一次整个内存区域,把所有没有标记活跃的对象进行回收处理。该算法遍历整个空间的成本较大暂停时间随空间大小线性增大,而且整理后堆里的碎片很多。
c.标记整理(mark-sweep-compact):综合了上述两者的做法和优点,先标记活跃对象,然后将其合并成较大的内存块。
参考资料:
参数调节:http://unixboy.iteye.com/blog/174173
JDK5.0垃圾收集优化之--Don't Pause:http://blog.csdn.net/calvinxiu/article/details/1614473
Java虚拟机内存模型及垃圾回收监控调优:http://www.cnblogs.com/tonyspark/p/3731696.html