JVM-线程,内存 ,finalize
2019-03-01 本文已影响0人
简书徐小耳
目前jvm的后台线程有如下:
image.pngJVM内存区域
image.png永久代/方法区
- 被 JVM 加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)
永久代和元数据区
- 在 Java8 中, 永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。 类的元数据放入 nativememory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
finalize方法的机制
- 一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存.所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作.
- finalize只会调用一次 如果我们在finalize方法内部救活了这个对象 然后再下次回收会直接执行。
- 如果这个对象被判定为有必要执行 finalize 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动建立的,优先级为 8 的 Finalizer 线程去执行它。(JVM 不保证这些对象的方法都被执行)
真实的逻辑
- 只要实现finalize方法的类都会被JVM调用了Finalizer类的register方法把我们的类包装成Finalizer。并把这个Finalizer加入到 unfinalized 的链表中。
- 而Finalizer是FinalReference类型的,当我们的自己的类快被回收则Finalizer会被加入到ReferenceQueue里面(通过Reference的线程进行回收到队列)
- 然后我们finalizer线程去队列里面取出Finalizer然后执行finalize方法。
- 通过这个我们知道当我们的Reference放入队列的时候,持有的对象还没有被回收只是被标记要回收了。(对于其他引用可能已经回收了 我们这边没有回收是因为finalize链表的存在)
- 当我们在finalize方法救活这个对象则下次再被释放的时候虽然还会放入队列 但是不会再执行finalize方法。这是因为我们执行完了finalize方法之后会clear referent。这样下次再执行finalize发现referent不存在 则不会执行finalize方法。
总结
- finalizer对象因为Finalizer的引用而变成了一个临时的强引用(链表类似Cleaner),即使没有其他的强引用了,还是无法立即被回收;
- finalizer对象至少经历两次GC才能被回收,因为只有在FinalizerThread执行完了finalizer对象的finalize方法的情况下才有可能被下次gc回收,而有可能期间已经经历过多次gc了,但是一直还没执行finalizer对象的finalize方法;
- cpu资源比较稀缺的情况下FinalizerThread线程有可能因为优先级比较低而延迟执行finalizer对象的finalize方法;
- 因为finalizer对象的finalize方法迟迟没有执行,有可能会导致大部分finalizer对象进入到old分代,此时容易引发old分代的gc,甚至fullgc,gc暂停时间明显变长;
- finalizer对象的finalize方法被调用了,但是这个对象其实还并没有被回收,虽然可能在不久的将来会被回收。
- 一般情况下弱引用进入队列时候referent已经被回收,虚引用则对象还没被回收(即将被回收)