java垃圾回收机制:全面分析Java的垃圾回收机制

  引言
|?B'M)Hi+U
)f)VBMh]yH  Java堆是个运行时数据区例子(对象)从中分配空间Java虚拟机(JVM)堆中储存着正在运行应用所建立所有对象这些对象通过.gif' />、a.gif' />和multia.gif' />等指令建立但是它们不需要代码来显式地释放般来说是由垃圾回收 来负责尽管JVM规范标准并不要求特殊垃圾回收技术甚至根本就不需要垃圾回收但是由于内存有限性JVM在实现时候都有个由垃圾回收所管理垃圾回收是种动态存储管理技术它自动地释放不再被引用对象按照特定垃圾收集算法来实现资源自动回收功能 JAVA中文站社区门户z8? V1?pD

zj;x;y K1UtN  垃圾收集意义
"|d!uC._`)^
X6_.C W9i i*H1B-V3L@  在C对象所占内存在结束运行的前直被占用在明确释放的前不能分配给其它对象;而在Java中当没有对象引用指向原先分配给某个对象内存时该内存便成为垃圾JVM个系统级线程会自动释放该内存块垃圾收集意味着不再需要对象是"无用信息"这些信息将被丢弃个对象不再被引用时候内存回收它占领空间以便空间被后来新对象使用事实上除了释放没用对象垃圾收集也可以清除内存记录碎片由于创建对象和垃圾收集器释放丢弃对象所占内存空间内存会出现碎片碎片是分配给对象内存块的间空闲内存洞碎片整理将所占用堆内存移到堆JVM将整理出内存分配给新对象
~J0i i+qrJAVA中文站社区门户+?/?4m'c"^ iJw-T
  垃圾收集能自动释放内存空间减轻编程负担这使Java 虚拟机具有些优点首先它能使编程效率提高在没有垃圾收集机制时候可能要花许多时间来解决个难懂存储器问题在用Java语言编程时候靠垃圾收集机制可大大缩短时间其次是它保护完整性 垃圾收集是Java语言安全性策略个重要部份 JAVA中文站社区门户0L'MhfY3~0q*Y4Yp}f8W
JAVA中文站社区门户 xm"k0?8N6n.n3D
  垃圾收集个潜在缺点是它开销影响性能Java虚拟机必须追踪运行中有用对象 而且最终释放没用对象个过程需要花费处理器时间其次垃圾收集算法不完备性早先采用某些垃圾收集算法就不能保证100%收集到所有废弃内存当然随着垃圾收集算法不断改进以及软、硬件运行效率不断提升这些问题都可以迎刃而解 JAVA中文站社区门户idaKM_b9Ts1J
JAVA中文站社区门户}m C \;T
  垃圾收集算法分析
7Iz [w5Rv0A2Y.]sJAVA中文站社区门户'C MW} leBs,A9H
  Java语言规范标准没有明确地介绍说明JVM使用哪种垃圾回收算法但是任何种垃圾收集算法般要做2件基本事情:(1)发现无用信息对象;(2)回收被无用对象占用内存空间使该空间可被再次使用 JAVA中文站社区门户,h5]!L#F2mn7?/P
JAVA中文站社区门户!~!o[n-W[SB
  大多数垃圾回收算法使用了根集(root )这个概念;所谓根集就量正在执行Java可以访问引用变量集合(包括局部变量、参数、类变量)可以使用引用变量访问对象属性和对象思路方法垃圾收集首选需要确定从根开始哪些是可达和哪些是不可达从根集可达对象都是活动对象它们不能作为垃圾被回收这也包括从根集间接可达对象而根集通过任意路径不可达对象符合垃圾收集条件应该被回收下面介绍几个常用算法 JAVA中文站社区门户L s8A w xT

Z]OHO2E\  1、 引用计数法(Reference Counting Collector)
Q]y$}p|8Z-W b@5VJAVA中文站社区门户Mlx&Osr6\%Q
  引用计数法是唯没有使用根集垃圾回收该算法使用引用计数器来区分存活对象和不再使用对象般来说堆中每个对象对应个引用计数器当每次创建个对象并赋给个变量时引用计数器置为1当对象被赋给任意变量时引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用)引用计数器减1旦引用计数器为0对象就满足了垃圾收集条件 JAVA中文站社区门户7mt1g2zM{`

!tt9y(yM`(\7P\)`  基于引用计数器垃圾收集器运行较快不会长时间中断执行适宜地必须 实时运行但引用计数器增加了执行开销每次对象赋给新变量计数器加1而每次现有对象出了作用域生计数器减1 JAVA中文站社区门户9NO"dM:E]j,q'G;g

j1Q nM X,z7M-O-?  2、tracing算法(Tracing Collector) JAVA中文站社区门户&S"S${g^
JAVA中文站社区门户C3OQ-t#aX-g.z'Y2Iw
  tracing算法是为了解决引用计数法问题而提出它使用了根集概念基于tracing算法垃圾收集器从根集开始扫描识别出哪些对象可达哪些对象不可达并用某种方式标记可达对象例如对每个可达对象设置个或多个位在扫描识别过程中基于tracing算法垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器. JAVA中文站社区门户1n \ Bf,] {0s3k

4Po*_4S1e  3、compacting算法(Compacting Collector)
YK2@7lpL[S7qJ
O+[`6X Y  为了解决堆碎片问题基于tracing垃圾回收吸收了Compacting算法思想在清除过程中算法将所有对象移到堆端就变成了个相邻空闲内存区收集器会对它移动所有对象所有引用进行更新使得这些引用在新位置能识别原来 对象在基于Compacting算法收集器实现中般增加句柄和句柄表 JAVA中文站社区门户fIFu H&Y
 
5H.E#iF'e~$nMRR4、copying算法(Coping Collector) JAVA中文站社区门户 nm*VKp.HNJ

9h{ RTf,F K"j[  该算法提出是为了克服句柄开销和解决堆碎片垃圾回收它开始时把堆分成 个对象 面和多个空闲面 从对象面为对象分配空间当对象满了基于coping算法垃圾 收集就从根集中扫描活动对象并将每个 活动对象复制到空闲面(使得活动对象所占内存的间没有空闲洞)这样空闲面变成了对象面原来对象面变成了空闲面会在新对象面中分配内存 JAVA中文站社区门户y.e&mtfXl]7o

O:r2^xO+J  种典型基于coping算法垃圾回收是stop-and-copy算法它将堆分成对象面和空闲区域面在对象面和空闲区域面切换过程中暂停执行
x5VQ;_m4QU;QyE&UJAVA中文站社区门户$aS$Xas@~:U7ncb'[)u
  5、generation算法(Generational Collector) JAVA中文站社区门户&b zN|3mb

1C'] }7u"N0o!@  stop-and-copy垃圾收集器个缺陷是收集器必须复制所有活动对象这增加了等待时间这是coping算法低效原因设计中有这样规律:多数对象存在时间比较短少数存在时间比较长因此generation算法将堆分成两个或多个每个子堆作为对象代(generation)由于多数对象存在时间比较短随着丢弃不使用对象垃圾收集器将从最年轻子堆中收集这些对象在分代式垃圾收集器运行后上次运行存活下来对象移到下最高代子堆中由于老子堆不会经常被回收因而节省了时间 JAVA中文站社区门户^&Vq.K2W z:BJ

;a@Z}9FX0V _2O  6、adaptive算法(Adaptive Collector) JAVA中文站社区门户;C#|9d\tm3C#T

$rG(f&F`:rC  在特定情况下些垃圾收集算法会优于其它算法基于Adaptive算法垃圾收集器就是监控当前堆使用情况并将选择适当算法垃圾收集器 JAVA中文站社区门户bB el}CA
 
g2E U sgUy$| 透视Java垃圾回收
LR?*tC*Q i$m
'`9Mf2@ Y| v  1、命令行参数透视垃圾收集器运行JAVA中文站社区门户`,VW)|'L!r fmx'D)L

,q:F%EG:tO  2、使用.gc可以不管JVM使用是哪种垃圾回收算法都可以请求Java垃圾回收在命令行中有个参数-verbosegc可以查看Java使用堆内存情况格式如下:
Lx,|a e0q;RPJAVA中文站社区门户 S$N\BC0d-x$~P
java -verbosegc file
#J6?(a["e@rOk9\`D c
#Dh;c@\-D/naD  可以看个例子:
v{f`XX p
wJzeqBx^K TestGC JAVA中文站社区门户y!B^R,w H ?wj8?
{JAVA中文站社区门户+MQ#E#{.P4A
 public void (String args) JAVA中文站社区门户/N9Q(|3}0dR"Kv1i"Q]
 {JAVA中文站社区门户j7x/Y2rvu
   TestGC;
l c7jASY  .gc;
.P5w@;z:F(P  .runFinalization;
aen/B{(t p.~$Zy }JAVA中文站社区门户pi;D"n3McZ9e
} JAVA中文站社区门户 C:O}yuz2dD[r

M}A[6p3V f  在这个例子中个新对象被创建由于它没有使用所以该对象迅速地变为可达编译后执行命令: java -verbosegc TestGC 后结果为:JAVA中文站社区门户&e\/__P/n
JAVA中文站社区门户\/b#b\K)s v
[Full GC 168K->97K(1984K) 0.0253873 secs]
#Fb0Rk{2dn H@-LJAVA中文站社区门户8hI[ J9e$^6N |`TK
  机器环境为Windows 2000 + JDK1.3.1箭头前后数据168K和97K分别表示垃圾收集GC前后所有存活对象使用内存容量介绍说明有168K-97K=71K对象容量被回收括号内数据1984K为堆内存总容量收集所需要时间是0.0253873秒(这个时间在每次执行时候会有所区别)JAVA中文站社区门户,|b/|+pidfmx
JAVA中文站社区门户t%T'?Y(]w
  2、finalize思路方法透视垃圾收集器运行JAVA中文站社区门户4qV3AO1s d
JAVA中文站社区门户%s.Ogq%yVm
  在JVM垃圾收集器收集个对象的前 般要求适当思路方法释放资源但在没有明确释放资源情况下Java提供了缺省机制来终止化该对象心释放资源这个思路方法就是finalize()原型为:JAVA中文站社区门户2t4y u2O c;HU
JAVA中文站社区门户9Iho@*[*z:}
protected void finalize throws Throwable
lU#B|"X QWsGJAVA中文站社区门户WIME(oUYr(]
  在finalize思路方法返回的后对象消失垃圾收集开始执行原型中throws Throwable表示它可以抛出任何类型异常JAVA中文站社区门户(P/sa8bS'P Vax\

z__~/y C:TT  的所以要使用finalize是由于有时需要采取和Java普通思路方法区别种思路方法通过分配内存来做些具有C风格事情这主要可以通过"固有思路方法"来进行它是从Java里非Java思路方法种方式C和C是目前唯获得固有思路方法支持语言但由于它们能通过其他语言编写所以能够有效地任何东西在非Java代码内部也许能Cmalloc系列用它分配存储空间而且除非了free否则存储空间不会得到释放从而造成内存"漏洞"出现当然free个C和C所以我们需要在finalize内部个固有思路方法中也就是说我们不能过多地使用finalize它并不是进行普通清除工作理想场所JAVA中文站社区门户 v#U yl;n.w

:q'A-p:tZ iA  在普通清除工作中为清除个对象那个对象用户必须在希望进行清除地点个清除思路方法这和C"破坏器"概念稍有抵触在C所有对象都会破坏(清除)或者换句话说所有对象都"应该"破坏若将C对象创建成个本地对象比如在堆栈中创建(在Java中是不可能)那么清除或破坏工作就会在"结束花括号"所代表、创建这个对象作用域末尾进行若对象是用创建(类似于Java)那么当Cdelete命令时(Java没有这个命令)就会相应破坏器员忘记了那么永远不会破坏器我们最终得到将是个内存"漏洞"另外还包括对象其他部分永远不会得到清除
/q;o6_ HpOYV|JAVA中文站社区门户!mb_'e3uI w$h
  相反Java不允许我们创建本地(局部)对象--无论如何都要使用但在Java中没有"delete"命令来释放对象垃圾收集器会帮助我们自动释放存储空间所以如果站在比较简化立场我们可以说正是由于存在垃圾收集机制所以Java没有破坏器然而随着以后学习深入就会知道垃圾收集器存在并不能完全消除对破坏器需要或者说不能消除对破坏器代表那种机制需要(而且绝对不能直接finalize所以应尽量避免用它)若希望执行除释放存储空间的外其他某种形式清除工作仍然必须Java中个思路方法它等价于C破坏器只是没后者方便
k+M2mu!|3_!`*A'G
VQ:mU5Yyg  下面这个例子向大家展示了垃圾收集所经历过程并对前面陈述进行了整理总结JAVA中文站社区门户$^5X:iE}\
JAVA中文站社区门户lJzmO6rZH5z+C,`z$m
Chair {JAVA中文站社区门户mRY"[3K`0\6Q-} ^6j
  boolean gcrun = false;JAVA中文站社区门户"f/c-@Rz!r"A$^
  boolean f = false;
`aA(p!\p+~  created = 0;
YZ`,a(N%U  finalized = 0;JAVA中文站社区门户"Pm8z3E8v7\
  i;
6m.l[%u5_ g"h Chair {JAVA中文站社区门户:XTmHX6Le X7s/N+r
  i = created;JAVA中文站社区门户HI*T qq\_+g
  (created 47) JAVA中文站社区门户gtwK^Vd$B
   .out.prln("Created 47");JAVA中文站社区门户1v!wW)N\
 }JAVA中文站社区门户}&K a+c0sXN8@
 protected void finalize {JAVA中文站社区门户0Fd5wZ+X,N.xA
  (!gcrun) {
8WG{e g'O7u!WH   gcrun = true;JAVA中文站社区门户&t^ F#_4i!X
   .out.prln("Beginning to finalize after " + created + " Chairs have been created");JAVA中文站社区门户8A0GK,GRT1O-M+b
  }
&@EuTck.GJ  (i 47) {
v E3@:DS0Sn&B,o   .out.prln("Finalizing Chair #47 " +"Setting flag to stop Chair creation");
h-C` c?'E   f = true;JAVA中文站社区门户f M5|V M.hc
  }JAVA中文站社区门户+I+W8k'qD
  finalized;JAVA中文站社区门户dA6B;xn7_`9~(MZ'dLN
  (finalized >= created)
+QC9@vs f   .out.prln("All " + finalized + " finalized");
]~^8k%TrO8RMD_ }JAVA中文站社区门户9i&h _M"Xq$d _1YL
}
h ~,~-]:\ go
s.X~9Nzpublic Garbage {
c'n p\[8Z_}KVk r public void (String args) {
;U)A AY9N7Hx  (args.length 0) {JAVA中文站社区门户a6U;?)^#V
   .err.prln("Usage: \n" + "java Garbage before\n or:\n" + "java Garbage after");JAVA中文站社区门户.w4ml`1q:C3}N7f;e0m'DA
   ;
i)Q]![1nF6U S Dn  }
A"M*\ |4V D7|Y  while(!Chair.f) {JAVA中文站社区门户hYB@/r e v
    Chair;JAVA中文站社区门户 ?W8Y n2h0Frd7Aw$_
    String("To take up space");JAVA中文站社区门户qpD'u s
  }
1S^M R8L  .out.prln("After all Chairs have been created:\n" + "total created = " + Chair.created +JAVA中文站社区门户er~EmcD~[ S
" total finalized = " + Chair.finalized);JAVA中文站社区门户 bKF3R1`3n p0w"~ m
  (args[0].equals("before")) {
yL,J6?Td    .out.prln("gc:");JAVA中文站社区门户~9p*mX"XL4r
    .gc;JAVA中文站社区门户"u,T&K wur.a
    .out.prln("runFinalization:");
_ne]/rV.d2wG?    .runFinalization;JAVA中文站社区门户4B5v s/jF tc
  }
*H*{;\1R!ZZ,?  .out.prln("bye!");JAVA中文站社区门户Eurk-g
  (args[0].equals("after"))
vZ3dnUg   .runFinalizersOnExit(true);JAVA中文站社区门户rM OGJ;x_,W
 }JAVA中文站社区门户 E({:JsX-~;c%z
}
6Jc D}RA(d,V%E3S
3^Yf0d EQk  上面这个创建了许多Chair对象而且在垃圾收集器开始运行后某些时候会停止创建Chair由于垃圾收集器可能在任何时间运行所以我们不能准确知道它在何时启动因此个名为gcrun标记来指出垃圾收集器是否已经开始运行利用第 2个标记fChair可告诉它应停止对象生成这两个标记都是在finalize内部设置于垃圾收集期间另两个变量--created以及finalized--分别用于跟踪已创建对象数量以及垃圾收集器已进行完收尾工作对象数量最后每个Chair都有它自己(非) i所以能跟踪了解它具体编号是多少编号为47Chair进行完收尾工作后标记会设为true最终结束Chair对象创建过程 JAVA中文站社区门户4Il/M$R0Z,m'XD
JAVA中文站社区门户,W9K:zC+S}o:K2}
有关垃圾收集几点补充
*Ejh,Y'pb`
f!lJ T4p U  经过上述介绍说明可以发现垃圾回收有以下几个特点:
&t-SL*Kb k:@GSJ#B
/h8G:Fw%j ZDlr  (1)垃圾收集发生不可预知性:由于实现了区别垃圾收集算法和采用了区别收集机制所以它有可能是定时发生有可能是当出现系统空闲CPU资源时发生也有可能是和原始垃圾收集等到内存消耗出现极限时发生这和垃圾收集器选择和具体设置都有关系
km(V"{B_JAVA中文站社区门户/K9i,Z&EV1v$L
  (2)垃圾收集精确性:主要包括2 个方面:(a)垃圾收集器能够精确标记活着对象;(b)垃圾收集器能够精确地定位对象的间引用关系前者是完全地回收所有废弃对象前提否则就可能造成内存泄漏而后者则是实现归并和复制等算法必要条件所有不可达对象都能够可靠地得到回收所有对象都能够重新分配允许对象复制和对象内存缩并这样就有效地防止内存支离破碎 JAVA中文站社区门户Z6sE'My3O

'c'j;tE)C3qYn  (3)现在有许多种区别垃圾收集器每种有其算法且其表现各异既有当垃圾收集开始时就停止应用运行又有当垃圾收集开始时也允许应用线程运行还有在同时间垃圾收集多线程运行
t/[,l*\Ty"wSJAVA中文站社区门户&?9gHSTMR
  (4)垃圾收集实现和具体JVM 以及JVM内存模型有非常紧密关系区别JVM 可能采用区别垃圾收集而JVM 内存模型决定着该JVM可以采用哪些类型垃圾收集现在HotSpot 系列JVM中内存系统都采用先进面向对象框架设计这使得该系列JVM都可以采用最先进垃圾收集 JAVA中文站社区门户Kz0|.QL*S*H

/B3Hj8? r6rb  (5)随着技术发展现代垃圾收集技术提供许多可选垃圾收集器而且在配置每种收集器时候又可以设置区别参数这就使得根据区别应用环境获得最优应用性能成为可能 JAVA中文站社区门户|7` c:FE,^]W`

R"o`f3jhuj  针对以上特点我们在使用时候要注意: JAVA中文站社区门户'o L&O_*C#cLV%H

Od\5d6K NyP(B  (1)不要试图去假定垃圾收集发生时间切都是未知比如思路方法中个临时对象在思路方法完毕后就变成了无用对象这个时候它内存就可以被释放
wfx{g&qX
,tw a(]7It$C[N;L-?  (2)Java中提供了些和垃圾收集打交道而且提供了种强行执行垃圾收集思路方法--.gc但这同样是个不确定思路方法Java 中并不保证每次该思路方法就定能够启动垃圾收集它只不过会向JVM发出这样个申请到底是否真正执行垃圾收集切都是个未知数 JAVA中文站社区门户],c)V"dA
JAVA中文站社区门户X*p T)VLF;R1FR
  (3)挑选适合自己垃圾收集器般来说如果系统没有特殊和苛刻性能要求可以采用JVM缺省选项否则可以考虑使用有针对性垃圾收集器比如增量收集器就比较适合实时性要求较高系统的中系统具有较高配置有比较多闲置资源可以考虑使用并行标记/清除收集器 JAVA中文站社区门户5l|4|'D-M1g9}Oh

2{:Lsq9o+i].@  (4)关键也是难把握问题是内存泄漏良好编程习惯和严谨编程态度永远是最重要不要让自己个小导致内存出现大漏洞 JAVA中文站社区门户;zG(I`C/EU\T8uYg0k:k
JAVA中文站社区门户#`8|SHtm CZL6\:U
  (5)尽早释放无用对象引用大多数员在使用临时变量时候都是让引用变量在退出活动域(scope)后自动设置为null暗示垃圾收集器来收集该对象还必须注意该引用对象是否被监听如果有则要去掉监听器然后再赋空值 JAVA中文站社区门户6I#P7? n3{0^T`1x,\
JAVA中文站社区门户*f-VW6o;[5M%@3T
  结束语
6?h(o,aEl}
DLx_R  般来说Java开发人员可以不重视JVM中堆内存分配和垃圾处理收集但是充分理解Java特性可以让我们更有效地利用资源同时要注意finalize思路方法是Java缺省机制有时为确保对象资源明确释放可以编写自己finalize思路方法

TAG: Java JAVA java 机制 垃圾
Tags:  垃圾回收 垃圾回收机制 java垃圾回收 java垃圾回收机制

延伸阅读

最新评论

发表评论