当前位置: 首页 > news >正文

java 内存泄漏_Java应用程序中的内存泄漏及内存管理

Java平台的一个突出的特性是自动内存管理。很多人把这种特性误读为Java没有内存泄露。然而,在我印象中,现代Java框架以及基于Java的平台并非如此。特别是Android平台,能举出很多反例。为了让大家对Java平台的内存泄露有一个初步的认识,我们先来看一个Java实现的栈:

c730f1c03b5448adef4e9baf4f035c36.png

这个栈的实现基于一个对象数组,并维护了一个用于指向栈内当前可用单元的整型指针。上面的实现中,每次从栈顶弹出元素都会产生内存泄露。确切的说,即使不再使用栈顶元素,对象数组会继续持有栈顶元素的引用(除非栈顶元素再次入栈,栈顶元素的引用会被完全相同的引用覆盖)。因此,即便这个对象的其他引用都被释放,Java虚拟机也不能回收这个对象。由于这种栈实现并不允许外界直接访问其底层的对象池,因此除非有新元素入栈并被放置在栈内的同一个位置上,否则这个无法访问的引用将阻止垃圾回收器回收该对象。

幸运的是,这个内存泄露很容易修复:

dc557f63c5fb63eb5d0def9668fa5c0a.png

当然,在日常的Java开发中一般不会去实现一个内存数据结构。因此,让我们来看一个更常见的Java内存泄漏的例子。在Java开发中经常用到的观察者模式就会引起内存泄露:

67a80f429ce4bd72740fc115f150241a.png

这次提供了一个直接删除底层对象池引用的方法。基于这种实现,任何已注册的Observer在使用后只要被正确注销,就不会存在内存泄漏的风险。然而,假设这样一个场景,框架的使用者在使用完Observer之后并没有及时注销。同理Observer将永远不会被回收,因为Observed一直保留着它的引用。更糟的是,没有Observer引用,是无法从Observed对象池外部删除Observer的,即无法回收未被及时注销的Observer。

不过,有一种简单的方法能够修复这种潜在的内存泄露——弱引用。我个人认为这是Java程序员都应该知道的特性。简单地说,弱引用在功能上和普通的引用一样,但它不会妨碍垃圾回收。因此JVM执行垃圾回收时,如果没有发现强引用,那么你就会发现弱引用会被置为null。要使用弱引用,我们可以将上面的代码改为:

d8d82c9a1a0d353895d2106d3c07a9c5.png

WeakHashMap是一个现成的弱引用Map,Map的键都是弱引用对象。使用WeakHashMap后,被观察者将不会阻止JVM对Observer进行垃圾回收。然而,你必须在代码注释中强调这一点。因为这个特性可能引起一些问题,比如使用者想要注册一个常驻内存的Observer(例如日志库),但他们并没有打算维持一个Observer引用。例如,Android平台上的OnSharedPreferencesChangeListener使用了弱引用,但文档中并没有声明这一特性。这给开发者带来了很多麻烦。

在本文的开头我提到了,现在的很多框架都需要使用者谨慎地管理内存。我想至少有两个例子可以印证这个观点。

Android平台

Android应用程序的核心类采用了基于生命周期的编程模型。这意味着你不能自行创建和管理这些类的实例,这些实例将由Android操作系统在需要的时候替你创建(比如应用程序需要显示某个特定的画面)。同理,Android操作系统将会决定应用何时不再需要某个特定实例(比如用户关闭了应用界面),并通过调用该实例特定的生命周期方法来通知该实例即将被删除。但是,如果你将这个实例的引用泄露到某个全局上下文,Android JVM将不能对这个实例进行回收。这与Android本身的设计理念相违背。由于Android手机通常没有限制应用程序的内存,即使在非常简单的应用中,也会频繁创建和销毁对象,所以在清理引用时必须格外小心。

不幸的是,应用程序核心类引用很容易被泄露到外部。你能看出下面的例子是如何泄露引用的吗?

1489a59f2b1c3f14d597d812a25dd91f.png

如果你认为是传入Intent构造函数的this指针泄露了当前实例的引用,你就错了。这个Intent对象仅用于启动ExampleService,它会在ExampleService启动之后被销毁。然而,那个实现了Serializable接口的匿名内部类会持有闭包类ExampleActivity的引用。如果ExampleService一直维持着这个匿名类实例引用,那么也会持有这个ExampleActivity实例的引用。

出于这个原因,我建议Android开发者避免使用匿名类。

Web应用框架(特别是Wicket)

Web应用框架通常将半永久性的用户数据存放在Session中。你在Session中写入的任何数据都会在内存中滞留,而且滞留的时间无法确定。如果有一定数量的访问者在你的Session中“乱扔垃圾”,运行Servlet容器的JVM早晚会挂掉。因此,你谨慎管理引用的另一个极端案例就是Wicket框架:Wicket框架会将用户的所有访问序列化成历史版本。这种过分简单的设计意味着,如果某个访问者点击十次欢迎页面,Wicket框架会在硬盘默认路径下序列化十个对象。Wicket页面对象持有的所有对象引用都会和页面对象一起被序列化到硬盘上,所以在管理引用时必须格外小心。

让我们来看一个错误使用Wicket框架的示例:

cdb177d63efc130943175fcdd13834c1.png

用户点击十次欢迎页面,就会在服务器硬盘上存储十份WorldPhoneBook拷贝。因此,在你使用Wicket开发应用时,务必要使用LoadableDetachableModels管理引用。

在Java程序中追踪内存泄漏是一件非常麻烦的事情,因此我想推荐一款非常好用的(但很可惜不是免费的)调式工具:JProfiler。它能够提供Java程序运行时的堆快照(heap dumps),帮助你了解程序运行时内部的具体情况。如果你的程序存在内存泄露的问题,我推荐你试一试JProfiler。JProfiler提供免费试用许可证。

相关文章:

  • python制作网页样式_HTML基础做出属于自己的完美网页
  • using在sql中是什么意思_知否 | “开到荼蘼”的“荼蘼”是什么东西?
  • python如何导入数据库生成图表_python数据库操作常用功能使用详解(创建表/插入数据/获取数据)...
  • python pip3 freeze_Python系列之包管理工具【pip3】
  • python元组倒序排列_python序列(列表,元组,字典)的常用排序
  • python k线顶分型_顶分型和底分型的确认及K线包含处理
  • oracle大量删除数据之后索引是否需要重建_深入浅出索引
  • python3.70_Python 2.7 辛苦了,你好Python 3.7
  • 内存分段分页机制理解_20 张图揭开「内存管理」的迷雾,瞬间豁然开朗
  • wpf项目无法使用针式打印机_针式打印机630K常见问题及解决方法
  • 控制网页frame vba_VBA网络通信基础
  • python str转list_python中从str中提取元素到list以及将list转换为str的方法
  • postman添加map_postman 脚本编程入门
  • 不同平台上安装python的方式是一样的吗_怎样在不同的操作系统上安装Python?
  • asp中注释掉代码_面试题:ASP.NET MVC笔试试卷
  • [deviceone开发]-do_Webview的基本示例
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • github指令
  • golang 发送GET和POST示例
  • IDEA常用插件整理
  • javascript 哈希表
  • javascript数组去重/查找/插入/删除
  • Linux CTF 逆向入门
  • Python连接Oracle
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 深度学习中的信息论知识详解
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 昨天1024程序员节,我故意写了个死循环~
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • (14)Hive调优——合并小文件
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (C语言)字符分类函数
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (转)菜鸟学数据库(三)——存储过程
  • **CI中自动类加载的用法总结
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net mvc部分视图
  • .NET Reactor简单使用教程
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .NET中统一的存储过程调用方法(收藏)
  • :=
  • ??eclipse的安装配置问题!??
  • @GlobalLock注解作用与原理解析
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce