- 浏览: 237521 次
- 来自: 上海
文章分类
最新评论
-
codingstandards:
914412060 写道楼主文章不错啊,顶一个同感!
二维码墓碑的技术探讨 -
914412060:
楼主文章不错啊,顶一个
二维码墓碑的技术探讨 -
codingstandards:
怪了,这么好的文章,怎么无缘无故的踩,是何道理?
二维码墓碑的技术探讨 -
codingstandards:
PV_love 写道比如什么呢?欢迎光临我的独立博客,谢谢!
三只青蛙的故事:一道脑筋急转弯题目引发的思考 -
PV_love:
比如什么呢?
三只青蛙的故事:一道脑筋急转弯题目引发的思考
问题描述
在一个项目中,有一个单独的java程序,它使用了第三方类库,而且是必须使用的那种,但是这个第三方类库有个致命的问题:它如同一头永远处于饥饿状态的野兽,它会不断的吃掉内存,最终导致“ java.lang.OutOfMemoryError: Java heap space”异常。
java.lang.OutOfMemoryError: Java heap space
虽然出现了异常,但它不会退出。只有等到发现的时候,人工去这个杀掉这个进程。这种情况多次反复出现,造成了一些不良影响。
不管用的解决方案
能不能让它定期重启一下呢?(这几乎总是最后的解决方法了,因为系统中未知的因素太多)程序退出之后,所占用的内存不也就释放掉了嘛!
在 main() 方法的末尾加上下面的代码,意思是说“在每天0点的时候程序自动退出”。(因为在系统中有一个任务管理程序,它会检查进程是否还在,若已终止则重新启动。)
while (true) { Thread.sleep(3600 * 1000); java.util.Date now = new java.util.Date(); if (now.getHours() == 0) { break; } }
这个看上去没什么问题,于是编译重新运行。然而,在第二天 0 点的日志中没有发现重启的记录,也就是说它没有按照预想的那样,在0点并没有退出!
管用的改进?
在 java 中,线程有个 daemon 属性(是否守护线程),应该将程序中主线程之外的线程设置为 daemon 属性,这样才能保证 main() 方法执行完之后程序自动退出来,否则进程就会一直等待所有的非守护线程退出。下面是关于守护线程的一些描述信息。
Java有两种Thread:“守护线程(Daemon Thread)”与“用户线程(User Thread)”。守护线程是一种“在后台提供通用性支持”的线程,它并不属于程序本体。从字面上我们很容易将守护线程理解成是由虚拟机(virtual machine)在内部创建的,而用户线程则是自己所创建的。事实并不是这样,任何线程都可以是“守护线程”或“用户线程”。他们在几乎每个方面都是相同的,唯一的区别是判断虚拟机何时离开:
用户线程:Java虚拟机在它所有用户线程(非守护线程)已经离开后自动离开。
守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
当程序只有守护线程时,该程序便可以结束运行。
Thread.setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。setDaemon(boolean on)方法必须在线程启动(start)之前调用,当线程正在运行时调用会产生异常(IllegalThreadStateException)。isDaemon方法将测试该线程是否为守护线程。值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
例如,我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
在这个程序中,有两个线程,一个线程是不断调用第三方类库提供的方法,一个线程是一个网络客户端,将这两个线程都设置为 daemon 属性。这两个类分别为 Relay 和 Client,是 Thread 类的子类。这样之后 main() 方法的内容大体如下:
// 第一个线程 Relay relay = new Relay(); // ... relay.setDaemon(true); relay.start(); // 第二个线程 Client client = new Client(); client.setDaemon(true); client.start(); // 下面的代码用于在每天0点退出 while (true) { Thread.sleep(3600 * 1000); java.util.Date now = new java.util.Date(); if (now.getHours() == 0) { break; } }
这总该管用了吧?!经过检查第二天的日志,发现还是没有在0点退出,为什么?
这个程序到底起了多少线程?
猜测,也许这个第三方类库中可能启动一些别的线程,否则就与 setDaemon(true) 的精神相违背。那得看一下它到底启动了哪些线程,在JDK中,jstack 可以观察到jvm中当前所有线程的运行情况和线程当前状态,使用方式是 jstack pid;其中 pid 为进程id,可用 jps 或 ps 或 top 等工具得到。
用 jstack 看一下。下面是去掉第三方类库的程序执行线程状态信息。
2012-06-11 13:35:01
Full thread dump Java HotSpot(TM) Client VM (16.3-b01 mixed mode, sharing):
"Attach Listener" daemon prio=10 tid=0x081db400 nid=0x562e runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" daemon prio=10 tid=0x081d5400 nid=0x1f9d runnable [0xb41ca000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at smj.client.SmjClient.recv(SmjClient.java:96)
at smj.client.SmjClient.loop(SmjClient.java:139)
at smj.client.SmjClient.run(SmjClient.java:124)
at java.lang.Thread.run(Thread.java:619)
"Low Memory Detector" daemon prio=10 tid=0x08096400 nid=0x1f99 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"CompilerThread0" daemon prio=10 tid=0x08093000 nid=0x1f98 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=10 tid=0x08091400 nid=0x1f97 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=10 tid=0x0807e400 nid=0x1f96 in Object.wait() [0xb48b4000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x87040b00> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
- locked <0x87040b00> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
"Reference Handler" daemon prio=10 tid=0x0807cc00 nid=0x1f95 in Object.wait() [0xb4905000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x87040a08> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:485)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked <0x87040a08> (a java.lang.ref.Reference$Lock)
"main" prio=10 tid=0x08058400 nid=0x1f93 waiting on condition [0xb6b96000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at kdx.sms.relay.Main.main(Main.java:22)
"VM Thread" prio=10 tid=0x0807b400 nid=0x1f94 runnable
"VM Periodic Task Thread" prio=10 tid=0x08098400 nid=0x1f9a waiting on condition
JNI global references: 994
[root@web186 emay_sms_relay]#
下面是增加调用第三方类库的线程之后新增的线程。其中有两个线程没有 daemon 属性,不是守护线程。
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at emay.sms.relay.EmaySDKClient.run(EmaySDKClient.java:405)
"MonitorThread" prio=10 tid=0x081e3400 nid=0x1eec in Object.wait() [0xb4136000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x89bf61b0> (a java.util.TaskQueue)
at java.util.TimerThread.mainLoop(Timer.java:509)
- locked <0x89bf61b0> (a java.util.TaskQueue)
at java.util.TimerThread.run(Timer.java:462)
"Thread-2" prio=10 tid=0x081e2c00 nid=0x1eeb waiting on condition [0xb4187000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at cn.emay.sdk.communication.socket.ReceiveThread.run(ReceiveThread.java:53)
也就是说,由于这两个非守护线程的存在, main()方法执行完之后,程序不会自动终止,因为它一直在执着的等待这两个线程的结束,它会一直等下去,直到poweroff。
最终解决
当然,问题总是可以解决的,那就是显式的调用 System.exit() 方法强制退出程序啦。如下所示:
while (true) { Thread.sleep(3600 * 1000); java.util.Date now = new java.util.Date(); if (now.getHours() == 0) { //break; System.exit(100); } }
经过观测,这个方法达到了预期的目标,每天0点的时候就会自动退出。
后记
从上述解决方案的本质上讲,算不得一个好的解决方案,这相当于让程序自己来监控自己,发现自己有问题就退出,这样的解决方法通常会有不管用的时候。因为程序在出现问题的时候,用于监控的那段程序也许也不灵了。(是不是可以联想到现实生活中来)
最好的方式还是第三方监控,就是说单独写个脚本(或程序)来监控进程是否正常,比如占用的内存、CPU、网络连接、数据库连接等是否超过指定的限度,在超过限度时进行告警或者自动终止、自动重启等。当然,这种监控也不是万能的,如果能够一劳永逸的解决问题,那还要我们搞 IT 的人干什么呢?!
发表评论
-
从今天起,改用JavaSE7
2013-02-04 08:39 1285原来我一直使用JavaSE6,现在因为JavaSE6快要 ... -
16道有点难度的24点游戏题,保准难住你,不信你来试试!
2012-12-23 09:51 623424点游戏是把4个 ... -
tomcat启动不来,报错:java.lang.NoClassDefFoundError: javax/net/ssl/KeyManagerFactory
2012-06-29 10:19 2296在一台centos虚拟机上,安装完tomcat之后,试图启动它 ... -
执行java时报错:Error occurred during initialization of VM
2012-06-29 09:52 12243在一台CentOS的虚拟机上安装了jdk之后,执行java报错 ... -
大家不要升级tomcat到最新的7.0.27
2012-06-19 09:26 1257自从将tomcat升级到7.0.27之后,发现了几件怪事: ... -
java定时任务实现方式总结
2012-06-15 17:35 0java定时任务实现方式总结(一) 定时任务概述 定时任务 ... -
用java也可以轻松实现收集系统信息:Sigar介绍
2012-06-13 16:13 7948Sigar简介 今天意外发现了一个开源工具包 SIGAR - ... -
怎样从java.sql.Timestamp得到java.sql.Date对象
2012-05-02 17:08 1359怎样从java.sql.Timestamp得到java.sql ... -
写email地址的正则表达式不容易
2012-04-27 14:39 2317从文本内容中提取email地址 当然用正则表达式啦。代码如下 ... -
从伪word格式的doc文件中提取文本内容
2012-04-27 14:14 2562从mht文件中提取html内容 ... -
被忽略的双引号
2012-04-26 13:53 1197被忽略的双引号 在一段从mht文件中提取html内容的程序中 ... -
与String.indexOf有关的取最小值方法
2012-04-25 14:59 1299特殊的取最小值方法 取最小值很简单,如下所示 ... -
关于String.substring方法
2012-04-25 14:15 1470关于String.substring方法 该方法的签名为 ...
相关推荐
在编程领域,线程是程序执行的基本单元,特别是在多任务操作系统中。易语言是一种中文编程环境,它提供了方便的线程操作接口。本篇将详细探讨如何在易语言中实现“正确退出线程”这一重要知识点。 首先,理解线程的...
由于线程可能在任何时刻抛出异常,开发者需要理解如何在多线程环境中捕获和处理异常,以确保程序的健壮性。 最后,书中还会讨论性能计数器、调试工具和性能优化技术,帮助读者更好地理解和调试多线程程序,提升程序...
《POSIX多线程程序设计》深入描述了IEEE的开放系统接口标准——POSIX线程,通常称为Pthreads标准。本书首先解释了线程的基本概念,包括异步编程、线程的生命周期和同步机制;然后讨论了一些高级话题,包括属性对象、...
在IT领域,多线程是程序设计中的一个重要概念,它允许程序同时执行多个任务,显著提高了计算机系统的效率和响应...学习这个示例将有助于提升我们在多线程编程方面的技能,使我们能够编写更加高效和可靠的并发应用程序。
在多线程程序中,资源管理也是一个重要的话题。线程可以拥有自己的堆栈空间,但全局变量和动态分配的内存通常是共享的,需要特别小心处理。`ExitThread`函数用于结束线程的执行,而`CloseHandle`函数用于关闭线程...
这个“简单QT多线程聊天程序”旨在提供一个基础的学习平台,帮助开发者理解如何在QT环境中利用多线程进行并发处理。 QT框架是一个跨平台的C++库,提供了丰富的GUI工具和网络编程接口。在这个聊天程序中,多线程技术...
4. 线程退出与等待:线程执行完毕后会自动退出,也可以调用ExitThread函数强制退出;主线程可以通过WaitForSingleObject或WaitForMultipleObjects函数等待一个或多个线程结束。 三、线程间的通信 线程间通信是多...
《Win32多线程程序设计》是一本深入探讨C++在Win32平台上实现多线程编程的专业书籍。该书全面覆盖了多线程技术的基础理论与实践应用,为开发者提供了详尽的指导。书中的"加全书签"功能使得读者能够更方便地定位和...
在本项目“C#后台多线程实现自动采集指定网页上面指定标签新闻”中,开发者利用C#语言的特性,创建了一个能够后台运行的新闻采集系统。这个系统能够高效地从设定的网站上抓取指定标签的新闻内容,包括图片、文字等...
而在多线程环境下,多个线程可以并发执行,使得程序能够并行处理多个任务,提升了程序的运行效率。易语言的多线程模块正是提供了这样的功能,使开发者能够在易语言中编写多线程程序。 创建线程是多线程编程的第一步...
在这个"多线程互斥实例 多线程获取同一变量"的示例中,我们将探讨如何在多个线程中安全地访问共享资源,避免数据不一致性和竞态条件。 首先,我们需要理解多线程中的一些核心概念: 1. **线程**:线程是操作系统...
在这个项目中,我们讨论的是一个使用C#编程语言实现的多线程`ping`程序,它可以同时对多个目标进行探测,并且能够统计和展示结果。下面我们将深入探讨这个程序涉及的技术点。 首先,我们要了解C#中的多线程。在C#中...
在这个微软VC6的示例中,程序展示了如何在多线程环境中有效利用GDI进行绘图。多线程的使用通常是为了提高性能,尤其是在处理密集型计算或I/O操作时。在图形绘制的场景下,如果在一个线程中完成所有的绘制工作,可能...
在IT行业中,程序崩溃是常见的问题,特别是在自动化测试和生产环境中。为了确保系统的稳定性和可靠性,开发者需要采取一些策略来处理这种情况。本篇文章将详细探讨如何实现“程序崩溃自动重启”以及“未捕获异常时写...
在单线程程序中,任务是顺序执行的,而在多线程程序中,可以同时执行多个任务,这极大地提高了程序的效率和响应性。在MFC中,`CWinThread`类是线程的基础,它封装了Windows API的线程创建和管理。 队列,作为一种...
Java多线程文件传输是Java编程中一个重要的实践领域,特别是在大数据处理、网络通信和分布式系统中。在Java中,多线程可以提高程序的执行效率,尤其在处理并发任务时,如大文件的上传、下载和传输。下面将详细探讨...
- **线程安全**:确保在多个线程访问共享数据时,数据的一致性和完整性。使用线程同步机制防止数据冲突。 - **消息泵**:在网络线程中,通常需要有一个消息泵来处理线程内部的消息,这样可以响应线程特定的事件,如...
2.熟悉在多线程下参数传递及子程序调用 3.副本功能的简单制作 4.特色功能的简单思路 幻想神域多线程实战视频 第一章(基础设置) 1.多线程实战之中控台简单说明 2.多线程实战之绑定模式与字库制作 3.多线程实战...
在MFC(Microsoft Foundation Classes)框架中,线程的管理是一项关键任务,特别是在多线程应用程序中。"mfc安全退出线程"是指在MFC环境中,如何正确、安全地终止一个正在运行的线程,避免引发数据不一致、资源泄露...