java 句柄泄露_191206_01 Java中的句柄与资源泄露

Java中的句柄与资源泄露

作者:邵发

本文内容介绍Java中的句柄与资源泄露问题,是Java网站开发中必须清楚掌握的基本概念。句柄包括两类:文件句柄和网络句柄。本文是Java学习指南系列教程的官方配套文档,配套示例代码或者视频讲解。

在项目开发中,句柄的概念极为重要。如果不了解句柄,就可能发生以下错误:

-文件被占用,无法删除或移动

-      Tomcat资源占满,无法访问

-      MySQL数据库连接数已满,无法连接

1.文件句柄

句柄( Handle ),是一种系统资源。在实际编程中分为两类:文件句柄,网络句柄(Socket)。

先说文件句柄。当在程序里打开一个文件的时候,操作系统内核里创建了一个文件句柄,该句柄为当前进程拥有。比如,

InputStream inputStream =

new FileInputStream(“C:\test\example.txt”);

此时就打开了一个文件句柄。文件句柄在用完之后一定要执行close关闭,在close的时候会释放系统资源。即使用以下的代码框架:

InputStream

inputStream = new FileInputSteam( file );

try{

...读取文件...

}

finally{

inputStream.close();

}

无论在try{}中有无异常发生,总是在finally{}中执行close()方法,确保文件句柄的释放。

涉及到文件句柄的地方有:

//读取一个文件

InputStream inputStream = new FileInputStream( file )

//写入一个文件

OutputStream outputStream = new OutputStream (file)

//随机读写文件(不常见)

RandomAccessFile  raf = new RandomAccessFile

(file, "rw")

也就是说,创建一个InputStream / OutputStream /

RandomAccessFile对象时,各自对应了一个句柄。所有带句柄的对象,在用完之后要记得关闭掉。

需要区分的是,File对象只是一个文件路径,相当于String的包装类,它是不对应文件句柄的。例如,File f = new

File(“C:\test\example.txt”)这只是定义了一个路径,什么事都没有发生,不会创建文件句柄。

2.网络句柄

当在项目中使用网络编程技术时,也需引起我们的注意。一个Socket对应一个句柄,可以称为网络句柄。网络句柄和文件句柄一样,都是有限的资源,也是用完了就必须关闭。

涉及网络句柄的地方有:

2.1 Socket和 ServerSocket

在TCP网络编程中的Socket和ServerSocket代表了网络句柄,以下示例详见Java学习指南系列的《网络通信篇》视频教程。

//服务器:创建一个ServerSocket的时候,句柄数加1

ServerSocket  serverSock = new

ServerSocket(2019);

//服务器:接收到一个客户端连接的时候,句柄数加1

Socket sock = serverSock.accept();

//客户端:连接上服务器的时候,句柄数加1

Socket sock = new Socket();

sock.connect( new

InetSocketAddress("127.0.0.1",2019));

也就是说,一个Socket / ServerSocket对象各自对应了一个句柄。

2.2 JDBC Connection

在数据库JDBC编程中,需要获取一个Connection对象,代表一个与MySQL服务器之间的一个链接。例如,

Connection conn =

DriverManager.getConnection(connectionUrl, username, password);

try{

...

} finally {

conn.close();

}

这个Connection对象内部会包含一个Socket连接,所以它也对应一个句柄,在用完后应当及时关闭。

2.3 HttpServletRequest和HttpServletResponse

在Java网站开发时,HttpServletRequest和HttpServletResponse内部包含了网络Socket连接,但是这个Socket是被Tomcat框架自己管理的,我们看不到也管不着。

比如,在处理Servlet请求时,

OutputStream

outputStream = response.getOutputStream();

outputStream.write(

data );

可以脑补一下,从HttpServletResponse获取OutputStream,其内部实现不就是从Socket中获取OutputStream吗?

Tomcat本身是一个Java写的TCP服务器,当一个客户端请求到来时,它会创建一个Socket并创建一个线程来处理。所以在实际运行时,一个请求对应一个线程和一个Socket资源的。再强调一遍,这个Socket是由Tomcat自行管理的,我们看不到也管不着。

3.句柄数量的监测

一个进程可创建的句柄是有限的,以前的Windows或Linux上最多可以创建约2000多个。但是最新的Win10系统似乎突破了这个限制。

可以自己试一下,

public static

void main(String[] args) throws Exception

{

File

file = new File("C:/test/example.txt");

byte[]

buf = new byte[4000];

for(int

i=1; i<10000; i++)

{

InputStream

is = new FileInputStream(file);

is.read(buf);

System.out.println("创建第" + i + "个文件句柄");

}

System.out.println("Exit.");

}

在任务管理器里,可以观察到一个进程所占用的系统资源,如CPU、内存、线程数、句柄数。其中,线程和句柄两列默认是隐藏的,必须打开这两列的显示。按CTRL + SHIFT + ESC,打开任务管理器,如下图所示。

67239977_201912061207260339WSS6NRJIH1QAJME4CE_wm.jpg

如果没有显示句柄这一列的话,可以右键点一下CPU这个标签,选择列即可显示(自己百度一下)。

在这里可以看到每一个进程所占用的资源情况,我们的Java进程的名称是java.exe或者javaw.exe。比如,Eclipse启动时对应一个javaw.exe进程。

以单步调试的方式运行上述代码,可以发现每创建一个FileInputStream对象,该进程的句柄数会加1。如果调用了close()方法,则句柄数会减1。

4.句柄与GC垃圾回收

Java里有一个GC垃圾回收机制,可以把失去引用的对象自动回收。比如,

for(int

i=1; i<10000; i++)

{

InputStream

inputStream = new FileInputStream(file);

inputStream.read(buf);

System.out.println("创建第" + i + "个文件句柄");

}

此处,每一轮循环创建一个对象inputStream,此对象在循环之后失去引用,会被自动回收(销毁)。但是需要区分的是,此处销毁的是对象自身,并没有释放句柄啊!

在Java里,一定要显式地调用inputStream.close()才能够释放这个inputStream所对应的句柄资源。句柄释放和GC机制是两码事!

5.

try-with-resources

在有的代码里,形式上看不到close()的调用,但是句柄在内部被关闭了。比如,在MyBatis编程中经常可见的代码:

try ( SqlSession session =

sqlSessionFactory.openSession() )  {

// do work

}

在这里,并没有看到session.close()的调用,为什么呢?

此种try语法称为try-with-resources,在小括号里是一个称为资源的对象。资源对象类型,就是实现了java.lang. AutoCloseable接口的类型。语法规定,在try{}运行完后会自动调用resource.close()方法来关闭资源。可以认为这种语法是对try { } finally{}的简写,相当于:

SqlSession

session = sqlSessionFactory.openSession();

try

{

//

do work

}

finally

{

session.close();

}

记住,此种try-with-resources语法,是要求资源对象类型是实现了AutoCloseable接口的。你得自己检查一下,有这个接口的类型才能这么简写。

6.线程与句柄

线程数和句柄数,都是有限的资源。一个程序不能开太多的线程,也不能开太多的句柄。

在网站开发中,这两个概念是相关的。表现在:

(1)当一个请求到来时,Tomcat创建一个线程,在线程里处理这个请求Socket。所以一个请求对应了一个线程和一个Socket句柄。

(2)无论是线程占满、还是句柄占满,都会导致系统不可访问。

比如,如果句柄占满,则Tomcat无法创建新的Socket连接,自然也就不能访问了。

7.句柄与服务器的稳定性

在网站开发中,通过观察任务管理器中的句柄数,可以检查有没有句柄泄露的情况。

如果句柄数保持稳定,则没有问题。如果持续增加、不见减少,就说明你的程序代码里有句柄泄露。如果已经达到上限(2000-3000),则服务器已经不可访问。

所谓句柄泄露,就是说你打开一个句柄但是忘了close掉。比如,

-打开了一个FileInputStream /

FileOutputStream / RandomAccessFile对象

-打开了一个数据库连接Connection对象(或者封装后的SqlSession等)

-打开了一个Socket连接对象

-使用HttpClient / FTP Client等封装后的API,打开了一个网络连接但没有close掉...

凡是涉及到这些调用的地方,一定要仔细核对,因为这关系到你的服务器是否能长期稳定地运行。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值