??xml version="1.0" encoding="utf-8" standalone="yes"?> 本文发表?004q?月《CSDN开发高手?/p>
写作本文的初h惛_大家分n垃圾攉Q?Garbage Collection
Q技术简单而有的发展双Ӏ动W之前,我站在窗边,望了望正在小区里装运垃圾的清zR。和生活中环卫工Z清运垃圾的工作相|软g开发里的垃圾收集其?
是一U自动打扫和清除内存垃圾的技术,它可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽Q这和生zd圑֠塞排污管道的
危险q没有什么本质的不同Q,以及不恰当的内存释放所造成的内存非法引用(q类g我们在生zM买到了一瓶已l过期三q的牛奶Q? 据历史学家们介绍Q四千多q前的古埃及人已l在城市里徏设了完善的排污和垃圾清运设施Q一千多q前的中国h更是修筑了当时世界上保洁能力最强的都市
——长安。今天,当我们在软g开发中体验自动垃圾攉的便捷与舒适时Q我们至应当知道,q种拒绝杂ؕ、追求整z的“垃圾攉”_其实是hc自古以来就
已经具备了的? 国内的程序员大多是在 Java 语言中第一ơ感受到垃圾攉技术的巨大力的,许多Z因此?Java
和垃圾收集看成了密不可分的整体。但事实上,垃圾攉技术早?Java 语言问世?30 多年已l发展和成熟h了, Java
语言所做的不过是把q项奇的技术带Cq大E序员n边而已? 如果一定要为垃圾收集技术找一个孪生兄弟,那么Q?Lisp 语言才是当之无愧的h选?1960 q前后诞生于 MIT ?Lisp
语言是第一U高度依赖于动态内存分配技术的语言Q?Lisp 中几乎所有数据都?#8220;?#8221;的Ş式出玎ͼ?#8220;?#8221;所占用的空间则是在堆中动态分配得到的?
Lisp 语言先天具有的动态内存管理特性要?Lisp 语言的设计者必解军_中每一个内存块的自动释N题(否则Q?Lisp
E序员就必然被程序中不计其数?free ?delete
语句Ҏ(gu)Q,q直接导致了垃圾攉技术的诞生和发展——说句题外话Q上大学Ӟ一位老师曑֑诉我们, Lisp
是对C软g开发技术A(ch)献最大的语言。我当时对这一说法不以为然Q布满了圆括P看上dq宫一L Lisp 语言怎么能比 C 语言?
Pascal
语言更伟大呢Q不q现在,当我知道垃圾攉技术、数据结构技术、h工智能技术、ƈ行处理技术、虚拟机技术、元数据技术以及程序员们耳熟能详的许多技术都?
源于 Lisp 语言Ӟ我特别想向那位老师当面道歉Qƈ收回我当时的q稚x? 知道?Lisp 语言与垃圾收集的密切关系Q我们就不难理解Qؓ什么垃圾收集技术的两位先驱?J. McCarthy ?M. L.
Minsky 同时也是 Lisp 语言发展史上的重要h物了?J. McCarthy ?Lisp 之父Q他在发?Lisp
语言的同时也W一ơ完整地描述了垃圾收集的法和实现方式; M. L. Minsky 则在发展 Lisp
语言的过E中成ؓ了今天好几种L垃圾攉法的奠Zh——和当时不少技术大师的l历怼Q?J. McCarthy ?M. L. Minsky
在许多不同的技术领域里都取得了令h艳M的成。也许,?1960
q代那个软g开发史上的拓荒时代里,思维敏捷、意志坚定的研究者更Ҏ(gu)成ؓ无所不能的西部硬汉吧? 在了解垃圾收集算法的h之前Q有必要先回一下内存分配的主要方式。我们知道,大多C的语言或运行环境都支持三种最基本的内存分配方式,它们分别是: 一、静态分配( Static Allocation Q:静态变量和全局变量的分配Ş式。我们可以把静态分配的内存看成是家里的耐用家具。通常Q它们无需释放和回Ӟ因ؓ没h会天天把大衣柜当作垃圾扔到窗外? 二、自动分配( Automatic Allocation
Q:在栈中ؓ局部变量分配内存的Ҏ(gu)。栈中的内存可以随着代码块退出时的出栈操作被自动释放。这cM于到家中串门的访客,天色一晚就要各回各Ӟ除了个别
不识时务者以外,我们一般没必要把客人捆在垃圾袋里扫地出门? 三、动态分配( Dynamic Allocation
Q:在堆中动态分配内存空间以存储数据的方式。堆中的内存块好像我们日怋用的巾U,用过了就得扔到垃圄里,否则屋内׃满地D。像我这L懒h?
梦都x一台家用机器h跟在w边打扫卫生。在软g开发中Q如果你懒得释放内存Q那么你也需要一台类似的机器人——这其实是一个由特定法实现的垃圾收?
器? 也就是说Q下面提到的所有垃圾收集算法都是在E序q行q程中收集ƈ清理废旧“巾U?#8221;的算法,它们的操作对象既不是静态变量,也不是局部变量,而是堆中所有已分配内存块? 1960 q以前,Z胎中?Lisp 语言设计垃圾攉机制ӞW一个想到的法是引用计数算法。拿巾U的例子来说Q这U算法的原理大致可以描述为: 午餐ӞZ把脑子里H然跛_来的设计灉|C来,我从巾U袋中抽Z张餐巄Q打在上面dpȝ架构的蓝图。按?#8220;巾U怋用规U之引用计数
?#8221;的要求,d之前Q我必须先在巾U的一角写上计数?1
Q以表示我在使用q张巾U。这Ӟ如果你也想看看我ȝ蓝图Q那你就要把巾U怸的计数值加 1 Q将它改?2 Q这表明目前?2
个h在同时用这张餐巄Q当Ӟ我是不会允许你用q张巾U来擦E涕的Q。你看完后,必须把计数值减 1
Q表明你对该巾U的使用已经l束。同P当我餐巄上的内容全部誊写到笔记本上之后,我也会自觉地把餐巄上的计数值减 1
。此Ӟ不出意外的话Q这张餐巄上的计数值应当是 0
Q它会被垃圾攉器——假N是一个专门负责打扫卫生的机器人——捡h扔到垃圾里Q因为垃圾收集器的惟一使命是扑ֈ所有计数gؓ 0
的餐巄q清理它们? 引用计数法的优点和~陷同样明显。这一法在执行垃圾收集Q务时速度较快Q但法对程序中每一ơ内存分配和指针操作提出了额外的要求Q增加或减少
内存块的引用计数Q。更重要的是Q引用计数算法无法正释攑@环引用的内存块,Ҏ(gu)Q?D. Hillis 有一D风而精辟的Q? 一天,一个学生走?Moon 面前_“我知道如何设计一个更好的垃圾攉器了。我们必记录指向每个结点的指针数目?#8221; Moon 耐心地给q位学生讲了下面q个故事Q?#8220;一天,一个学生走?Moon 面前_‘我知道如何设计一个更好的垃圾攉器了……’” D. Hillis
的故事和我们时候常说的“从前有山,׃有个庙,庙里有个老和?#8221;的故事有异曲同工之妙。这说明Q单是用引用计数算法还不以解军_圾收集中的所?
问题。正因ؓ如此Q引用计数算法也常常被研I者们排除在狭义的垃圾攉法之外。当Ӟ作ؓ一U最单、最直观的解x案,引用计数法本nh其不可替
代的优越性?1980 q代前后Q?D. P. Friedman Q?D. S. Wise Q?H. G. Baker
{h对引用计数算法进行了数次改进Q这些改q得引用计数算法及其变U(如gq计数算法等Q在单的环境下,或是在一些综合了多种法的现代垃圾收集系l?
中仍然可以一展n手? W一U实用和完善的垃圾收集算法是 J. McCarthy {h?1960 q提出ƈ成功地应用于 Lisp 语言的标讎ͼ清除法。仍以餐巄ZQ标讎ͼ清除法的执行过E是q样的: 午餐q程中,厅里的所有h都根据自q需要取用餐巄。当垃圾攉机器人想攉废旧巾U的时候,它会让所有用的人先停下来,然后Q依ơ询问餐
厅里的每一个hQ?#8220;你正在用巾U吗Q你用的是哪一张餐巄Q?#8221;机器人根据每个h的回{将Z正在使用的餐巄M记号。询问过E结束后Q机器h在餐厅里
L所有散落在桌上且没有记号的餐巄Q这些显焉是用q的废旧巾U)Q把它们l统扔到垃圾里? 正如其名U所暗示的那P标记Q清除算法的执行q程分ؓ“标记”?#8220;清除”两大阶段。这U分步执行的思\奠定了现代垃圾收集算法的思想基础。与引用
计数法不同的是Q标讎ͼ清除法不需要运行环境监每一ơ内存分配和指针操作Q而只要在“标记”阶段中跟t每一个指针变量的指向——用cM思\实现的垃
圾收集器也常被后人统UCؓ跟踪攉器( Tracing Collector Q? 伴随着 Lisp 语言的成功,标记Q清除算法也在大多数早期?Lisp
q行环境中大攑ּ彩。尽最初版本的标记Q清除算法在今天看来q存在效率不高(标记和清除是两个相当耗时的过E){诸多缺P但在后面的讨ZQ我们可?
看到Q几乎所有现代垃圾收集算法都是标讎ͼ清除思想的gl,仅此一点, J. McCarthy {h在垃圾收集技术方面的贡献׃毫不亚于他们?
Lisp 语言上的成就了? Z解决标记Q清除算法在垃圾攉效率斚w的缺P M. L. Minsky ?1963 q发表了著名的论?#8220;一U用双存储区的
Lisp 语言垃圾攉器( A LISP Garbage Collector Algorithm Using Serial Secondary
Storage Q?#8221;?M. L. Minsky 在该论文中描q的法被h们称为复制算法,它也?M. L. Minsky 本h成功地引入到?
Lisp 语言的一个实现版本中? 复制法别出心裁地将堆空间一分ؓ二,q用简单的复制操作来完成垃圾收集工作,q个思\相当有趣。借用巾U的比喻Q我们可以这L?M. L. Minsky 的复制算法: 厅被垃圾收集机器h分成南区和北Z个大完全相同的部分。午时Q所有h都先在南区用(因ؓI间有限Q用h数自然也减一半)Q用时?
以随意用餐巄。当垃圾攉机器为有必要回收废旧巾U时Q它会要求所有用者以最快的速度从南{Ud北区Q同旉w携带自己正在用的巾U?
{所有h都{Ud北区之后Q垃圾收集机器h只要单地把南Z所有散落的巾U扔q垃圄q完成d了。下一ơ垃圾收集的工作q程也大致类|惟一的不
同只是h们的转移方向变成了从北区到南区。如此@环往复,每次垃圾攉都只需单地转移Q也是复制Q一ơ,垃圾攉速度无与伦比——当Ӟ对于用餐者往
q奔波于南北两区之间的辛劻I垃圾攉机器人是决不会流露出丝毫怜?zhn)的? M. L. Minsky
的发明绝对算得上一U奇思妙惟뀂分区、复制的思\不仅大幅提高了垃圾收集的效率Q而且也将原本J纷复杂的内存分配算法变得前所未有地简明和DQ既然每?
内存回收都是Ҏ(gu)个半区的回收Q内存分配时也就不用考虑内存片{复杂情况,只要Ud堆顶指针Q按序分配内存可以了Q,q简直是个奇q!不过QQ何奇
q的出现都有一定的代h(hun)Q在垃圾攉技术中Q复制算法提高效率的代h(hun)是h为地可用内存羃?yu)了一半。实话实_q个代h(hun)未免也太高了一些? 无论优缺点如何,复制法在实践中都获得了可以与标讎ͼ清除法相比拟的成功。除?M. L. Minsky 本h?Lisp
语言中的工作以外Q从 1960 q代末到 1970 q代初, R. R. Fenichel ?J. C. Yochelson {h也相l在
Lisp 语言的不同实C对复制算法进行了改进Q?S. Arnborg 更是成功地将复制法应用C Simula 语言中? xQ垃圾收集技术的三大传统法——引用计数算法、标讎ͼ清除法和复制算法——都已在 1960
q前后相l问世,三种法各有所长,也都存在致命的缺陗从 1960
q代后期开始,研究者的主要_֊逐渐转向对这三种传统法q行改进或整合,以扬镉K短,适应E序设计语言和运行环境对垃圾攉的效率和实时性所提出的更?
要求? ?1970
q代开始,随着U学研究和应用实늚不断深入Qh们逐渐意识刎ͼ一个理想的垃圾攉器不应在q行时导致应用程序的暂停Q不应额外占用大量的内存I间?
CPU
资源Q而三U传l的垃圾攉法都无法满些要求。h们必L出更新的法或思\Q以解决实践中碰到的诸多N。当Ӟ研究者的努力目标包括Q? W一Q提高垃圾收集的效率。用标讎ͼ清除法的垃圾收集器在工作时要消耗相当多?CPU 资源。早期的 Lisp
q行环境攉内存垃圾的时间竟占到了系l总运行时间的 40% Q——垃圾收集效率的低下直接造就?Lisp
语言在执行速度斚w的坏名声Q直C天,许多条g反射似地误以为所?Lisp E序都奇慢无比? W二Q减垃圾收集时的内存占用。这一问题主要出现在复制算法中。尽复制算法在效率上获得了质的H破Q但牺牲一半内存空间的代h(hun)仍然是巨大的。在计算机发展的早期Q在内存h?KB 计算的日子里Q浪费客L一半内存空间简直就是在变相敲诈或拦路打劫? W三Q寻扑֮时的垃圾攉法。无论执行效率如何,三种传统的垃圾收集算法在执行垃圾攉d旉必须打断E序的当前工作。这U因垃圾攉而造成?
延时是许多程序,特别是执行关键Q务的E序没有办法容忍的。如何对传统法q行改进Q以便实CU在后台(zhn)?zhn)执行Q不影响——或臛_看上M影响——当?
q程的实时垃圾收集器Q这昄是一件更h战性的工作? 研究者们探寻未知领域的决心和研究工作的进展速度同样令h惊奇Q在 1970 q代?1980
q代的短短十几年中,一大批在实用系l中表现优异的新法和新思\脱颖而出。正是因为有了这些日成熟的垃圾攉法Q今天的我们才能?Java ?
.NET 提供的运行环境中随心所Ʋ地分配内存块,而不必担心空间释放时的风险? 标记Q整理算法是标记Q清除算法和复制法的有机结合。把标记Q清除算法在内存占用上的优点和复制算法在执行效率上的牚wl合hQ这是所有h都希
望看到的l果。不q,两种垃圾攉法的整合ƈ不像 1 ?1 {于 2 那样单,我们必须引入一些全新的思\?1970 q前后, G. L.
Steele Q?C. J. Cheney ?D. S. Wise {研I者陆l找C正确的方向,标记Q整理算法的轮廓也逐渐清晰了v来: 在我们熟(zhn)的厅里,q一ơ,垃圾攉机器Z再把厅分成两个南北区域了。需要执行垃圾收集Q务时Q机器h先执行标讎ͼ清除法的第一个步骤,?
所有用中的餐巄d标记Q然后,机器人命令所有就者带上有标记的餐巄向餐厅的南面集中Q同时把没有标记的废旧餐巄扔向厅北面。这样一来,机器
人只消站在餐厅北面,怀抱垃圄Q迎接扑面而来的废旧餐巄p了? 实验表明Q标讎ͼ整理法的M执行效率高于标记Q清除算法,又不像复制算法那样需要牺牲一半的存储I间Q这昄是一U非常理想的l果。在许多C的垃圾收集器中,Z都用了标记Q整理算法或其改q版本? 对实时垃圾收集算法的研究直接D了增量收集算法的诞生? 最初,Z关于实时垃圾攉的想法是q样的:Zq行实时的垃圾收集,可以设计一个多q程的运行环境,比如用一个进E执行垃圾收集工作,另一个进E执行程序代码。这样一来,垃圾攉工作看上d仿佛(jng)是在后台(zhn)?zhn)完成的,不会打断E序代码的运行? 在收集餐巄的例子中Q这一思\可以被理解ؓQ垃圾收集机器h在h们用的同时L废弃的餐巄q将它们扔到垃圾里。这个看似简单的思\会在设计
和实现时Cq程间冲H的N。比如说Q如果垃圾收集进E包括标记和清除两个工作阶段Q那么,垃圾攉器在W一阶段中辛辛苦苦标记出的结果很可能被另一?
q程中的内存操作代码修改得面目全非,以至于第二阶D늚工作没有办法开展? M. L. Minsky ?D. E. Knuth 对实时垃圾收集过E中的技术难点进行了早期的研IӞ G. L. Steele ?
1975 q发表了题ؓ“多进E整理的垃圾攉Q?Multiprocessing compactifying garbage
collection Q?#8221;的论文,描述了一U被后hUCؓ“ Minsky-Knuth-Steele 法”的实时垃圾收集算法?E. W.
Dijkstra Q?L. Lamport Q?R. R. Fenichel ?J. C. Yochelson
{h也相l在此领域做Z各自的A(ch)献?1978 q_ H. G. Baker 发表?#8220;串行计算Z的实时表处理技术( List
Processing in Real Time on a Serial Computer
Q?#8221;一文,pȝ阐述了多q程环境下用于垃圾收集的增量攉法? 增量攉法的基仍是传统的标讎ͼ清除和复制算法。增量收集算法通过对进E间冲突的妥善处理,允许垃圾攉q程以分阶段的方式完成标记、清理或?
制工作。详l分析各U增量收集算法的内部机理是一件相当繁琐的事情Q在q里Q读者们需要了解的仅仅是: H. G. Baker
{h的努力已l将实时垃圾攉的梦惛_成了现实Q我们再也不用ؓ垃圾攉打断E序的运行而烦g? 和大多数软g开发技术一Pl计学原理总能在技术发展的q程中v到强力催化剂的作用?1980
q前后,善于在研I中使用l计分析知识的技术h员发玎ͼ大多数内存块的生存周期都比较短,垃圾攉器应当把更多的精力放在检查和清理新分配的内存块上。这
个发现对于垃圾收集技术的价值可以用巾U的例子概括如下Q? 如果垃圾攉机器够聪明,事先摸清了餐厅里每个人在用餐时用餐巄的习惯——比如有些h喜欢在用前后各用掉一张餐巄Q有的h喜欢自始至终
攥着一张餐巄不放Q有的h则每打一个喷嚏就用去一张餐巄——机器h可以制定出更完善的巾U回收计划,qL在h们刚扔掉巾U没多久把垃圾?
走。这U基于统计学原理的做法当然可以让厅的整z度成倍提高? D. E. Knuth Q?T. Knight Q?G. Sussman ?R. Stallman
{h对内存垃圄分类处理做了最早的研究?1983 q_ H. Lieberman ?C. Hewitt
发表了题?#8220;Z对象寿命的一U实时垃圾收集器Q?A real-time garbage collector based on the
lifetimes of objects Q?#8221;的论文。这著名的论文标志着分代攉法的正式诞生。此后,?H. G. Baker Q?R.
L. Hudson Q?J. E. B. Moss {h的共同努力下Q分代收集算法逐渐成ؓ了垃圾收集领域里的主技术? 分代攉法通常堆中的内存块按寿命分ؓ两类Q年老的和年ȝ。垃圾收集器使用不同的收集算法或攉{略Q分别处理这两类内存块,q特别地把主?
工作旉花在处理q轻的内存块上。分代收集算法垃圾攉器在有限的资源条件下Q可以更为有效地工作——这U效率上的提高在今天?Java
虚拟Z得到了最好的证明? Lisp 是垃圾收集技术的W一个受益者,但显然不是最后一个。在 Lisp
语言之后Q许许多多传l的、现代的、后C的语a已经把垃圾收集技术拉入了自己的怀抱。随便D几个例子吧:诞生?1964 q的 Simula
语言Q?1969 q的 Smalltalk 语言Q?1970 q的 Prolog 语言Q?1973 q的 ML 语言Q?1975 q的
Scheme 语言Q?1983 q的 Modula-3 语言Q?1986 q的 Eiffel 语言Q?1987 q的 Haskell
语言……它们都先后用了自动垃圾攉技术。当Ӟ每一U语a使用的垃圾收集算法可能不相同,大多数语a和运行环境甚臛_时用了多种垃圾攉法。但
无论怎样Q这些实例都说明Q垃圾收集技术从诞生的那一天v׃是一U曲高和寡的“学院z?#8221;技术? 对于我们熟?zhn)?C ?C++ 语言Q垃圾收集技术一样可以发挥巨大的功效。正如我们在学校中就已经知道的那P C ?C++
语言本nq没有提供垃圾收集机Ӟ但这q不妨碍我们在程序中使用h垃圾攉功能的函数库或类库。例如,早在 1988 q_ H. J. Boehm
?A. J. Demers 成功地实现了一U用保守垃圾收集算法( Conservative GC Algorithmic
Q的函数库(参见 http://www.hpl.hp.com/personal/Hans_Boehm/gc Q。我们可以在 C 语言?C++ 语言中用该函数库完成自动垃圾收集功能,必要Ӟ甚至q可以让传统?C/C++ 代码与用自动垃圾收集功能的 C/C++ 代码在一个程序里协同工作? 1995 q诞生的 Java 语言在一夜之间将垃圾攉技术变成了软g开发领域里最为流行的技术之一。从某种角度_我们很难分清I竟?
Java 从垃圾收集中受益Q还是垃圾收集技术本w?Java 的普及而扬名。值得注意的是Q不同版本的 Java
虚拟Z用的垃圾攉机制q不完全相同Q?Java 虚拟机其实也l过了一个从单到复杂的发展过E。在 Java 虚拟机的 1.4.1
版中Qh们可以体验到的垃圾收集算法就包括分代攉、复制收集、增量收集、标讎ͼ整理、ƈ行复Ӟ Parallel Copying Q、ƈ行清除(
Parallel Scavenging Q、ƈ发( Concurrent Q收集等许多U, Java
E序q行速度的不断提升在很大E度上应该归功于垃圾攉技术的发展与完善? 管历史上已l有许多包含垃圾攉技术的应用q_和操作系l出玎ͼ?Microsoft .NET
却是W一U真正实用化的、包含了垃圾攉机制的通用语言q行环境。事实上Q?.NET q_上的所有语aQ包?C# ?Visual Basic
.NET ?Visual C++ .NET ?J# {等Q都可以通过几乎完全相同的方式?.NET
q_提供的垃圾收集机制。我们似乎可以断aQ?.NET
是垃圾收集技术在应用领域里的一ơ重大变革,它垃圾攉技术从一U单U的技术变成了应用环境乃至操作pȝ中的一U内在文化。这U变革对未来软g开发技?
的媄响力也许要远q超q?.NET q_本n的商业h(hun)倹{? 今天Q致力于垃圾攉技术研I的Z仍在不懈努力Q他们的研究方向包括分布式系l的垃圾攉、复杂事务环境下的垃圾收集、数据库{特定系l的垃圾攉{等? 但在E序员中_仍有不少人对垃圾攉技术不屑一,他们宁愿怿自己逐行~写?free ?delete 命oQ也不愿把垃圾收集的重Q交给那些在他们看来既蠢又W的垃圾攉器? 我个为,垃圾攉技术的普及是大势所,q就像生zM来好一h庸置疑。今天的E序员也怼因ؓ垃圾攉器要占用一定的 CPU
资源而对其望而却步,但二十多q前的程序员q曾因ؓ高语言速度太慢而坚持用机器语言写程序呢Q在g速度日新月异的今天,我们是要吝惜那一点儿旉损?
而踟w不前,q是该坚定不Ud站在代码和运行环境的净化剂——垃圾收集的一边呢Q? [王咏刚,2003q?2月]
不过配置代码都是常用的,留着备䆾吧?br />mavne pox 配置
2 <groupId>javax.mail</groupId>
3 <artifactId>mail</artifactId>
4 <version>1.4</version>
5 </dependency>
6 <dependency>
7 <groupId>org.springframework</groupId>
8 <artifactId>spring-beans</artifactId>
9 <version>${org.springframework.version}</version>
10 </dependency>
spring-xml 配置
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:cfg/config.properties</value>
</list>
</property>
</bean>
<bean id="mail"
class="org.springframework.mail.javamail.JavaMailSenderImpl">
<!-- SMTP发送邮件的服务器的IP和端?nbsp;-->
<property name="host" value="${mail.host}" />
<property name="port" value="${mail.port}" />
<!-- 登陆SMTP邮g发送服务器的用户名和密?nbsp;-->
<property name="username" value="${mail.username}" />
<property name="password" value="${mail.password}" />
<!-- 获得邮g会话属?验证d邮g服务器是否成?/span>-->
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="prop">true</prop>
<prop key="mail.smtp.timeout">25000</prop>
</props>
</property>
</bean>
properties 文g配置
mail.port=25
mail.username=****
mail.password=****
import com.ms.AppContext;
public class SpringHelper {
/**
* 获取spring依赖注入的对?br /> *
* @param name
* @return Object Bean
*/
public static Object getBean(String name) {
AbstractApplicationContext ctx = AppContext.getInstance()
.getAppContext();
return ctx.getBean(name);
}
}
import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppContext {
private static AppContext instance;
private volatile AbstractApplicationContext appContext;
public synchronized static AppContext getInstance() {
if (instance == null) {
instance = new AppContext();
}
return instance;
}
private AppContext() {
List<String> list = new ArrayList<String>();
list.add("/cfg/*.xml");
String ss[] = list.toArray(new String[] {});
for (int i = 0; i < ss.length; i++) {
System.out.println("ss[" + i + "]" + ss[i]);
}
this.appContext = new ClassPathXmlApplicationContext(ss);
}
public AbstractApplicationContext getAppContext() {
return appContext;
}
}
import java.io.File;
import java.util.Date;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import com.ms.SpringHelper;
import com.util.Configuration;
/**
* 发送邮?nbsp;工具
*
* @author
* @file com.ms.util --- SentMaileUtil.java
* @version 2013-2-28 -下午03:42:03
*/
public class SendMaileUtil {
private static JavaMailSender javaMailSender;
private static Logger logger = Logger.getLogger(SendMaileUtil.class);
private static JavaMailSender newIntstance() {
if (javaMailSender == null) {
javaMailSender = (JavaMailSender) SpringHelper.getBean("mail");
}
return javaMailSender;
}
/**
* 发送的文本试邮g
*
* @param to
* @param mailSubject
* @param mailBody
*/
public static void sendTextMaile(String to, String mailSubject,
String mailBody) {
if (logger.isDebugEnabled())
logger.debug("准备发送文本Ş式的邮g");
SimpleMailMessage mail1 = new SimpleMailMessage();
String from = Configuration.getValue("mail.form");
mail1.setFrom(from);// 发送h名片
mail1.setTo(to);// 收g人邮?/span>
mail1.setSubject(mailSubject);// 邮g主题
mail1.setSentDate(new Date());// 邮g发送时?/span>
mail1.setText(mailBody);
// 发
SimpleMailMessage[] mailMessages = { mail1 };
newIntstance().send(mailMessages);
if (logger.isDebugEnabled())
logger.debug("文本形式的邮件发送成功!Q!");
}
/**
* ?nbsp;HTML脚本形式邮g发?br /> *
* @param to
* @param mailSubject
* @param mailBody
*/
public static void sendHtmlMail(String to, String mailSubject,
String mailBody) {
JavaMailSender mailSender = newIntstance();
MimeMessage mimeMessage = mailSender.createMimeMessage();
try {
if (logger.isDebugEnabled())
logger.debug("HTML脚本形式邮g正在发?img src="http://www.tkk7.com/Images/dot.gif" alt="" />");
// 讄utf-8或GBK~码Q否则邮件会有ؕ?/span>
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true,
"UTF-8");
// 讄发送h名片
String from = Configuration.getValue("mail.form");
helper.setFrom(from);
// 讄收g人名片和地址
helper.setTo(new InternetAddress("\""
+ MimeUtility.encodeText("gamil邮箱") + "\" <" + to + ">"));// 发送?br /> // 邮g发送时?/span>
helper.setSentDate(new Date());
// 讄回复地址
helper.setReplyTo(new InternetAddress(from));
// 讄抄送的名片和地址
// helper.setCc(InternetAddress.parse(MimeUtility.encodeText("抄送h001")
// + " <@163.com>," + MimeUtility.encodeText("抄送h002")
// + " <@foxmail.com>"));
// 主题
helper.setSubject("ݞ피언쉽");
// 邮g内容Q注意加参数trueQ表C启用html格式
helper
.setText(
"<html><head></head><body><h1>hello!!我是乔布?lt;/h1></body></html>",
true);
// 发?/span>
mailSender.send(mimeMessage);
} catch (Exception e) {
e.printStackTrace();
}
if (logger.isDebugEnabled())
logger.debug("HTML脚本形式邮g发送成功!Q!");
}
/**
* 以附件的形式发送邮?br /> *
* @param to
* 收g人eamil 地址
* @param toName
* 收g人昵U?br /> * @param mailSubject
* 主题
* @param mailBody
* 内容?br /> * @param files
* 附g
*/
public static void sendFileMail(String to, String toName,
String mailSubject, String mailBody, File[] files) {
JavaMailSender mailSender = newIntstance();
MimeMessage mimeMessage = mailSender.createMimeMessage();
try {
if (logger.isDebugEnabled())
logger.debug("带附件和囄的邮件正在发?img src="http://www.tkk7.com/Images/dot.gif" alt="" />");
// 讄utf-8或GBK~码Q否则邮件会有ؕ?/span>
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true,
"UTF-8");
// 讄发送h名片
String from = Configuration.getValue("mail.form");
helper.setFrom(from);
// 讄收g人邮?/span>
helper.setTo(new InternetAddress("\""
+ MimeUtility.encodeText(toName) + "\" <" + to + ">"));
// 讄回复地址
// helper.setReplyTo(new InternetAddress("@qq.com"));
// 讄收g人抄送的名片和地址(相当于群发了)
// helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001")
// + " <@163.com>," + MimeUtility.encodeText("邮箱002")
// + " <@foxmail.com>"));
// 主题
helper.setSubject(mailSubject);
// 邮g内容Q注意加参数trueQ表C启用html格式
helper.setText(mailBody);
if (files != null && files.length > 0) {
for (int i = 0; i < files.length; i++)
// 加入附g
helper.addAttachment(MimeUtility.encodeText(files[i]
.getName()), files[i]);
}
// 加入插图
helper.addInline(MimeUtility.encodeText("pic01"), new File(
"c:/temp/2dd24be463.jpg"));
// 发?/span>
mailSender.send(mimeMessage);
} catch (Exception e) {
e.printStackTrace();
}
if (logger.isDebugEnabled()) {
logger.debug("带附件和囄的邮件发送成功!Q!");
}
}
public static void main(String[] args) {
PropertyConfigurator.configure(ClassLoader
.getSystemResource("cfg/log4j.properties"));
SendMaileUtil.sendTextMaile("*****@gmail.com",
"Spring Mail 试邮g", "Hello,Boy,This is my Spring Mail,哈哈Q!");
SendMaileUtil.sendHtmlMail("*****@gmail.com", null, null);
File file = new File("c:/temp");
File[] fs = file.listFiles();
SendMaileUtil.sendFileMail("******@yeah.net", "늧", "主题", "内容",
fs);
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import com.pub.Forward;
/**
* dproperties文g
* @author
*
*/
public class Configuration
{
private static Properties propertie;
private InputStream in;
private static Configuration config = new Configuration();
/**
* 初始化Configurationc?br /> */
public Configuration()
{
propertie = new Properties();
try {
// System.out.println(System.getProperty("user.dir"));
// inputFile = new FileInputStream("cfg/config.properties");
in = ClassLoader.getSystemResourceAsStream("cfg/config.properties");
propertie.load(in);
in.close();
} catch (FileNotFoundException ex) {
System.out.println("d属性文?-->p|Q? 原因Q文件\径错误或者文件不存在");
ex.printStackTrace();
} catch (IOException ex) {
System.out.println("装蝲文g--->p|!");
ex.printStackTrace();
}
}
/**
* 重蝲函数Q得到key的?br /> * @param key 取得其值的?br /> * @return key的?br /> */
public static String getValue(String key)
{
if(propertie.containsKey(key)){
String value = propertie.getProperty(key);//得到某一属性的?/span>
return value;
}
else
return "";
}//end getValue()
public static void main(String[] args)
{
System.out.println(Configuration.getValue("aaa"));
System.out.println(System.getProperty("user.dir"));
}//end main()
}//end class ReadConfigInfo
]]>
2 List<String> list = new ArrayList<String>();
3 list.add("a");
4 list.add("b");
5 list.add("c");
6 list.add("136");
7 list.add("14");
8 list.add("f");
9
10 for(int i = 0;i<list.size();i++){
11 String str = list.get(i);
12 if(isNumeric(str)){
13 list.remove(i);
14 }
15 }
16 for(int i = 0;i<list.size();i++){
17 System.out.println(list.get(i));
18 }
19 }
20 public static boolean isNumeric(String str) { // 判断字符串是否ؓ数字
21 Pattern pattern = Pattern.compile("[0-9]*");
22 Matcher isNum = pattern.matcher(str);
23 if (!isNum.matches()) {
24 return false;
25 }
26 return true;
27 }
]]>垃圾攉史
拓荒时代
引用计数Q?Reference Counting Q算?/h3>
标记Q清除( Mark-Sweep Q算?/h3>
复制Q?Copying Q算?/h3>
走向成熟
标记Q整理( Mark-Compact Q算?/h3>
增量攉Q?Incremental Collecting Q算?/h3>
分代攉Q?Generational Collecting Q算?/h3>
应用潮
大势所?/h3>
]]>
那本谭浩Z~的Java入门教材_
……
1、简单?/font>
设计Java语言的出发点是Ҏ(gu)~程Q不需要深奥的知识。Java语言的风格十分接qC++语言Q但要比C++单得多。Java舍弃了一些不常用的、难以理解的、容易淆的成分Q如q算W重载、多l承{。增加了自动垃圾搜集功能Q用于回收不再用的内存区域。这不但使程序易于编写,而且大大减少了由于内存分配而引发的问题?/font>
……
q样cM的描q出现在众多的Java入门U教材中Q非常容易让Z忽略了内存垃圑֛收的问题Q其实Java的垃圑֛攉q是需要关注一下的。这个问题在招聘单位的笔试题中出现的频率也比较高Q我们需要好好的研究一下Java的垃圑֛收机制?/p>
4.5.1 什么是内存垃圾Q哪些内存符合垃圄标准
我们在前面讲q了Q堆是一?q行?数据区,是通过"new"{指令徏立的QJava的堆是由Java的垃圑֛收机制来负责处理的,堆是动态分配内存大,垃圾攉器可以自动回收不再用的内存I间?/p>
也就是说Q所谓的"内存垃圾"是指在堆上开辟的内存I间在不用的时候就变成?垃圾"?/p>
C++或其他程序设计语a中,必须q序员自行声明产生和回Ӟ否则其中的资源将消耗,造成资源的浪费甚x机。但手工回收内存往往是一复杂而艰巨的工作。因预先定占用的内存空间是否应该被回收是非常困隄Q如果一D늨序不能回收内存空_而且在程序运行时pȝ中又没有了可以分配的内存I间Ӟq段E序只能崩溃?/p>
Java和C++相比的优势在于,q部?垃圾"可以被Java 虚拟机(JVMQ中的一个程序发现ƈ自动清除掉,而不用程序员自己想着"delete"了?/p>
Java语言提供了一个系l的线E,卛_圾收集器U程QGarbage Collection ThreadQ,来跟t每一块分配出ȝ内存I间Q当JVM处于I闲循环Ӟ自动回收每一块可以回收的内存?/p>
4.5.1.1 垃圾回收工作机制
垃圾攉器线E它是一U低优先U的U程Q它必须在一个JavaE序的运行过E中出现内存I闲的时候才去进行回收处理?/p>
垃圾攉器系l有其判断内存块是否需要回收的判断标准的。垃圾收集器完全是自动被执行的,它不能被强制执行Q即使程序员能明地判断出某一块内存应该被回收了,也不能强制执行垃圑֛收程序进行垃圑֛收?/p>
E序员可以做的只有调?System.gc()"?"执行垃圾攉器程序,但是q个垃圾攉E序什么时候被执行以及是否被执行了Q都是不不能控制的。但是虽然垃圾收集器是低优先U的U程Q却在系l内存可用量q低Ӟ它仍然可能会H发地执行来挽救pȝ?/p>
4.5.1.2 哪些W合"垃圾"标准
如果想了解JVM的垃圑֛Ӟ必要知道JVM垃圾回收的标准?/p>
垃圾攉器的"垃圾"标准Q对象已l不能被E序中的其他E序所引用的时候,那么q个对象的内存空间已l没有用了?/p>
比如当一个方法执行完毕时Q在q个Ҏ(gu)中声明的对象p出其声明周期Q这时候就可以被当作垃圾收集了Q只有当q个Ҏ(gu)被再ơ被调用时才会被重新创徏?/p>
例如Q?/p>
…… |
另外q可以将对象的引用变量初始化为null|也可以来暗示垃圾攉器来攉该对象?/p>
例如Q?/p>
…… |
finalize()在该对象垃圾回收前调?/strong>
垃圾攉器跟t每一个对象,把那些不可到辄对象占有的内存空间收集v来,q且在每ơ进行垃圾收集之前,垃圾攉器都会调用一下finalize()Ҏ(gu)。Java语言允许E序员给M对象dfinalize( )Ҏ(gu)Q但也不能过分依赖该Ҏ(gu)对系l资源的回收和再利用Q因个方法调用后的执行结果是不可预知的。对于Q何给定对象,Java 虚拟机最多只调用一? finalize Ҏ(gu)?
我们用这个程序来演示一下:
public class finalizeTest{ |
java -Xmx1k finalizeTest |
q时候,我们JVM所许可使用的最大内存设|成"1k"Q当内存被占满前JVM会首先去q行内存回收Q于是失L动的"test"对象被回Ӟ在回收前调用?finalize()"?br />
4.5.2 JVM垃圾回收的相关知?/strong>
JVM使用的是分代垃圾回收的方式,主要是因为在E序q行的时候会有如下特点:
大多数对象在创徏后很快就没有对象使用它了?/p>
大多数在一直被使用的对象很再d用新创徏的对象?/p>
因此将Java对象分ؓ"q轻"对象?q?对象QJVM内存堆QHeapQ分Z个区域,一个是"q轻"区,另一个是"?区,Java这两个区域分别UC?新生??老生??/p>
"新生?区域中,l大多数新创建的对象都存攑֜q个区域里,此区域一般来说较?yu)而且垃圾回收频率较高Q同时因?新生?采用的算法和其存攄对象的特点,使该区域垃圾回收的效率也非常高?/p>
?老生?区域中存攄是在"新生?中生存了较长旉的对象,q些对象被转移?老生?区。这个区域一般要大一些而且增长的速度相对?新生?要慢一些,"老生?垃圾回收的执行频率也会低很多?/p>
׃JVM在垃圑֛收处理时会消耗一定的pȝ资源Q因此有时候通过JVM启动的时候添加相兛_数来控制"新生?区域的大,来调整垃圑֛收处理的频率非常有用。以便于我们更合理的利用pȝ资源?/p>
"新生?区域讄参数?-Xmn"Q用q个参数可以制定"新生?区域的大?/p>
我们来D一个例子说明:
我们qpȝ自带的程序作Z子,在命令行上键入如下指令:
CD C:"java"demo"jfc"SwingSet2[回R] |
[GC [DefNew: 3469K->84K(3712K), 0.0007778 secs] |
我们需要解释一下输出的详细内容的意思,拿第一行输出来_
"DefNew: 3469K->84K(3712K), 0.0007778 secs"是指"新生?的垃圑֛收情况,q里的意思是从占?469K内存I间变ؓ84K内存I间Q用?.0007778U?/p>
"23035K->19679K(28728K), 0.0009191 secs"是指MGC的回收情况,整体堆空间占用从23035K降低?9679K的水qI用时0.0009191U?/p>
那么Q这时候我们在?新生?的内存设?MQƈ把堆的最大可控D定ؓ32MQ再L行,键入如下指oQ?/p>
java -jar -verbose:gc -Xmn8m -Xmx32m |
[GC [DefNew: 6633K->6633K(7424K), 0.0000684 secs] 25496K->18505K(32000K), 0.0934295 secs] |
q个l果说明Q?/font>
"[DefNew: 6633K->6633K(7424K), 0.0000684 secs]"是指"新生?的垃圑֛收情况,q里的意思是从占?633K内存I间变ؓ6633K内存I间Q用?. 0000684U?br /> "25374K->18820K(32000K), 0.0639274 secs"是指MGC的回收情况,整体堆空间占用从25374K降低?8820K的水qI用时0. 0639274U?br /> "[Tenured: 18740K->18820K(24576K), 0.0636505 secs]"是指"老生?GC的回收情况,整体堆空间占用从18740K降低?8820K的水qI用时0.0009012U?/font>
通过q些参数的调整我们可以看到在处理垃圾攉问题Ӟ从垃圑֛收的频率是时间方面的变化Q我们可以根据不同程序的不同情况予以调整?/p>
最后有必要提一下GC的相兛_敎ͼ
-XX:+PrintGCDetails
昄GC的详l信?br />
-XX:+PrintGCApplicationConcurrentTime
打印应用执行的时?br />
-XX:+PrintGCApplicationStoppedTime
打印应用被暂停的旉
注:":"后的"+"可C开启此选项,如果?-"号那么表C关闭此选项?/font>
怿大家都知道String和StringBuffer之间是有区别的,但究竟它们之间到底区别在哪里Q我们就再本节中一探究竟,看看能给我们些什么启C。还是刚才那个程序,我们把它改一改,本E序中的Stringq行无限ơ的累加Q看看什么时候抛出内存超限的异常Q程序如下所C:
public class MemoryTest{ |
我们注意刎ͼ在String的实际字节数只有8M的情况下Q@环后已占内存数竟然已l达C63.56M。这说明QStringq个对象的实际占用内存数量与其自w的字节C相符。于是,在@?9ơ的时候就已经?OutOfMemoryError"的错误了?/p>
因此Q应该少用Stringq东西,特别? String?+="操作Q不仅原来的String对象不能l箋使用Q而且又要产生多个新对象,因此会较高的占用内存?/p>
所以必要改用StringBuffer来实现相应目的,下面是改用StringBuffer来做一下测试:
public class MemoryTest{ |
启示2Q用"-Xmx"参数来提高内存可控制?/strong>
前面我们介绍q?-Xmx"q个参数的用法,如果我们q是处理刚才的那个用StringBuffer的JavaE序Q我们用"-Xmx1024m"来启动它Q看看它的@环次数有什么变化?/p>
输入如下指oQ?/p>
java -mx1024m MemoryTest |
那么通过使用"-Xmx"参数其可控内存量扩大至1024M后,那么q个E序C1G的时候才内存限Q从而内存的可控性提高了?/p>
但扩大内存用量永远不是最l的解决Ҏ(gu)Q如果你的程序没有去更加的优化,早晚q是会超限的?/p>
启示3Q二l数l比一l数l占用更多内存空?/strong>
对于内存占用的问题还有一个地方值得我们注意Q就是二l数l的内存占用问题?/p>
有时候我们一厢情愿的认ؓQ?/p>
二维数组的占用内存空间多无非是二维数组的实际数l元素数比一l数l多而已Q那么二l数l的所占空_一定是实际甌的元素数而已?/p>
但是Q事实上q不是这LQ对于一个二l数l而言Q它所占用的内存空间要q远大于它开辟的数组元素数。下面我们来看一个一l数l程序的例子Q?/p>
public class MemFor{ |
我们再将q个E序q行修改Q改Z个二l数l,q个二维数组的元素数量我们也量的和上一个一l数l的元素数量保持一致?/p>
public class MemFor{ |
4.4.4 启示4Q用HashMap提高内存查询速度
田富鹏主~的《大学计机应用基础》中是这hq内存的Q?/strong>
……
DRAMQ即内存条。常说的内存q不是内部存储器Q而是DRAM?/font>
……CPU的运行速度很快Q而外部存储器的读取速度相对来说很慢,如果CPU需要用到的数据L从外部存储器中读取,׃外部讑֤很慢Q?#8230;…QCPU可能用到的数据预先读到DRAM中,CPU产生的(f)时数据也暂时存放在DRAM中,q样的结果是大大的提高了CPU的利用率和计机q行速度?/font>
……
q是一个典型计机基础教材针对内存的描qͼ也许作ؓ计算Z业的E序员对q段描述q不陌生。但也因D|qͼ而对内存的处理速度有神话的理解Q认为内存中的处理速度是非常快的?/p>
以持有q种观点的程序员遇到一个巨型的内存查询循环的较长时间时Q而束手无{了?/p>
L一下如下程序:
public class MemFor{ |
java -Xmx1024m MemFor |
E序的运行结果如下:
取值结?3145728
赋值@环时?2464ms
获取循环旉:70ms
Java可控内存:1016M
已占用内?128M
我们发现Q这个程序@环了3145728ơ获得想要的l果Q@环获取数值的旉用了70毫秒?/p>
你觉得快吗?
是啊Q?0毫秒虽然于1U钟Q但是如果你不得不在q个循环外面再套一个@环,即外层嵌套的@环只?00ơ,那么Q想想看是多毫U呢Q?/font>
回答Q?0毫秒*100=7000毫秒=7U?/font>
如果Q@?000ơ呢Q?/font>
70U!
70U的q行旉对于q个E序来说是N了?br />
面对q个E序的运行时间很多程序员已经束手无策了,其实QJaval程序员们提供了一个较快的查询Ҏ(gu)--哈希表查询?/p>
我们这个程序用"HashMap"来改造一下,再看看运行结果:
import java.util.*; |
java -Xmx1024m HashMapTest |
E序的运行结果如下:
取之l果:3145727
赋值@环时?16454ms
获取循环旉:0ms
Java可控内存:1016M
已占用内?566M
那么现在用HashMap来取值的旉竟然不到1msQ这时我们的E序的效率明显提高了Q看来用哈希表进行内存中的数据搜索速度实很快?/p>
在提高数据搜索速度的同时也要注意到Q赋值时间的差异和内存占用的差异?/p>
赋值@环时_
HashMapQ?6454ms
普通数l:2464ms
占用内存Q?/p>
HashMapQ?66M
普通数l:128M
因此Q可以看出HashMap在初始化以及内存占用斚w都要高于普通数l,如果仅仅是ؓ了数据存储,用普通数l是比较适合的,但是Q如果ؓ了频J查询的目的QHashMap是必然的选择?br />
启示5Q用"arrayCopy()"提高数组截取速度
当我们需要处理一个大的数l应用时往往需要对数组q行大面U截取与复制操作Q比如针对图形显C的应用时单U的通过Ҏ(gu)l元素的处理操作有时捉襟见肘?/p>
提高数组处理速度的一个很好的Ҏ(gu)?System.arrayCopy()"Q这个方法可以提高数l的截取速度Q我们可以做一个对比试验?/p>
例如我们用普通的数组赋值方法来处理E序如下Q?/p>
public class arraycopyTest1{ |
public final class arraycopyTest2{ |
E序q行l果如图 3 9所C?/p>
两个E序的差距再3U多Q如果处理更大批量的数组他们的差距还会更大,因此Q可以在适当的情况下用这个方法来处理数组的问题?/p>
有时候我们ؓ了用方便,可以在自qtools包中重蝲一个arrayCopyҎ(gu)Q如下:
public static Object[] arrayCopy(int pos,Object[] srcObj){ |