JVM对象引用与内存分配策略

原创 2016年05月31日 20:46:41

我的简书同步发布:JVM对象引用与内存分配策略

前两天对《深入理解虚拟机》一书做了个总结:《JVM理解其实并不难! 》,今天针对对象引用和内存分配做个稍微深入的了解。

关于引用

《JVM理解其实并不难! 》一文中提到,JVM是通过可达性分析来判断对象是否需要被回收,这可以弥补引用计数法的不足。即就算两个对象相互引用,只要这两个对象没有引用链连接GC Roots,这两个对象都会被判定为可回收的对象!注意,这里是指被判定位可回收的对象,并不是说他们就一定会被回收!这相当于“标记”的过程,即标记这个对象为可以回收的对象。

什么意思呢?既然被标记为可回收的对象,难道不就是要对他们回收吗?且继续往下看~

JVM中真正将一个对象判死刑至少需要经历两次标记过程:

第一个过程,就是我们所熟知的可达性分析。
第二个过程,就是判断这个对象是否需要执行finalize()方法。

第一个过程无需再述,我们看看第二个过程,什么叫判断这个对象是否需要执行finalize()方法呢?在学习Java时,我们都知道,如果我们重写Objectfinalize()方法时,在当前这个对象消亡之前会执行finalize()方法。然后我们就将一些资源在finalize()中释放。其实这种做法并不正确,至于为什么不正确,我们来看看finalize()方法在垃圾回收时是怎么触发的。

如果一个对象被判定为有必要执行finalize()方法,那么这个对象会被放入F-Queue队列中,JVM中有一个优先级比较低的线程去执行这个队列中的对象的finalize()方法。这里的执行是指JVM会触发这个方法,但不会承诺会等到它运行结束,这主要是防止某个对象的finalize过于耗时(比如死循环),导致队列中其他的对象无法被执行,最终使得整个内存回收系统崩溃~

那么什么样的对象会被判定为有必要执行finalize()方法呢?首先,这个对象必须是通过可达性分析判定为没有引用链连接到GC Roots;其次,这个对象重写了finalize()方法并且没有执行过finalize()方法。也就是说finalize()只会被执行一次。

既然如此,我们可以写一个对象自救的测试,让一个对象自己拯救自己,我们去看看一个对象是如何“耍流氓”的:


public class Test {
    public static Test self;



    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("嗯,已经执行了finalize()方法了");
        Test.self = this;
    }


    private static void save() throws Exception {

        self = null;
        System.gc();

        //Finalizer线程优先级比较低,我们稍等一小会
        Thread.sleep(500);

        if (self != null) {
            System.out.println("我还活着~你咬我啊~");
        } else {
            System.out.println("啊哦,我挂了~");
        }
    }

    public static void main(String[] args) throws Exception {
        self = new Test();

        save();
        save();

    }
}

运行结果是:

嗯,已经执行了finalize()方法了
我还活着~你咬我啊~
啊哦,我挂了~

我们看到,同样是执行save方法,第一次对象成功拯救了自己,第二次却无法拯救自己,finalize方法也仅仅只被执行了一次而已!

软引用、弱引用、虚引用

我们知道,只有当对象存在引用链连接GC Roots时才确保不会被回收,即对象为强引用。那么有些对象,我们希望在内存充足的情况下不要回收,在内存不足的时候再将其回收掉。如果只有强引用,那这个对象永远都不会被回收。于是引入了软引用弱引用虚引用

软引用:用来描述一些还有用但并非必须的对象。对于软引用对象,在系统将要发生内存溢出之前,会把这些对象列入回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才抛出溢出异常。

怎么理解呢?其实就是说,如果我的内存不足了,JVM有点想骂人(抛异常)了,JVM会先看看哪些对象时软引用对象,先把这些软引用对象给回收掉,再看看内存是不是够用,如果还是不够用,那JVM就真的发飙了(抛异常)。

弱引用:同样,弱引用也是描述非必须的对象,但是它的强度更弱一点,被弱引用关联的对象只能生存到下一次垃圾回收之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉弱引用的对象。

有了软引用的理解基础,我们理解起来就不费劲了。就是说,弱引用对象能生存到下次垃圾回收,它比软引用活的时间要短。爱思考的你肯定会想问:那这跟直接将对象赋null值有什么区别啊,如果一个对象“切断”掉对对象的引用,那个对象也是活到下次垃圾回收啊。可是,有没有想过,假设引用变量A a=new A()。然后你直接a=null,那如果在a之前所引用的对象被回收之前,我还想引用它怎么办?你已经没有办法找到它了~~~~,但是弱引用就不一样,我们可以先判断a引用的对象有没有被回收即if(a!=null),如果没有被回收,我们就还可以利用它啦~

虚引用:它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对这个对象的生死有影响。你无法通过虚引用来取得一个对象实例。为一个对象设置虚引用管理的唯一目的就是能在这个对象呗收集器回收时收到一个系统通知。

我们来讨论一下,什么时候该用软引用,什么时候该用弱引用。首先,软引用比弱引用活的时间长一点,当你不希望某个对象轻易被回收,但是呢由于这个对象比较占用内存,为了防止OOM,你可以将它声明为软引用。那什么时候用弱引用呢?当某个对象,你后面基本上不去用它了,但是又有可能会用它,而频繁的创建这个对象又比较耗资源,可以声明为弱引用。其实我觉得,大部分时间用弱引用就行了,软引用更多是为了在内存溢出之前多回收点内存。当然了,具体该使用软引用还是弱引用,需要根据实际需要决定。

说那么多引用,比较抽象,我们看看如何声明使用这些引用,在心里将它们与我们平时所用的强引用的用法上默默地做个比较:

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class Test {

    SoftReference<RefObject> softRefObject;
    WeakReference<RefObject> weakRefObject;
    RefObject softObject;
    RefObject weakObject;

    public Test() {
        softObject = new RefObject();
        weakObject = new RefObject();

        softRefObject = new SoftReference<Test.RefObject>(softObject);
        weakRefObject = new WeakReference<Test.RefObject>(weakObject);

    }



    public static void main(String args[])   {

        Test test = new Test();

        RefObject softRefObject = test.softRefObject.get();
        RefObject weakRefObject = test.weakRefObject.get();


        if(softRefObject!=null){
            softRefObject.hello();
        }

        if(weakRefObject!=null){
            weakRefObject.hello();
        }

    }

    class RefObject {
        public void hello(){
            System.out.println("hello reference");
        }
    }
}

内存分配策略

内存的分配,主要是在堆里面进行分配(也有可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),堆里面主要是发生在新生代的Eden区中,少数情况下是在老年代中,分配的规则不是固定的,这需要根据当前使用的是哪种垃圾收集器组合还有虚拟机与内存相关的参数设置。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区进行分配。当Eden区没有足够空间进行分配时JVM发生一次Minor GC。什么叫Minor GC呢?Minor GC是指发生在新生代的垃圾收集动作,因为Java对象大多具备朝生夕死的特性,所以Minor GC非常频繁,当然了,其回收速度肯定也是比较快的~,与之对应,还有个Full GC或者称为Major GC,是指老年代中的GC,经常会伴随一次Minor GC,Major GC速度一般会比Minor GC速度慢10倍以上!

大对象直接进入老年代

所谓的大对象,是指占用大量连续内存空间的Java对象。最经典的大对象就是那种很长的字符串和数组。大对象对于虚拟机来说是个坏消息~我们写程序时,尽量要避免出现一群朝生夕死的大对象。经常出现大对象容易导致内存还有不少空间时就得提前触发垃圾收集以获取足够的空间来存放大对象。

长期存活的对象将进入老年代

JVM采用分代收集思想来管理内存,就要去区分哪些是年轻的对象,哪些是老年的对象。我们知道,刚创建的对象肯定是年轻的对象,那么怎么将对象判断为老年呢?

在Eden区出生,并经过一次Minor GC后仍然存活,并且能被To Suvivor容纳,移动到To Suvivor区后,年龄设置为1。以后每经历一次Minor GC就将年龄加1,当它的年龄达到一个阀值(默认15,也可以更改-XX:MaxTenurinigThreshold来设置),就会被晋级到老年代中。

动态对象年龄判定

为了更好地适应不同程序内存情况,JVM并不一定是等到对象年龄达到阀值才将对象晋级到老年代。如果在Survivor空间中的相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年的对象就可以直接进入老年代,无需等到指定的阀值。这句话可能有点绕,不太好理解,我来再解释一下,就是说,假设Survivor的空间大小为max,年龄为y的对象总共有n个,如果y*n>max/2,那么所有年龄大于y的对象全部进入到老年代。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

java类在jvm中经历的几个阶段以及对象中的属性赋值和方法的执行顺序

本文基于个人的一些理解做的整理,如果有什么位置有问题,欢迎留言指教。jvm加载资源的几个阶段  jvm加载一个类需要经过加载、连接、初始、使用和卸载几个阶段。我们介绍下前3个阶段加载加载是jvm加载二...

Jvm堆内存的划分结构和优化,垃圾回收详解(详细解答篇)

jvm的结构 设置 疑问点 垃圾回收原理 方式, 触发条件。 设置等等的相关纤细介绍...

深入理解JVM之JVM内存区域与内存分配

JAVA虚拟机把管理的内存划分为几个不同的数据区。 Java堆-存放new的对象和数组(jvm不定时查看这个对象,如果没有引用指向这个对象就回收) Java堆是被所有线程共...
  • USTC_Zn
  • USTC_Zn
  • 2017年02月07日 17:00
  • 859

JVM 内存分配详解

 JVM 内存分配详解2010-12-13 11:25使用Java程序从数据库中查询大量的数据时出现异常: java.lang.OutOfMemoryError: Java heap space 在J...

JVM 深入笔记(1)内存区域是如何划分的?

JVM 深入笔记(1)内存区域是如何划分的?作者:柳大 · Poechant电邮:zhongchao.ustc#gmail.com (#->@)博客:blog.csdn.net/poechant日期:...
  • Poechant
  • Poechant
  • 2012年02月24日 00:07
  • 19995

JVM配置优化和内存分配情况查看

本文提供为TOMCAT做优化时调整JVM配置参数和方式,以及确认参数是否生效的方式。分别介绍了几种方式,并给出了建议使用的方式。最后环节给出了内存的模型,以辅助理解JVM的内存分配和管理机制,采用文中...

JVM之内存区域分配

JVM的内存模型,对象的分配

JVM内存分配以及存储总结

最近看了一下JVM的内存分配,还是比较复杂的。这里做个总结,首先一个common sense就是操作系统会为每个java进程实例化一个jvm实例。jvm然后再来运行java程序,具体的过程就不多说了,...

Java内存分配全面浅析

本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java。这类文章网上有很多,但大多比较零碎。本文从认知过程角度出发,将带给读者一个系统的介绍。          进入正题前首先...

JVM 内存分配模型概念和java中各种对象的存储

JVM 内存分配模型概念 --在工作中可能用到的机会不多,有个概念的了解 --此文是转载某位读者,应该是在阅读了《深入理解Java虚拟机JVM高级特性与最佳实践》 一书后,总结所得。写的不错,转载哈...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JVM对象引用与内存分配策略
举报原因:
原因补充:

(最多只允许输入30个字)