很长一段时间,相当多的人(主要来自学术界和/或Java编程团队)忠实地相信一种可怕的误解,即“垃圾收集的程序不可能有内存泄漏”(或者至少是“垃圾收集的程序中内存泄漏从根本上说是更难的”)。尽管与Java内存泄漏相关的问题早在1999年就已经讨论过了,而且经常在与上面的误解相同的地方进行讨论。
然而,随着时间的推移,{大多数|相当多|一些}真实世界的Java程序随着时间的推移成为可怕的内存吞噬者的现实
Java中存在内存泄漏
我会根据你所在的营地选择一个,但不要忘记Eclipse和OpenHAB
句法和语义内存泄露
任何一个不太好的垃圾回收器都会确保不可访问的对象被清除;但是,尽管所有不可访问的对象都是无用的,
并非所有无用的对象都是不可访问的
将那些无法访问但仍存在于程序中的对象称为语法内存泄漏,以及那些无用但仍然可以访问的对象称为语义内存泄漏。
到目前为止还不错,但是现在我们不得不注意到,从程序的最终用户的角度来看,我不在乎程序的不可访问性,我关心的是程序在使用了半天之后不会进入交换状态;即使是那些不容易被删除的语法对象,我也不可能因为内存的缺失而被删除。
事实上,它是“终将被清理干净”,但本着对那些已经受苦受难的人友善的精神,我们将暂时忘记这个词
Java内存中的语义泄漏
Java中出现内存泄漏的常见情况有很多,但大多数情况归结为要么忘记从某个集合中删除对某个项的引用,要么忘记设置不再需要的对null的引用。实际上,如果我们在一个集合中保留一些无用的东西,或者保留对一个不再需要的对象的引用而没有机会再次使用这个引用,那么我们确实存在语义内存泄漏。
有些作者倾向于将后一个问题简单化为“嘿,让我们小心可变静态数据”;然而,由于对可变静态/全局数据(是的,这包括单例数据)的不尊重,我不得不说语义内存泄漏的问题并不局限于静态(事实上,静态只是一种特殊的数据)现有但从未使用过的案例)。例如,即使我把非空引用放在堆栈上,它也不会被释放,直到我超过堆栈中的这一点——这取决于应用程序,它可以很容易地持续到app will uspart的死亡。
一个这样的例子是一个对象,它的引用由main()
函数保存。更一般地说,只要我们有任何类型的顶级循环(比如事件循环),那么事件循环为我们保留的所有对象,包括通过来自任何此类对象的引用可访问的所有对象,都需要手动将其引用设为空,以避免此类引用成为语义内存泄漏。
C/C++如何?
因此,在Java中,为了避免语义内存泄漏,我们确实需要使用x=null
;以避免内存泄漏。但是这是一个精确的等价于显式删除的方法,必须在C/C++中完成!,尽管原因不同(为了避免悬而未决的指针)!
让我们比较以下三段代码:
//pre-C++11 C++
struct State {
uint8_t* data;
void addData() {
data = new uint8_t[1000000];
//do something with data
}
void removeData() {
delete [] data;
data = nullptr;//(*)
}
~State() {
delete [] data;
}
};
//post-C++11 C++
struct State {
std::unique_ptr<uint8_t[]> data;
void addData() {
data = make_unique<uint8_t[]>(1'000'000);
//do something with data
}
void removeData() {
data.reset();//(*)
}
};
//JAVA
class State {
byte[] data;
void addData() {
data = new byte[1000000];
//do something with data
}
void removeData() {
data = null;//(*)
}
};
从我目前的观点来看,这三段代码在语义上是完全相同的(即,唯一的区别是关于语法的——TBH也没有太大区别)。
他们真的一模一样吗?嗯,不完全是…
尽管在这方面两种编程语言下可以被视为“安全和无内存泄漏的代码”之间有着惊人的相似之处,但仍然有一个主要的区别。
具体来说,如果我们忘记将null赋给以(*)(或调用CeSeCe(11)C++)标记的行中的数据,则效果将不同:
- C++惩罚访问已删除的数据,最坏的情况是数据被破坏
- Java在这方面明显更为宽松,而遗忘的data=null只会受到语义内存泄漏的惩罚。
- 正是这种宽容导致了Java程序中语义内存泄漏无处不在:一个C++程序崩溃是一个明显的错误,它比java程序更容易被固定,语义内存泄漏(除java以外),内存泄漏常常不明显,直到有人运行程序很多小时。在大多数常规测试中被忽略
- 此外,在Java中,即使在我们这里为null之后,也有机会让其他类的实例引用数据。据我所见,这种隐藏的引用是复杂的真实Java程序中语义内存泄漏的主要来源。
- C++ C++ 11的行为在这方面更像java。
- 它仍然与java完全不同,因为C++的
unique_ptr<>
保证是对数据对象的唯一引用。这反过来又消除了那些类似Java的隐藏引用,从而大大减少了我们发生语义内存泄漏的可能性。然而,C++下,这样一个隐藏的引用将变成一个悬空指针,再次引起内存损坏。
总结
在C++和java环境下,代码可以被认为是“好”内存(从崩溃和内存泄漏中安全)。
是的,与很多书中所告诉我们的相反,即使在用Java编程时,我们也必须考虑内存管理(嘿,有人会说data=null是手动内存管理)。
然而,如果我们偏离了这种“好”代码实践,不同的编程语言将惩罚我们不同的(C++中的可能是崩溃或内存损坏,在java中它可能是语义内存泄漏)。
换句话说,当从C++移动到java时,我们正在进行内存泄漏的崩溃处理。
由于内存泄漏不像崩溃那样明显,它们有生存更长时间(通常更长)的趋势。换句话说,当从C++移动到java时,我们倾向于处理一些崩溃来存储大量的内存泄漏,BTW倾向于与我个人的任何经验/轶事证据一致。我不想争论这是否是一个很好的权衡。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/1115.html
暂无评论