首页 > 学院 > 开发设计 > 正文

虚拟机的那些事儿~

2019-11-08 00:44:40
字体:
来源:转载
供稿:网友

最近在看jvm虚拟机,对垃圾回收这里很是感兴趣。今天有时间,来整理一下关于垃圾回收的一些事儿。

大家都知道 垃圾回收是保证内存不够使用的时候进行一下回收,将一些该被回收的对象(无引用)的对象进行回收,以保证拥有足够的空间。 这只是最粗粒度的意义

下来我就从多个角度来分析垃圾回收。

一.内存方面

当堆或者栈,在申请内存时,发现内存不够了,会进行垃圾回收,来释放足够的空间。如果回收过后内存仍然不够的话,将会报出outOfMemoryError。内存溢出异常。

二.引用方面

在引用上分为4中: 强引用: 强引用是一种永远不会被回收的引用 eg:

String str=new String("abc");

如果不进行操作。那么这个强引用将一直被使用直到jvm关闭停止。

软引用:软引用是一种有用,但是又不是飞却不可的一种。 eg:

Test test =new Test(new user()); //软引用

在这里是作为一个参数存在的。 当一般情况下,软引用的对象不会被回收,但是当内存实在不够,会抛出outofmermoryError之前会回收。可以用作内存敏感的高效缓存

弱引用:弱引用是比软引用关联性更差的一种引用

String str =new String("abc");str=null;

弱引用的对象在下一次回收时一定会被回收

虚引用:是最弱的一种引用 随时都有可能被回收,存在的意义就是在被回收的时候,会得到通知。

三.回收方式

gc的回收方式又分为三种: ● MinorGC 只回收新生代 出于常理, 所有的Minor GC都会触发stop-the-world暂停, 它意味着会暂停应用的所有线程 ● MajorGC只回收老年代 ● fullGC都回收

当新申请内存不足时,会立即进行一次新生代的内存回收MinorGC

那么什么是新生代,什么又是老年代呢: -当对象进行初始化创建的时候,会在堆上划分一部分内存。这时候其为新生代(Eden)。 - 进过一次垃圾回收后,并且其仍然是有引用的时候(存活下来了),会放在存活区(Survivor),并将年龄标记为1。之后每经过一次gc,那么年龄都会加1。一旦超过了MaxTenuringThreshold(默认15,可以修改)的值,就会放入老年代。 - 当survivor中的对象,年龄相同的对象超过survivor对象的一半,这时候会将年龄超过一半的(大于等于)放入老年代。 - 大对象是需要连续空间的对象(比如很长的字符串或者数组),这种对象一般是直接放在老年代的。大对象的存储对于虚拟机老说是一个不怎么好的消息,因为它们的存在,会提前触发垃圾回收。

空间担保: 在经历MinorGC之前,要确定老年代中最大可用的连续空间是否大于新生代所有对象的总空间,如果这个成立,那么就确保minorGC是安全的。如果小于,那么久先查看HandlePRomototionFailure是否允许担保失败,如果允许,那么检查老年代的最大可用连续空间是否足够这次晋升的空间。如果不够,或者HandlePromototionFailure不允许担保失败,那么就进行一次FullGC。 原因: 如果某一次MinorGC后,所有的对象都存活了下来,那么就要全部放入Survivor空间,这时候老年代就要容乃survivor无法容纳的对象。 有可能就会全部进入老年代。

四.判定方式

之前是有使用<计数算法>来判定是否应该回收的,在对象上,放置对象的引用数量,来的话加1,失去引用的话就减一,后来发现,在两个对象来回调用的时候,并不好用,eg:

obj1.instance=obj2;obj2.instance=obj1;obj1=null;obj2=null;

遇到这种情况下,虽然两个对象都已经失效,但是实际引用还是在互相指向,所以计数器没有减一,所以一直不能够gc。

判定该内存是否应该被回收,是通过<可达性分析算法>来判定的 通过一系列被称为GCroots的对象作为起始点,从这些节点开始向下探索,搜索所有走过的路径为引用链,当一个对象到GC Roots没有任何引用链项链时,则证明此对象时不可用的。 这里写图片描述 如图所示,这图出自周志明著的java虚拟机。 可以去充当GCroots的对象有: 虚拟机栈(栈帧中的本地变量表)中的引用的对象(方法中引用的对象、变量) 方法区中类静态属性引用的对象(静态对象) 方法区中常量引用的对象(常量) 本地方法栈中JNI(即Native方法)引用的对象

注意:当内存确定被回收的时候,并不是一定会立刻死的,有一个方法叫做finalize()。 这个方法是对象逃脱死亡的最后一次机会。 什么时候才能执行该方法呢? 当该对象重写了finalize(),且第一次执行该方法时,才会执行。finalize()方法只会被执行一次。 如果这个对象被确定需要执行finalize()方法的时候,会将其放置在一个叫做F-queue的队列中去,并在稍后由虚拟机去自动建立一个低级别的finalizer线程去执行(优先级低,所以执行的时候感觉有点延迟)。不能保证该方法是否能够执行完毕(存在有写死循环的那种情况,会使得队列中的其他对象一直处于等待,导致内存崩溃)。 如果在finalize()方法中将自己和引用链上的一个对象建立联系就可以逃脱回收的命运了。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表