java认知描述_Java技术:JVM的初步认识( 四 )


不得不提的一种是引用计数法,实现起来最简单,一个对象被引用一次,计数器就+1,失去引用就计数器-1,等到计数器减为0了,这个对象就没有其他对象在使用了,也就可以对它进行回收了.这种算法效率很高,但这种会有一个问题在于,两个对象相互引用,但两个对象都没有被其他对象继续引用了,计数器仍然不会减为0.
通过引用计数来看,node1被node2引用着,node2也被node1引用着,两个互相引用,却没有其他地方在引用,应该被清除掉,但引用计数器的值并没有减为0,无法回收 。所以几乎已经被现代语言抛弃掉了,取而代之的是可达性分析标记存活对象而后使用其他算法.
可达性分析是从一个GC Root节点开始找引用的节点,找到后继续找其引用的节点,直到查找完毕,其余没有被找到过的节点就是垃圾节点,一般作为GC Root的对象有Java栈中的本地变量对象,方法区的静态变量引用的对象,方法区的常量引用的对象,本地方法栈中引用的对象等.
如上图所示,遍历所有的GC Root(黑色的对象),然后向下寻找所有的引用关系,能够找到的就标记为存活(蓝色的对象) 。而无法找到的,也就无法打上标记(黄色的对象),这些没有存活标记的就是可以回收的对象 。
4.4.3基本垃圾回收算法
大多数人对于GC的直观感受是,飘忽不定,它执行的时间是不确定的,就算手动调用.gc()也不见得会执行.但其实不尽然,GC作为一个守护线程,它的优先级是随着内存使用情况不断变化的,会在可用内存低到一定程度后自动调用.
基本GC算法主要是标记-清除算法,复制算法,标记-整理算法.
标记-清除算法其实在JVM中没怎么露脸,但它是现代GC算法的基础 。通过可达性分析,将存活的对象打上标记,然后对全部对象进行扫描,将没有标记的对象清除掉.这种算法会有一个问题,清除废弃对象后,释放的内存并不是连续的,而是一个个内存碎片,这对于后续JVM分配内存并不是很好,如果需要一块较大的连续内存就没有办法将这些碎片利用起来.并且它需要遍历所有的对象,清除没有标记的,这种性能消耗很大 。
复制算法,一般应用于新生代,这也是为什么新生代要设计成一个Eden,两个区的原因 。所有对象都在Eden创建出来,每次gc就会把Eden和其中一个正在使用的区中存活的对象复制到另外一个没有使用的区 。然后清除掉原来内存区的所有对象,也就是废弃的对象 。每次gc都这样操作,始终留一个区不使用 。这种算法的好处在于不会残留内存碎片,方便内存管理,但是需要预留一块内存,并且性能消耗是根据存活对象多少而来的,不适用于存活对象较多的情况 。

java认知描述_Java技术:JVM的初步认识

文章插图
标记-整理算法,是标记-清除算法的升级版,一般用于老年代 。它将标记存活的对象统一移到内存的某一端,然后将边界外的空间清空 。这样既不会占着一块内存作为备用,也不会存在内存碎片无法有效利用 。但是由于要遍历存活的对象,还有重新存活对象的引用地址,所以效率要低于复制算法 。
4.4.4分代回收算法
正如我们前面了解到的,新生代和老年代各自的情况不同,直接把某种算法套用在两个区上,可能效果并不理想 。而现在商业虚拟机的GC都是采用的分代回收算法,不同的堆分区采用不同的算法进行回收 。
4. GC和Full GC
在说这两种回收的区别之前,我们先来说一个概念,"Stop-The-World" 。
如字面意思,每次垃圾回收的时候,都会将整个JVM暂停,回收完成后再继续 。如果一边增加废弃对象,一边进行垃圾回收,完成工作似乎就变得遥遥无期了 。