让我们来了解一下JVM中垃圾回收机制的工作原理与思想吧!

此博文内容摘自《java编程思想》,我精选出来的片段并加以添加、修改,做以记录,以便翻看!如果有建议或者需要添加内容,请联系我,我将虚心接受您的赐教!不喜勿喷!谢谢~
 
 
垃圾回收机制用到了finalize()方法,并加以概述

  • 当对象被判定为垃圾对象时,由JVM自动调用此方法,用以标记垃圾对象
  • 垃圾对象:没有有效引用指向此对象时,为垃圾对象
  • 垃圾回收:由GC销毁垃圾对象,释放数据存储空间
  • 自动回收机制:JVM的内存耗尽,一次性回收所有垃圾对象
  • 手动回收机制:使用System.gc(); 通知JVM执行垃圾回收(作为了解即可)

注意:在C或C++中自己需要掌握对象在程序中的存活时间,而java的垃圾回收机制不需要在此手动回收,只要需要理解明白就好,使用不当则会造成内存泄漏

在这里简单科普一下内存:
内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
 
 

垃圾回收机制工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间, 将首先调用其finalize() 方法, 并且在下一次垃圾回收动作发生时, 才会真正回收对象占用的内存。

1.对象可能不被垃圾回收
2.垃圾回收并不等于“析构”
3.垃圾回收只与内存有关

Java并未提供“析构函数”或相似的概念, 要做类似的清理工作, 必须自己动手创建一个执行清理工作的普通方法。(所以这里不用担心,只是了解就OK!)

也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的在储空间,则随着程序的退出,那些资源也会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。

使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize) 方法) , 它们也必须同内存及其回收有关。无论对象是如何创建的, 垃圾回收器都会负责释放对象占据的所有内存。

Java中的堆未必完全像传送带那样工作。要真是那要想更好地理解Java中的垃圾回收, 先了解其他系统中的垃圾回收机制将会会导致频繁的内存页面调度——将其移进移出硬盘,因此会显得需要拥有比实际存。页面调度会显著地影响性能,最终,在创建了足够多的对象之后,内存资源的秘密在于垃圾回收器的介入。当它工作时,将一面回收空间,一面使堆中的对这样“堆指钍”就可以很容易移动到更靠近传送带的开始处,也就尽量避免了页垃圾回收器对对象重新排列,实现了一种高速的、有无限空间可供分配的堆模型。

在一些更快的模式中,垃圾回收器并非基于引用记数技术。它们依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果苁堆栈和静态存储区开始,遍历所有的引用,就能我到所有活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止。你所访问过的对象必须都是“活”的。注意,这就解决了“交互自引用的对象组”的问题这种现象根本不会被发现,因此也就被自动回收了。

在这种方式下, Java虚拟机将采用一种自适应的垃圾回收技术。至于如何处理找到的存活对于这种所谓的“复制式回收器”而言,效率会降低,这有两个原因。首先,得有两个堆,对象, 取决于不同的Java虚拟机实现。有一种做法名为停止一复制(sop-and-copy) 。显然这意味着,先暂停程序的运行(所以它不于后台恒收模式),然后将所有存活的对象从当前堆复铜到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间了。当把对象从一处搬到另一处时,所有指向它的那些引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到(可以想像成有个表格,将旧地址映射至新地址)。然后得在这两个分离的堆之间来回倒腾, 从而得维护比实际需要多一倍的空间。某些Java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。第二个问题在于复制。程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。

尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,这很浪费。为了避免这种情形, 一些Java虚拟机会进行检查:要是没有新垃圾产生, 就会转换到另一种工作模式(即“自适应”) 。这种模式称为标记-清扫(mark-and-sweep) , Sun公司早期版本的Java虚拟机使用了这种技术。对一般用途而言,“标记-清扫”方式速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,它的速度就很快了。

标记-清扫”所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
停止-复制”的意思是这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时, 程序将会被暂停。在Sun公司的文档中会发现, 许多参考文献将垃圾回收视为低优先级的后台进程, 但事实上垃圾回收器在Sun公司早期版本的Java虚拟机中并非以这种方式实现的。当可用内存数量较低时, Sun版本的垃圾回收器会暂停运行程序, 同样, “标记-清扫”工作也必须在程序暂停的情况下才能进行。

如前文所述, 在这里所讨论的Java虚拟机中, 内存分配以较大的“块”为单位。如果对象较大,它会占用单独的块。严格来说,“停止一复制”要求在释放旧有对象之前,必须先把所有存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的代数(gen at in count) 来记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。Java虚拟机会进行监视, 如果所有对象都很稳定, 垃圾回收器的效率降低的话, 就切换到“标记-清扫”方式; 同样, Java虚拟机会跟踪“标记-清扫”的效果, 要是堆空间出现很多碎片,就会切换回“停止-复制”方式。这就是“自适应”技术,你可以给它个罗嗦的称呼:“自适应的、分代的、停止-复制、标记-清扫”式垃圾回收器。

Java虚拟机中有许多附加技术用以提升速度。尤其是与加载器操作有关的, 被称为“即时”(Just-In-Time, JIT) 编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作) , 程序运行速度因此得以提升。当需要装载某个类(通常是在为该类创建第一个对象) 时, 编译器会先找到其.class文件, 然后将该类的字节码装入内存。此时,有两种方案可供选择。一种是就让即时编译器编译所有代码。但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另一种做法称为惰性评估(lazy evaluation) , 意思是即时编译器只在必要的时候才编译代码。这样, 从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了类似方法,代码每次被执行的时候都会做一些优化,所以执行的次数越多,它的速度就越快。

发布了103 篇原创文章 · 获赞 162 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_44170221/article/details/104772868