bootstrap 数据库
JVM
首先明确对象(引用)类型:
1 软引用 SoftReference
2 弱引用
3 虚引用 PhantomReference
最弱,被垃圾回收的时候会收到一个通知。
4 强引用
new创建。软引用、弱引用在jdk1.2才有。一般使用强引用,内存不够情况例外。软引用(SoftReference)和弱引用 (WeakReference),有时候做缓存使用。
如何判断对象是否可以被回收?
方法一、引用计数法
顾名思义,计算对象被引用的次数。但是引用计数法已经落后了,可达性分析能够解决互相引用的问题(两种方法都是基于强引用)。
方法二、可达性分析
可达性指GC Root能否找到某一对象,如果不能该对象可大致(不绝对,后面会讲)认为能够被回收。
以下对象可以作为GC Root:
注意,不需要死记硬背。可以这么思考:Java中变量总共四类——局部变量、静态变量 、常量、成员变量。方法区中常量引用的对象即局部变量;而虚拟机栈和本地方法栈是线程运行时创建的,里面引用的对象需要被使用,不能被回收。
为什么说:开酒店宾馆的最烦要免费房的股东?
我来回答一个吧。
什么叫做垃圾?
在GC中如何来判断一个对象是否为垃圾呢?
reference count(引用计数法)
主要是查看该对象是否还有引用指向它,如果有则说明该对象不是垃圾,反之则为垃圾。
具体就是给一个对象脑门上标一个数字用来记录有多少个引用指向了该对象,当这个数字记录为0时,那就表示这个对象已经没有引用指向它了,那么这个对象就变成了垃圾。
通过上图可以看到:
在阶段一时,这个对象脑门的数字是3,表示有三个引用指向它,所以这时它不是垃圾;
到了阶段二时,对象脑门上的数字变成了2,表示有两个引用指向它,这时它也不是垃圾;
依此,到了阶段三数字变成了1,还有一个引用指向它,这时还不是垃圾,直到阶段四,这个数字变成了0,没有引用再指向它了,这时候这个对象就变成了垃圾,垃圾回收器就可以将它回收了。
引用计数法存在的问题
当出现循环引用时,如下图:
这时候,A、B、C三个对象各自指向这个循环中的另一个对象,也就是这三个对象的脑门上都有数字1,但是实际上又没有其他的对象指向他们三个,这种情况下,A、B、C三个对象都应该属于垃圾,需要被垃圾回收器清理掉。
但问题是按照引用计数法来判断的话,这三个对象脑门上的数字都不是0,所以垃圾回收器就无法找到这三个对象来清理掉,需要解决这个问题,JVM引入了另外一种算法来判断对象是否为垃圾-根可达算法(Root Searching)。
根可达算法(Root Searching)
名词解析:
根可达算法:是从根上对象开始搜索的算法。
线程栈变量:一个main方法开始运行,main线程栈中的变量调用了其他变量,main栈中的方法访问到的对象叫根对象。
静态变量:T.class对静态变量初始化时能访问到的对象叫根对象。
常量池:如果一个class能够用其他class的对象叫根对象。
JNI指针:如果调用本地方法运用到的对象叫根对象。
根对象:当一个程序启动的时候需要用到的对象叫根对象。
根可达算法意思就是从根上开始搜,Java程序是从一个main方法开始运行的,一个main方法会开启一个线程,这个线程就会有线程栈,里面就会有main栈帧。从main栈帧里面创建出来的对象都是根对象,当然main方法里面调用了别的方法,那别的方法也是我们引用到的,这些都是有引用的对象,但是从main开始的这个栈帧里的对象都叫做根对象。
另外一个是静态变量,一个class它有一个静态的变量,class的被load到内存之后就会对静态变量进行初始化,所以静态变量访问到的对象也是根对象。
还有就是常量池,如果你这个class会用到其他class的那些类的对象,那这些对象也是根对象。
最后是JNI,JNI指的是如果你调用了C和C++写的那些本地方法所用的的对象也是根对象。
如上图所示,对象一、二、三、四、五均是存在根对象的引用,对象五、六之间是我们上面所提到的循环引用,对象八不存在引用,故对象六、七、八是垃圾。
三色标记算法原理
GC Algorithms(常见的垃圾回收算法) 到目前为止一共有三种。
- Mark-Sweep(标记清除)
- Copying(拷贝)
- Mark-Compact(标记压缩或标记整理)
1.Mark-Sweep(标记清除)
将可回收的垃圾对象进行标记定位,清除被标记的对象即可,将垃圾位变为可用位。
这个算法非常简单,但存在缺点,长时间运行会存在大量的内存碎片。
何为碎片?
由上述得知,每一小块可回收内存均需要标记后单独清除,在业务量较大,频繁更新数据的情况下,会有个别的“碎片”长期存在于内存中不被使用,占用资源空间。大量的碎片就会造成查询效率极其低下,所以我们就需要进行处理。
2.Copying(拷贝)
直接把内存一分为二,分开之后把有用的对象拷贝到下面绿色的区域,拷贝完之后,直接把上面的全部清掉即可。
由于每次拷贝完成,所有的内存空间都是排列在一起的,因此Copying(拷贝)算法就可以解决上面提到的碎片化问题,但是会造成内存空间的浪费,每次仅可以使用一般的内存空间进行操作。
2.Mark-Compact(标记压缩或标记整理)
把有用的对象全部压缩到整块内存的最前面,剩下的大块空间就全部清出来了,既不存在碎片化问题,也不浪费空间。
标记压缩存在的问题是效率过低,由于每次在压缩之间都需要计算空间,导致回收的效率大大降低。
垃圾回收器的制定原则
上述三种标记算法可谓是各有利弊,因此在实际应用中,一个垃圾回收器的制定是综合了上述三种算法。
新生代分为:
- eden(伊甸)默认比例8:是我们刚刚新new出来对象之后就往里面扔的内存区域。
survivor区又分为两个区域:
- survivor0 默认比例1:是对象被回收一次之后跑到这个区域的,这里面由于装的对象不同,所以采取的算法也不同。
- survivor1 默认比例1
新生代存活的对象特别少,死去的对象特别多,适合使用Copying算法。
old老年代
- tenured(终身)
老年代活着的对象特别多,适用于Mark-Compact算法。
如上图,我们将新诞生的对象存放在新生代里。如果新诞生的对象经历了数次垃圾回收仍然没有被回收掉(即每经历一次垃圾回收,该对象年龄 +1,即 age++),当 age 到达一定数值,将该对象置于老年代中进行特殊处理。
我们来看一个对象从出生到消亡的过程,通过上图你应该就明白这个对象是怎么进行GC过程的。
一个对象产生之后首先在栈上分配,栈上如果分配不下就会进入伊甸区,伊甸区经过一次垃圾回收之后进入survivor区,survivor区再经过一次回收后又进入另一个survivor区,什么时候对象的年龄够了就会进入Old区,这就是整个对象的一个逻辑移动过程。
几个问题
1.新生代里面对象的 age 要取值多少?
这个即是我们进行 JVM 调优所需要的自行调整的,根据项目需求来设置。
同时对于年龄的设置,与具体所使用的 GC 息息相关。
参数:
-XX:Maxtenuringthreshholdbr
- 如果之前没有对 GC 进行调整或调优的话,默认使用的GC 是 PS+PO(Parallel Scavenge+ParallelOld),默认年龄为 15。
- 如果进行调整之后所使用的 GC 是 CMS,那 age 就是 6。
- 如果使用的 GC 是 G1 的话,则就彻底与 age 无关,因为该 GC 不分代。
关于GC的分类,在下篇文章中会详细讲到。
2.为什么年轻代用 Copying(拷贝)算法?
首先我们先考虑Mark-Sweep(标记清除)和 Mark-Compact(标记压缩或标记整理),上面我们已经说到,这两种 GC 算法的缺点分别是:产生碎片化问题、内存回收效率低。
程序产生对象后,该对象很可能会在很短的时间内被回收,根据统计,一次垃圾回收可以回收掉 90% 的对象。在这样的情况下,使用 Mark-Sweep(标记清除)和 Mark-Compact(标记压缩或标记整理)效率就太低了,会造成伊甸园区很快爆满或者大规模碎片化,而新产生对象在放进去时效率就会大大降低。
所以在 JVM 设计中,要求年轻代的算法效率是特别高、特别快的。而 Copying(拷贝)算法的效率是最高的,但是浪费了年轻代中至少一半的内存空间。
那我们既要利用好 Copying(拷贝)算法效率高的优势,又要尽量避免内存浪费的问题,怎么解决?
Copying(拷贝)算法在年轻代中的具体应用
第一次垃圾回收:首先将 10% 的幸存对象拷贝到第一个 survivor 中,即 s0 中,然后将整个伊甸区进行清除,这时所有有用对象都存放在 s0 中。
第二次垃圾回收:将伊甸区中有用的对象拷贝到另一个 survivor 中,即 s1 中,再将之前 s0 中的有用对象拷贝到 s1 中,对伊甸园区与第一个 s0 进行垃圾回收。这时所有有用的对象存放在 s1 中。
第三次垃圾回收:再次利用 s0,将之前存活的对象与伊甸区中产生的新对象存放在 s0 中,对伊甸园区与 s1 进行二垃圾回收。
第n次垃圾回收:如此循环往复利用新生代中的伊甸园区与 survivor 区即可。
好了以上就是关于GC如何判定对象是否为可回收的垃圾对象的方法,又3000字了,如果觉得对您有所帮助,欢迎点赞收藏转发,同时也欢迎关注我的微信公众号【Seven的代码实验室】获取更多最新文章动态,不定期有项目福利分享。