2008年4月27日
PS: 阅读之后感触很深,以前写程序都没考虑到这些细节.好好学习一下,为了以后能写出高质量代码的专业程序员而努力
出自:ibm:developerworks中国网站 欧阳辰周欣
一 问题的提出
Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。
随着越来越多的服务器程序采用Java技术,例如JSP,Servlet, EJB等,服务器程序往往长期运行。另外,在很多嵌入式系统中,内存的总量非常有限。内存泄露问题也就变得十分关键,即使每次运行少量泄漏,长期运行之后,系统也是面临崩溃的危险。
二 Java是如何管理内存
为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
三 什么是Java中的内存泄露
下面,我们就可以描述什么是内存泄漏。在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。
下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
四 如何检测内存泄漏
最后一个重要的问题,就是如何检测Java的内存泄漏。目前,我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,我们将简单介绍Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,并且可以支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,该软件是由Java编写,因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。
当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。
在运行过程中,我们还可以从不同视角观查内存的使用情况,Optimizeit提供了四种方式:
- 堆视角。 这是一个全面的视角,我们可以了解堆中的所有的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化情况。
- 方法视角。通过方法视角,我们可以得知每一种类的对象,都分配在哪些方法中,以及它们的数量。
- 对象视角。给定一个对象,通过对象视角,我们可以显示它的所有出引用和入引用对象,我们可以了解这个对象的所有引用关系。
- 引用图。 给定一个根,通过引用图,我们可以显示从该顶点出发的所有出引用。
在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件非常麻烦的事情,它需要程序员对整个程序的代码比较清楚,并且需要丰富的调试经验,但是这个过程对于很多关键的Java程序都是十分重要的。
综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。
相关资源:
文章:
Jim Patrick, Handling memory leaks in Java programs,
http://www-106.ibm.com/developerWorks/library/j-leaks/index.html
Ed Lycklama, Does Java Technology Have Memory Leaks?
http://www.klgroup.com/javaone
Sun, The Java HotSpot Virtual Machine, Technical White Paper
软件:
Sitraka Software's Jprobe http://www.sitraka.com
Boland Software's Optimizeit http://optimizeit
IBM alphaWorks' Jinsight http://www.alphaworks.ibm.com/tech/jinsight
关于作者
欧阳辰,北京大学计算机硕士毕业,98年起开始研究基于java的软件开发、测试,参与开发、测试过多个基于Java的应用程序和Web服务项目。联系方式yeekee@sina.com
周欣,北京大学计算机系在读博士生,主要研究方向:程序理解、逆向工程及软件度量,联系方式 zhouxin@sei.pku.edu.cn
posted @
2008-04-27 12:41 Mr. Michael.Q 阅读(200) |
评论 (0) |
编辑 收藏
线程 是 java 的一大特性,它可以是给定的指令序列、给定的方法中定义的变量或者一些共享数据(类一级的变量)。 在 java 中每个 线程 有自己的堆栈和程序计数器(pc),其中堆栈是用来跟踪 线程 的上下文(上下文是当 线程 执行到某处时,当前的局部变量的值),而程序计数器则用来跟踪当前 线程 正在执行的指令。
在通常情况下,一个 线程 不能访问另外一个 线程 的堆栈变量,而且这个 线程 必须处于如下状态之一:
1.排队状态(ready),在用户创建了一个 线程 以后,这个 线程 不会立即运行。当 线程 中的方法start()被调用时,这个 线程 就会进行排队状态,等待调度程序将它转入运行状态(running)。当一个进程被执行后它也可以进行排队状态。如果调度程序允许的话,通过调用方法yield()就可以将进程放入排队状态。
2.运行状态(running),当调度程序将cpu的运行时间分配给一个 线程 ,这个 线程 就进入了运行状态开始运行。
3.等待状态(waiting),很多原因都可以导致 线程 处于等待状态,例如 线程 执行过程中被暂停,或者是等待i/o请求的完成而进入等待状态。
在 java 中不同的 线程 具有不同的优先级,高优先级的 线程 可以安排在低优先级 线程 之前完成。如果多个 线程 具有相同的优先级, java 会在不同的 线程 之间切换运行。一个应用程序可以通过使用 线程 中的方法setpriority()来设置 线程 的优先级,使用方法getpriority()来获得一个 线程 的优先级。
线程 的生命周期
一个 线程 的的生命周期可以分成两阶段:生存(alive)周期和死亡(dead)周期,其中生存周期又包括运行状态(running)和等待状态(waiting)。当创建一个新 线程 后,这个 线程 就进入了排队状态(ready),当 线程 中的方法start()被调用时, 线程 就进入生存周期,这时它的方法isalive()始终返回真值,直至 线程 进入死亡状态。
线程 的实现
有两种方法可以实现 线程 ,一种是扩展 java .lang.thread类,另一种是通过 java .lang.runnable接口。
posted @
2008-04-27 12:39 Mr. Michael.Q 阅读(164) |
评论 (0) |
编辑 收藏
地址:http://blog.csdn.net/jerryao/archive/2006/07/04/874101.aspx
一、堆栈(stack)和堆(heap)?
(1)内存分配的策略
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
(2)堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较, 堆主要用来存放对象的,栈主要是用来执行程序的 .而这种不同又主要是由于堆和栈的特点决定的:
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快,当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
(3)JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
posted @
2008-04-27 12:37 Mr. Michael.Q 阅读(246) |
评论 (0) |
编辑 收藏
地址:http://blog.csdn.net/jerryao/archive/2006/07/04/874101.aspx
一、堆栈(stack)和堆(heap)?
(1)内存分配的策略
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
(2)堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较, 堆主要用来存放对象的,栈主要是用来执行程序的 .而这种不同又主要是由于堆和栈的特点决定的:
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快,当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
(3)JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
posted @
2008-04-27 12:37 Mr. Michael.Q 阅读(2513) |
评论 (0) |
编辑 收藏
进程通常被定义为一个正在运行的程序的实例,它由两个部分组成:
* 一个组成部分是操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。
* 另一个组成部分是地址空间,它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆栈分配空间。
进程是不活泼的。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,该线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可能包含若干个线程,所有这些线程都“同时”执行进程地址空间中的代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少拥有一个线程,来执行进程的地址空间中的代码。如果没有线程来执行进程的地址空间中的代码,那么进程就没有存在的理由了,系统就将自动撤消该进程和它的地址空间。
若要使所有这些线程都能运行,操作系统就要为每个线程安排一定的CPU时间。它通过以一种循环方式为线程提供时间片(称为量程),从而造成一种假象,仿佛所有线程都是同时运行的一样。
当创建一个进程时,系统会自动创建它的第一个线程,称为主线程。然后,该线程可以创建其他的线程,而这些线程又能创建更多的线程。
---------------------------------------------------------------摘自 《Windows 核心编程》
进程有三大部分:代码段、数据段、PCB(进程控制段)。
操作系统正是通过PCB来管理这多个进程。在这样的系统里,进程是操作系统独立调度和分派的基本单位,又是一个可拥有资源的独立单位。
线程:系统调度和分派的基本单位。
进程和线程有如下不同:
进程可以拥有资源,线程共享进程拥有的资源
进程间的切换必须保存PCB
---------------------------------------------------------------
微软官方对进程和线程的定义:
进程:用最简洁的话来说,进程就是一个正在执行的程序,一个或多个线程在进程中运行,线程是操作系统分配CPU运算时间的最小单位。每一个进程都提供了运行一个程序所必需的资源,一个进程具有4GB的虚拟地址空间(Windows NT Server Enterprise Edition及Windows 2000 Advanced Server中低3GB虚拟地址空间供进程使用,高1GB供操作系统的内核代码使用。Windows NT/2000中低2GB供进程使用,高2GB供操作系统内核代码使用。Windows9X:0——64K只读空间用来装入Microsoft DOS信息,64K——4M装入DOS的兼容代码,4M——2GB的私有空间供进程使用,2GB——3GB的共享空间装入各种DLL代码,3GB——4GB为共享的系统内核代码空间,其中共享的2GB——4GB的空间是99%的“内存无效页错误”、“General Protect Error(GPE)”及蓝屏的罪魁祸首。),可执行代码,数据,对象句柄,环境变量,优先权以及设置最大化最小化的功能。每一个进程都从一个主线程开始执行,但可以在它所拥有的线程中创建额外的线程。一个进程的所有线程共享进程的虚拟地址空间和系统资源,一个线程的资源包括线程的机器寄存器设置,内核堆栈,线程环境变量和进程虚拟地址中的用户堆栈。
---------------------------------------------------------------
两者的区别。根据定义,进程为一个数据结构及能在其上进行的一次操作,它有两个基本特征,一个是进程是可用有资源的独立单位,第二个是进程同时又是一个可以独立调度和分派的基本单位,这两个基本属性使之能够独立运行,也能够并发运行。但是在并发运行的时候,系统还需要执行一系列操作:
1、需要创建进程,并为之分配其所必需的资源。
2、撤销进程,对资源进行回收。
3、进程切换,它需要保留当前进程的CPU环境和设置新选中进程的CPU环境,为此需要花费不少处理时间。正因为进程拥有资源,所以在并发执行进程的时候,在创建、撤销和切换种,系统需要付出较大的开销,因此,系统中设置的进程不能太多,进程切换的频率也不能过高,这就限制了并发程度的提高。为了解决这一问题,于是产生并引入了线程概念。
线程是进程中的一个实体,它的基本思想是将程序的执行和资源分开,只拥有一点必不可少的资源。一个进程可用有多个线程,但它可以和同属于同一进程的其他线程共享进程所拥有的所有的资源,同一进程中的线程之间可以并发执行。这样的话,并发程度可以获得显著的提高。线程也具有许多进程所具有的特征,因此被称为轻型进程。
---------------------------------------------------------------网上收集
posted @
2008-04-27 12:36 Mr. Michael.Q 阅读(263) |
评论 (0) |
编辑 收藏
2007年8月3日
JDBC高级应用
转自 http://www.blogcn.com/user69/galiasun/index.html
本来想继续谈JDBC的高级连结方式,事务模式.但发现关于大对象存储有很多人在问,所以
先来插入一节关于大对象存储的内容,然后再接着原来的思路写下去.
JDBC的大对象存储听起来复杂,其实如果你明白了原理以后,就非常简单,网上有关这方面的
教材很少,而SUN的文档中,我从1.2开始看到一在仍然是错误的,不知道写文档的人长脑子没
有,就那几行代码你试试不就知道了,这么多次重抄下来还是错误的.
大对象分类:一般来说,大对象分为:大的文本对象,比如一个很长的文本(请你要注意什么是
文本文件,什么是二进制文件)文件,或者是你定义的一个长字符串,比如你定义了:
String s = "我们要去吃饭了......................然后睡觉!";
从吃饭到睡觉中间省略了实际的10000000000000字,虽然你不会真的定义这么称的String,但
有时会从什么地方得到这样的String,要写到数据库中.
另一种就是大的二进制对象,象执行文件,图象文件等,注意,word,excel,ppt这些"带格式"的文
档都应该以二进制对象存储.
一般来说,数据库如果支持大对象存储,会有这几种类型的SQL数据类型:
BLOB,CLOCB,NLOB,也有的数据数只有一种BLOB,基本上是这样的:BLOB用来存放二进制文件,而
CLOB用来存放文本文件,NLOB是对多字节文本文件支持.假如你的文本文件是纯英文的,放在
BLOB中当然可以,也就是说它是以byte格式存储的,而多字节是以CHAR格式存储的.
同样对于这几种类型的文档,有几种相对应的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于写入一般的文本流.setBinaryStream()方法用于写入二进制流
而setUnicodeStream()用于写好UNICODE编码的文本,与此相对应的ResultSet中三个getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
对于文件本身,要把它作为一个流,只要new InputStream(new FileInputStream("文件路径")
就可以了,但对于大的String对象,你不会写入文件再转换成输入流吧?
new StringBufferInputStream(String s),记住了.
JDBC2以后提供了java.sql.BLOB对象,我不建议大家使用它,一是很麻类,二是容易出错,要先插
入一个空的BLOB对象,然后再填充它,实在没有必要,直接setXXX就行了,我试过,至少mysql,
oracle,sql server是可以直接set的.
好了,我们先看一个例子如何写入文件到数据库:
数据结构:
create table test(
name varchar(200),
content BLOB
);
File f = new File("a.exe";//先生成File对象是为了取得流的长度.FileInputStram可以直接
//传入文件路径
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)";
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的长度一定要做从long到int的转换,SUN的文档中好几版都没有改过来.就这么简单,当然,不同的
数据库存本身要设置它允许的最大长度,MYSQL默认只能传1M的文件,要修改参数原能存更大的文件.
如果要从数库中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe";
ResultSet rs = ps.executeQuery();
if(rs.next()){
InputStream in = rs.getBinaryStream("content";
}
得到in对象后,你可以进行任何处理,写向文件和写向页面只是out对象不同而已:
写向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe");
写向页面:
response.reset();
response.setContType("类型";
ServletOutputSreamt out = response.getOutputSream();
得到out对象后,就可以输出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
out.write(buf,0,len);
in.close();
out.close();
对于向页面输入,要设置什么样的ContType,要看你想如何输出,如果你想让对方下载,就设为
"application/octet-stream",这样即使是文本,图象都会下载而不会在浏览器中打开.如果你要想
在浏览器中打开,就要设置相应的类型,还要在容器的配置文件中设置支持这种文档类型的输出,但
对于很多格式的文件,到底要输出什么类型,其实就是HTTP的MIME集,比如图片:image/gif,当然你如
果你的文件扩展名(ext)不确定,你也不要用if(ext.equals("gif")......这样来判断,我教你一个
技巧,我之所以说是技巧,是我没有在别的地方发现有人用这种方法,对我来说我是绝对不会把别人的
方法拿来说是我的技巧的:
构造一个file类型的URL,我们知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,从FILE类型
的URL就可以得到它的MIME:
URL u = new URL("file://a.exe";
String mime = u.openConnection().getContentType();
这样你就可以直接response.setContType(mime);而不用一个一个类型判断了.
好了,大对象存储就说到这儿,不同的数据仍然和些特殊的规定,不在此一一列举了.
posted @
2007-08-03 00:40 Mr. Michael.Q 阅读(287) |
评论 (0) |
编辑 收藏