转载:原文地址:http://jarfield.iteye.com/admin/blogs/583946
一直赞叹Sun对待技术的严谨和优雅(可怜的Sun)。Sun JDK中Java库的源代码,连注释都清清楚楚、规规范范,javadoc注解的使用也一丝不苟,读起来很熟舒服。因此,在日常工作和学习中,经常读读 Java库的源代码,不亦乐乎?如果遇到诡异问题,源代码的帮助就更大了。
闲话少说,回归正题。这几天,一直在为Java的“内存泄露”问题纠结。Java应用程序占用的内存在不断的、有规律的上涨,最终超过了监控阈值。福尔摩 斯不得不出手了!
说起Java的内存泄露,其实定义不是那么明确。首先,如果JVM没有bug,那么理论上是不会出现“无法回收的堆空间”,也就是说C/C++中的那种内 存泄露在Java中不存在的。其次,如果由于Java程序一直持有某个对象的引用,但是从程序逻辑上看,这个对象再也不会被用到了,那么我们可以认为这个 对象被泄露了。如果这样的对象数量很多,那么很明显,大量的内存空间就被泄露(“浪费”更准确一些)了。
不过,本文要说的内存泄露,并不属于上述原因,因此打上了引号。其具体原因,确实出乎意料。欲知详情,请看下面讲解。
分析内存泄露的一般步骤
如果发现Java应用程序占用的内存出现了泄露的迹象,那么我们一般采用下面的步骤分析
如果Java应用程序出现了内存泄露,千万别着急着把应用杀掉,而是要保存现场。如果是互联网应用,可以把流量切到其他服务器。保存现场的目的就是为了把 运行中JVM的heap dump下来。
JDK自带的jmap工具,可以做这件事情。它的执行方法是:
format=b的含义是,dump出来的文件时二进制格式。
file-heap.bin的含义是,dump出来的文件名是heap.bin。
<pid>就是JVM的进程号。
(在linux下)先执行ps aux | grep java,找到JVM的pid;然后再执行jmap -dump:format=b,file=heap.bin <pid>,得到heap dump文件。
analyze heap
将二进制的heap dump文件解析成human-readable的信息,自然是需要专业工具的帮助,这里推荐Memory Analyzer 。
Memory Analyzer,简称MAT,是Eclipse基金会的开源项目,由SAP和IBM捐助。巨头公司出品的软件还是很中用的,MAT可以分析包含数亿级对 象的heap、快速计算每个对象占用的内存大小、对象之间的引用关系、自动检测内存泄露的嫌疑对象,功能强大,而且界面友好易用。
MAT的界面基于Eclipse开发,以两种形式发布:Eclipse插件和Eclipe RCP。MAT的分析结果以图片和报表的形式提供,一目了然。总之个人还是非常喜欢这个工具的。下面先贴两张官方的screenshots:
言归正传,我用MAT打开了heap.bin,很容易看出,char[]的数量出其意料的多,占用90%以上的内存 。一般来说,char[]在JVM确实会占用很多内存,数量也非常多,因为String对象以char[]作为内部存储。但是这次的char[]太贪婪 了,仔细一观察,发现有数万计的char[],每个都占用数百K的内存 。这个现象说明,Java程序保存了数以万计的大String对象 。结合程序的逻辑,这个是不应该的,肯定在某个地方出了问题。
顺藤摸瓜
在可疑的char[]中,任意挑了一个,使用Path To GC Root功能,找到该char[]的引用路径,发现String对象是被一个HashMap中引用的 。这个也是意料中的事情,Java的内存泄露多半是因为对象被遗留在全局的HashMap中得不到释放。不过,该HashMap被用作一个缓存,设置了缓 存条目的阈值,导达到阈值后会自动淘汰。从这个逻辑分析,应该不会出现内存泄露的。虽然缓存中的String对象已经达到数万计,但仍然没有达到预先设置 的阈值(阈值设置地比较大,因为当时预估String对象都比较小)。
但是,另一个问题引起了我的注意:为什么缓存的String对象如此巨大?内部char[]的长度达数百K。虽然缓存中的 String对象数量还没有达到阈值,但是String对象大小远远超出了我们的预期,最终导致内存被大量消耗,形成内存泄露的迹象(准确说应该是内存消 耗过多) 。
就这个问题进一步顺藤摸瓜,看看String大对象是如何被放到HashMap中的。通过查看程序的源代码,我发现,确实有String大对象,不 过并没有把String大对象放到HashMap中,而是把String大对象进行split(调用String.split方法),然后将split出 来的String小对象放到HashMap中 了。
这就奇怪了,放到HashMap中明明是split之后的String小对象,怎么会占用那么大空间呢?难道是String类的split方法有问题?
查看代码
带着上述疑问,我查阅了Sun JDK6中String类的代码,主要是是split方法的实现:
可以看出,Stirng.split方法调用了Pattern.split方法。继续看Pattern.split方法的代码:
注意看第9行:Stirng match = input.subSequence(intdex, m.start()).toString();
这里的match就是split出来的String小对象,它其实是String大对象subSequence的结果。继续看 String.subSequence的代码:
String.subSequence有调用了String.subString,继续看:
看第11、12行,我们终于看出眉目,如果subString的内容就是完整的原字符串,那么返回原String对象;否则,就会创建一个新的 String对象,但是这个String对象貌似使用了原String对象的char[]。我们通过String的构造函数确认这一点:
为了避免内存拷贝、加快速度,Sun JDK直接复用了原String对象的char[],偏移量和长度来标识不同的字符串内容。也就是说,subString出的来String小对象 仍然会指向原String大对象的char[],split也是同样的情况 。这就解释了,为什么HashMap中String对象的char[]都那么大。
原因解释
其实上一节已经分析出了原因,这一节再整理一下:
原因找到了,也就有了。split是要用的,但是我们不要把split出来的String对象直接放到HashMap中,而是调用一下 String的拷贝构造函数String(String original),这个构造函数是安全的,具体可以看代码:
只是,new String(string)的代码很怪异,囧。或许,subString和split应该提供一个选项,让程序员控制是否复用String对象的 char[]。
是否Bug
虽然,subString和split的实现造成了现在的问题,但是这能否算String类的bug呢?个人觉得不好说。因为这样的优化是比较合理 的,subString和spit的结果肯定是原字符串的连续子序列。只能说,String不仅仅是一个核心类,它对于JVM来说是与原始类型同等重要的 类型。
JDK实现对String做各种可能的优化都是可以理解的。但是优化带来了忧患,我们程序员足够了解他们,才能用好他们。
一些补充有个地方我没有说清楚。
我的程序是一个Web程序,每次接受请求,就会创建一个大的String对象,然后对该String对象进行split,最后split之后的 String对象放到全局缓存中。如果接收了5W个请求,那么就会有5W个大String对象。这5W个大String对象都被存储在全局缓存中,因此会 造成内存泄漏。我原以为缓存的是5W个小String,结果都是大String。
“抛出异常的爱”同学,在回帖(第7页)中建议用"java.io.StreamTokenizer"来解决本文的问题。确实是终极,比我上面提到的“new String()”,要好很多很多。
【翻译】(69)uses-sdk元素
see
http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
原文见
http://developer.android.com/guide/topics/manifest/uses-sdk-element.html
-------------------------------
<uses-sdk>
uses-sdk元素
-------------------------------
* syntax:
* 语法:
-------------------------------
<uses-sdk android:minSdkVersion="integer"
android:targetSdkVersion="integer"
android:maxSdkVersion="integer" />
-------------------------------
* contained in:
* 被包含在:
<manifest>
* description:
* 描述:
Lets you express an application's compatibility with one or more versions of the Android platform, by means of an API Level integer. The API Level expressed by an application will be compared to the API Level of a given Android system, which may vary among different Android devices.
让你表达一个应用程序和Android平台的一个或多个版本的兼容性,通过一个API级别整数。应用程序表达的API级别将和一个给定的Android系统的API级别比较,而它可能在不同的Android设备之间有区别。
Despite its name, this element is used to specify the API Level, not the version number of the SDK (software development kit) or Android platform. The API Level is always a single integer. You cannot derive the API Level from its associated Android version number (for example, it is not the same as the major version or the sum of the major and minor versions).
虽然这个元素的名称,它被用于指定API级别,并不是SDK(软件开发工具箱)的版本号或Android平台。API级别总是一个单一的整数。你不可以从它关联的Android版本号中派生出API级别(例如,它不同于主版本号或主次版本号的和)。
For more information, read about Android API Levels and Versioning Your Applications.
想获得更多信息,请阅读关于Android API级别以及版本化你的应用程序。
-------------------------------
Android Market and <uses-sdk> attributes
Android市场和<uses-sdk>属性
Android Market filters the applications that are visible to users, so that users can only see and download applications that are compatible with their devices. One of the ways Market filters applications is by Android version-compatibility. To do this, Market checks the <uses-sdk> attributes in each application's manifest to establish its version-compatibility range, then shows or hides the application based on a comparison with the API Level of the user's Android system version. For more information, see Market Filters.
Android市场过滤对于用户可见的应用程序,使用户只可以看到和下载兼容它们设备的应用程序。市场过滤应用程序的其中一种方式是通过Android的版本兼容性。为了做到这点,市场检查每个应用程序的清单中的<uses-sdk>属性以建立它的版本兼容性范围,然后展示或隐藏应用程序,基于对用户的Android系统版本的API级别的比较。想获得更多信息,参见市场过滤器。
-------------------------------
* attributes:
* 属性:
* android:minSdkVersion
An integer designating the minimum API Level required for the application to run. The Android system will prevent the user from installing the application if the system's API Level is lower than the value specified in this attribute. You should always declare this attribute.
一个整数,它指示应用程序运行所必需的最小API级别。Android系统将阻止用户安装该应用程序,如果系统的API级别低于这个属性中指定的值。你应该总是声明此属性。
-------------------------------
Caution: If you do not declare this attribute, the system assumes a default value of "1", which indicates that your application is compatible with all versions of Android. If your application is not compatible with all versions (for instance, it uses APIs introduced in API Level 3) and you have not declared the proper minSdkVersion, then when installed on a system with an API Level less than 3, the application will crash during runtime when attempting to access the unavailable APIs. For this reason, be certain to declare the appropriate API Level in the minSdkVersion attribute.
警告:如果你不声明此属性,那么系统假设默认值为"1",它指示你的程序兼容所有Android版本。如果你的应用程序不兼容所有版本(例如,它使用API级别3中引入的API)并且你不曾声明合适的minSdkVersion,那么当安装在带有小于3的API级别的系统上时,应用程序将在运行时期间崩溃,当它尝试访问不可用的API时。因此,请确保在minSdkVersion属性中声明合适的API级别。
-------------------------------
* android:targetSdkVersion
An integer designating the API Level that the application targets. If not set, the default value equals that given to minSdkVersion.
一个整数,指示应用程序目标定为的API级别。如果未被设置,那么默认值等于给予minSdkVersion的值。
This attribute informs the system that you have tested against the target version and the system should not enable any compatibility behaviors to maintain your app's forward-compatibility with the target version. The application is still able to run on older versions (down to minSdkVersion).
这个属性告诉系统你已经针对该目标版本测试过并且该系统不应该使能任何兼容行为以维持你的应用对目标版本的向后兼容性。该应用程序仍然可以运行在较旧的版本(低至minSdkVersion)。
As Android evolves with each new version, some behaviors and even appearances might change. However, if the API level of the platform is higher than the version declared by your app's targetSdkVersion, the system may enable compatibility behaviors to ensure that your app continues to work the way you expect. You can disable such compatibility behaviors by specifying targetSdkVersion to match the API level of the platform on which it's running. For example, setting this value to "11" or higher allows the system to apply a new default theme (Holo) to your app when running on Android 3.0 or higher and also disables screen compatibility mode when running on larger screens (because support for API level 11 implicitly supports larger screens).
当Android伴随每个新版本进化时,一些行为甚至外观可能会改变。然而,如果平台的API级别高于你的应用的targetSdkVersion声明的版本,那么系统可以使能兼容行为以确保你的应用继续以你期望的方式工作。你可以屏蔽这种兼容行为,通过指定targetSdkVersion以匹配它正在运行在的平台的API级别。例如,设置这个值为"11"或更高,会允许系统应用一个新的默认主题(Holo)到你的应用,当运行在Android 3.0或更高,而且还屏蔽屏幕兼容性模式,当它运行在较大的屏幕(因为对API级别11的支持隐式地支持较大的屏幕)。
There are many compatibility behaviors that the system may enable based on the value you set for this attribute. Several of these behaviors are described by the corresponding platform versions in the Build.VERSION_CODES reference.
存在系统可能使能的许多兼容行为,基于你为这个属性设置的值。这些行为中的一些被Build.VERSION_CODES参考中对应的平台版本描述。
To maintain your application along with each Android release, you should increase the value of this attribute to match the latest API level, then thoroughly test your application on the corresponding platform version.
为了维护你的应用程序和每个Android发布版在一起,你应该递增此属性的值以匹配最新的API级别,然后彻底地在对应的平台版本上测试你的应用程序。
Introduced in: API Level 4
引入:API级别4
* android:maxSdkVersion
An integer designating the maximum API Level on which the application is designed to run.
一个整数,指出该应用程序被设计运行所在的最大API级别。
In Android 1.5, 1.6, 2.0, and 2.0.1, the system checks the value of this attribute when installing an application and when re-validating the application after a system update. In either case, if the application's maxSdkVersion attribute is lower than the API Level used by the system itself, then the system will not allow the application to be installed. In the case of re-validation after system update, this effectively removes your application from the device.
在Android 1.5,1.6,2.0,和2.0.1,系统检查这个属性的值,在安装一个应用程序和在一次系统更新后重新验证该应用程序的时候。在其中一种情况下,如果该应用程序的maxSdkVersion属性低于系统自身使用的API级别时,那么系统将不允许应用程序被安装。在系统更新后的重新验证的情况下,它有效地从设备中移除你的应用程序。
To illustrate how this attribute can affect your application after system updates, consider the following example:
为了描述这个属性如何可以在系统更新后影响你的应用程序,请考虑以下示例:
An application declaring maxSdkVersion="5" in its manifest is published on Android Market. A user whose device is running Android 1.6 (API Level 4) downloads and installs the app. After a few weeks, the user receives an over-the-air system update to Android 2.0 (API Level 5). After the update is installed, the system checks the application's maxSdkVersion and successfully re-validates it. The application functions as normal. However, some time later, the device receives another system update, this time to Android 2.0.1 (API Level 6). After the update, the system can no longer re-validate the application because the system's own API Level (6) is now higher than the maximum supported by the application (5). The system prevents the application from being visible to the user, in effect removing it from the device.
一个在它的清单中声明maxSdkVersion="5"的应用程序被发布在Android市场上。一位用户,他的设备正在运行Android 1.6(API级别4),下载并安装该应用。在几星期后,用户收到一个到Android 2.0的空中系统更新(API级别5)。在更新后被安装后,系统检查应用程序的maxSdkVersion并成功地重新验证它。应用程序正常地起作用。然而,一段时间后,设备收到另一个系统更新,这次是升到Android 2.0.1(API级别6)。在更新后,系统可能不再重新验证该应用程序,因为系统自己的API级别(6)现在高于应用程序支持的最大值(5)。系统阻止应用程序对用户可见,事实上从设备中移除它。
-------------------------------
Warning: Declaring this attribute is not recommended. First, there is no need to set the attribute as means of blocking deployment of your application onto new versions of the Android platform as they are released. By design, new versions of the platform are fully backward-compatible. Your application should work properly on new versions, provided it uses only standard APIs and follows development best practices. Second, note that in some cases, declaring the attribute can result in your application being removed from users' devices after a system update to a higher API Level. Most devices on which your application is likely to be installed will receive periodic system updates over the air, so you should consider their effect on your application before setting this attribute.
警告:不建议声明此属性。首先,没有必要设置该属性作为阻碍你的应用程序安装到新版本的Android平台上的方式,当它们被发布时。设计上,新版本的平台完全是向后兼容的。你的应用程序应该在新版本上工作正常,假设它只使用标准API并且遵循开发的最佳实践。其次,注意在一些情况下,声明该属性可以导致在系统升级到一个较高API级别之后你的应用程序从用户设备中被移除。你的应用程序可能打算被安装在的大多数设备将通过无线网络收到周期性系统更新,所以在设置这个属性前你应该考虑它们对你的应用程序的影响。
-------------------------------
Introduced in: API Level 4
引入:API级别4
-------------------------------
Future versions of Android (beyond Android 2.0.1) will no longer check or enforce the maxSdkVersion attribute during installation or re-validation. Android Market will continue to use the attribute as a filter, however, when presenting users with applications available for download.
Android的未来版本(在Android 2.0.1之后)将不再在安装或重新验证期间检查或实施maxSdkVersion属性。然而,Android市场将继续使用该属性作为一个过滤器,当把可用于下载的应用程序展示给用户时。
-------------------------------
* introduced in:
* 引入:
API Level 1
API级别1
Except as noted, this content is licensed under Apache 2.0. For details and restrictions, see the Content License.
除特别说明外,本文在Apache 2.0下许可。细节和限制请参考内容许可证。
Android 4.0 r1 - 14 Feb 2012 21:12
-------------------------------
Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
(此页部分内容基于Android开源项目,以及使用根据创作公共2.5来源许可证描述的条款进行修改)
(本人翻译质量欠佳,请以官方最新内容为准,或者参考其它翻译版本:
* ソフトウェア技術ドキュメントを勝手に翻訳
http://www.techdoctranslator.com/android
* Ley's Blog
http://leybreeze.com/blog/
* 农民伯伯
http://www.cnblogs.com/over140/
* Android中文翻译组
http://androidbox.sinaapp.com/
)
1.在activity中 Intent i = new Intent(); i.setAction("ck"); //ck是随便定义的一个字符串 i.putExtra("msg","message from broadcast..."); sendBroadcast(i); 2.在Manifest中 <receiver android:name="MyReceiver"> //MyReceiver是接受类的名字 <intent-filter> <action android:name="ck"/> //和之前的字符串保持一致.. </intent-filter> </receiver> 3.在接受类MyReceiver中 MyReceiver继承BroadcastReceiver 重写onReceive方法,获取msg信息.. String msg = intent.getString("msg");