精华内容
下载资源
问答
  • Java 对象生命周期和类生命周期

    万次阅读 2015-04-21 16:57:30
    Java 对象生命周期 在JVM运行空间中,对象的整个生命周期大致可以分为7阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected...

    Java 对象生命周期


    在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结阶段(Finalized)与释放阶段(Free)。上面的这7个阶段,构成了 JVM中对象的完整的生命周期。下面分别介绍对象在处于这7个阶段时的不同情形。 

    创建阶段

    在对象创建阶段,系统要通过下面的步骤,完成对象的创建过程:

    (1)为对象分配存储空间。

    (2)开始构造对象。

    (3)递归调用其超类的构造方法。

    (4)进行对象实例初始化与变量初始化。

    (5)执行构造方法体。

    上面的5个步骤中的第3步就是指递归地调用该类所扩展的所有父类的构造方法,一个Java类(除Object类外)至少有一个父类(Object),这个规则既是强制的,也是隐式的。你可能已经注意到在创建一个Java类的时候,并没有显式地声明扩展(extends)一个Object父类。实际上,在 Java程序设计中,任何一个Java类都直接或间接的是Object类的子类。例如下面的代码:

    public class A { 
         … 
    } 
    这个声明等同于下面的声明: 
    public class A extends java.lang.Object { 
         … 
    } 

    上面讲解了对象处于创建阶段时,系统所做的一些处理工作,其中有些过程与应用的性能密切相关,因此在创建对象时,我们应该遵循一些基本的规则,以提高应用的性能。

    下面是在创建对象时的几个关键应用规则:

    (1)避免在循环体中创建对象,即使该对象占用内存空间不大。

    (2)尽量及时使对象符合垃圾回收标准。

    (3)不要采用过深的继承层次。

    (4)访问本地变量优于访问类中的变量。

    关于规则(1)避免在循环体中创建对象,即使该对象占用内存空间不大,需要提示一下,这种情况在我们的实际应用中经常遇到,而且我们很容易犯类似的错误,例如下面的代码:

    … … 
    for (int i = 0; i < 10000; ++i) { 
        Object obj = new Object(); 
        System.out.println("obj= "+ obj); 
    } 
    … … 

    上面代码的书写方式相信对你来说不会陌生,也许在以前的应用开发中你也这样做过,尤其是在枚举一个Vector对象中的对象元素的操作中经常会这样书写,但这却违反了上述规则(1),因为这样会浪费较大的内存空间,正确的方法如下所示:

    … … 
    Object obj = null; 
    for (int i = 0; i < 10000; ++i) { 
        obj = new Object(); 
        System.out.println("obj= "+ obj); 
    } 
    … … 

    采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象应用,浪费大量的内存空间,而且增大了系统做垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。

    另外,不要对一个对象进行多次初始化,这同样会带来较大的内存开销,降低系统性能,如:

    public class A { 
        private Hashtable table = new Hashtable (); 
        public A() { 
            // 将Hashtable对象table初始化了两次 
            table = new Hashtable(); 
        } 
    } 

    正确的方式为:

    public class B { 
         private Hashtable table = new Hashtable (); 
         public B() { 
         } 
    } 

    不要小看这个差别,它却使应用软件的性能相差甚远,如图2-5所示。

     

     

    图2-5  初始化对象多次所带来的性能差别

    看来在程序设计中也应该遵从“勿以恶小而为之”的古训,否则我们开发出来的应用也是低效的应用,有时应用软件中的一个极小的失误,就会大幅度地降低整个系统的性能。因此,我们在日常的应用开发中,应该认真对待每一行代码,采用最优化的编写方式,不要忽视细节,不要忽视潜在的问题。

    应用阶段

    当对象的创建阶段结束之后,该对象通常就会进入对象的应用阶段。这个阶段是对象得以表现自身能力的阶段。也就是说对象的应用阶段是对象整个生命周期中证明自身“存在价值”的时期。在对象的应用阶段,对象具备下列特征:

    ◆系统至少维护着对象的一个强引用(Strong Reference);

    ◆所有对该对象的引用全部是强引用(除非我们显式地使用了:软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference))。

    上面提到了几种不同的引用类型。可能一些读者对这几种引用的概念还不是很清楚,下面分别对之加以介绍。在讲解这几种不同类型的引用之前,我们必须先了解一下Java中对象引用的结构层次。

    Java对象引用的结构层次示意如图2-6所示。

     

    图2-6  对象引用的结构层次示意

    由图2-6我们不难看出,上面所提到的几种引用的层次关系,其中强引用处于顶端,而虚引用则处于底端。下面分别予以介绍。

    1.强引用

    强引用(Strong Reference)是指JVM内存管理器从根引用集合(Root Set)出发遍寻堆中所有到达对象的路径。当到达某对象的任意路径都不含有引用对象时,对这个对象的引用就被称为强引用。

    2.软引用

    软引用(Soft Reference)的主要特点是具有较强的引用功能。只有当内存不够的时候,才回收这类内存,因此在内存足够的时候,它们通常不被回收。另外,这些引用对象还能保证在Java抛出OutOfMemory 异常之前,被设置为null。它可以用于实现一些常用资源的缓存,实现Cache的功能,保证最大限度的使用内存而不引起OutOfMemory。再者,软可到达对象的所有软引用都要保证在虚拟机抛出OutOfMemoryError之前已经被清除。否则,清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束。然而,虚拟机实现不鼓励清除最近访问或使用过的软引用。下面是软引用的实现代码:

    … … 
    import java.lang.ref.SoftReference; 
    … 
    A a = new A(); 
    … 
    // 使用 a 
    … 
    // 使用完了a,将它设置为soft 引用类型,并且释放强引用; 
    SoftReference sr = new SoftReference(a); 
    a = null; 
    … 
    // 下次使用时 
    if (sr!=null) { 
         a = sr.get(); 
    } 
    else{ 
         // GC由于内存资源不足,可能系统已回收了a的软引用, 
         // 因此需要重新装载。 
         a = new A(); 
         sr=new SoftReference(a); 
    } 
    … … 

    软引用技术的引进,使Java应用可以更好地管理内存,稳定系统,防止系统内存溢出,避免系统崩溃(crash)。因此在处理一些占用内存较大而且声明周期较长,但使用并不频繁的对象时应尽量应用该技术。正像上面的代码一样,我们可以在对象被回收之后重新创建(这里是指那些没有保留运行过程中状态的对象),提高应用对内存的使用效率,提高系统稳定性。但事物总是带有两面性的,有利亦有弊。在某些时候对软引用的使用会降低应用的运行效率与性能,例如:应用软引用的对象的初始化过程较为耗时,或者对象的状态在程序的运行过程中发生了变化,都会给重新创建对象与初始化对象带来不同程度的麻烦,有些时候我们要权衡利弊择时应用。

    3.弱引用

    弱引用(Weak Reference)对象与Soft引用对象的最大不同就在于:GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象, GC总是进行回收。因此Weak引用对象会更容易、更快被GC回收。虽然,GC在运行时一定回收Weak引用对象,但是复杂关系的Weak对象群常常需要好几次GC的运行才能完成。Weak引用对象常常用于Map数据结构中,引用占用内存空间较大的对象,一旦该对象的强引用为null时,对这个对象引用就不存在了,GC能够快速地回收该对象空间。与软引用类似我们也可以给出相应的应用代码:

    … … 
    import java.lang.ref.WeakReference; 
    … 
    A a = new A(); 
    … 
    // 使用 a 
    … 
    // 使用完了a,将它设置为weak 引用类型,并且释放强引用; 
    WeakReference wr = new WeakReference (a); 
    a = null; 
    … 
    // 下次使用时 
    if (wr!=null) { 
        a = wr.get(); 
    } 
    else{ 
        a = new A(); 
        wr = new WeakReference (a); 
    } 
    … … 

    弱引用技术主要适用于实现无法防止其键(或值)被回收的规范化映射。另外,弱引用分为“短弱引用(Short Week Reference)”和“长弱引用(Long Week Reference)”,其区别是长弱引用在对象的Finalize方法被GC调用后依然追踪对象。基于安全考虑,不推荐使用长弱引用。因此建议使用下面的方式创建对象的弱引用。

    … … 
    WeakReference wr = new WeakReference(obj); 
    或 
    WeakReference wr = new WeakReference(obj, false); 
    … … 

    4.虚引用

    虚引用(Phantom Reference)的用途较少,主要用于辅助finalize函数的使用。Phantom对象指一些执行完了finalize函数,并且为不可达对象,但是还没有被GC回收的对象。这种对象可以辅助finalize进行一些后期的回收工作,我们通过覆盖Reference的clear()方法,增强资源回收机制的灵活性。虚引用主要适用于以某种比 java 终结机制更灵活的方式调度 pre-mortem 清除操作。

    &注意  在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

    不可视阶段

    在一个对象经历了应用阶段之后,那么该对象便处于不可视阶段,说明我们在其他区域的代码中已经不可以再引用它,其强引用已经消失,例如,本地变量超出了其可视范围,如下所示。

    … … 
    public void process () { 
        try { 
             Object obj = new Object(); 
             obj.doSomething(); 
         } catch (Exception e) { 
         e.printStackTrace(); 
         } 
         while (isLoop) { // ... loops forever 
          // 这个区域对于obj对象来说已经是不可视的了 
             // 因此下面的代码在编译时会引发错误 
             obj.doSomething();  
         } 
    } 
    … … 

    如果一个对象已使用完,而且在其可视区域不再使用,此时应该主动将其设置为空(null)。可以在上面的代码行obj.doSomething();下添加代码行obj = null;,这样一行代码强制将obj对象置为空值。这样做的意义是,可以帮助JVM及时地发现这个垃圾对象,并且可以及时地回收该对象所占用的系统资源。

    不可到达阶段

    处于不可到达阶段的对象,在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量,所有已装载的类的静态变量或者对本地代码接口(JNI)的引用。这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收。其实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的,但是用户程序不可到达的内存块。

    可收集阶段、终结阶段与释放阶段

    对象生命周期的最后一个阶段是可收集阶段、终结阶段与释放阶段。当对象处于这个阶段的时候,可能处于下面三种情况:

    (1)垃圾回收器发现该对象已经不可到达。

    (2)finalize方法已经被执行。

    (3)对象空间已被重用。

    当对象处于上面的三种情况时,该对象就处于可收集阶段、终结阶段与释放阶段了。虚拟机就可以直接将该对象回收了。





    Java 类生命周期


            最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大多只是告诉你“怎样做”,但至于“为什么这样做”却不多说,所以造成大家在基础和原理方面的知识比较匮乏,所以笔者今天就斗胆来讲一下这个问题,权当抛砖引玉,希望对在这个问题上有疑惑的朋友有所帮助,文中有说的不对的地方,也希望各路高手前来指正。

            首先来了解一下jvm(java虚拟机)中的几个比较重要的内存区域,这几个区域在java类的生命周期中扮演着比较重要的角色:

    • 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
    • 常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
    • 堆区:用于存放类的对象实例。
    • 栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。

            除了以上四个内存区域之外,jvm中的运行时内存区域还包括本地方法栈程序计数器,这两个区域与java类的生命周期关系不是很大,在这里就不说了,感兴趣的朋友可以自己百度一下。



    类的生命周期

            当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。

            一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,如图所示:

     

     

    下面我们就依次来说一说这五个阶段。

     

    加载

           在java中,我们经常会接触到一个词——类加载,它和这里的加载并不是一回事,通常我们说类加载指的是类的生命周期中加载、连接、初始化三个阶段。在加载阶段,java虚拟机会做什么工作呢?其实很简单,就是找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。

           类的加载方式比较灵活,我们最常用的加载方式有两种,一种是根据类的全路径名找到相应的class文件,然后从class文件中读取文件内容;另一种是从jar文件中读取。另外,还有下面几种方式也比较常用:

    • 从网络中获取:比如10年前十分流行的Applet。
    • 根据一定的规则实时生成,比如设计模式中的动态代理模式,就是根据相应的类自动生成它的代理类。
    • 从非class文件中获取,其实这与直接从class文件中获取的方式本质上是一样的,这些非class文件在jvm中运行之前会被转换为可被jvm所识别的字节码文件。

           对于加载的时机,各个虚拟机的做法并不一样,但是有一个原则,就是当jvm“预期”到一个类将要被使用时,就会在使用它之前对这个类进行加载。比如说,在一段代码中出现了一个类的名字,jvm在执行这段代码之前并不能确定这个类是否会被使用到,于是,有些jvm会在执行前就加载这个类,而有些则在真正需要用的时候才会去加载它,这取决于具体的jvm实现。我们常用的hotspot虚拟机是采用的后者,就是说当真正用到一个类的时候才对它进行加载。

           加载阶段是类的生命周期中的第一个阶段,加载阶段之后,是连接阶段。有一点需要注意,就是有时连接阶段并不会等加载阶段完全完成之后才开始,而是交叉进行,可能一个类只加载了一部分之后,连接阶段就已经开始了。但是这两个阶段总的开始时间和完成时间总是固定的:加载阶段总是在连接阶段之前开始,连接阶段总是在加载阶段完成之后完成。

     

    连接

           连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。

    1. 验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。
    2. 准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:
      • 基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
      • 引用类型的默认值为null。
      • 常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。
    3.  解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。那么什么是符号引用,什么又是直接引用呢?我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如安徽省黄山市余暇村18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而安徽省黄山市余暇村18号张三就是直接引用。在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。

            连接阶段完成之后会根据使用的情况(直接引用还是被动引用)来选择是否对类进行初始化。

     

    初始化

           如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:

    • 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
    • 通过反射方式执行以上三种行为。
    • 初始化子类的时候,会触发父类的初始化。
    • 作为程序入口直接运行时(也就是直接调用main方法)。

            除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。请看主动引用的示例代码:

    [java] view plaincopy
    1. import java.lang.reflect.Field;  
    2. import java.lang.reflect.Method;  
    3.   
    4. class InitClass{  
    5.     static {  
    6.         System.out.println("初始化InitClass");  
    7.     }  
    8.     public static String a = null;  
    9.     public static void method(){}  
    10. }  
    11.   
    12. class SubInitClass extends InitClass{}  
    13.   
    14. public class Test1 {  
    15.   
    16.     /** 
    17.      * 主动引用引起类的初始化的第四种情况就是运行Test1的main方法时 
    18.      * 导致Test1初始化,这一点很好理解,就不特别演示了。 
    19.      * 本代码演示了前三种情况,以下代码都会引起InitClass的初始化, 
    20.      * 但由于初始化只会进行一次,运行时请将注解去掉,依次运行查看结果。 
    21.      * @param args 
    22.      * @throws Exception 
    23.      */  
    24.     public static void main(String[] args) throws Exception{  
    25.     //  主动引用引起类的初始化一: new对象、读取或设置类的静态变量、调用类的静态方法。  
    26.     //  new InitClass();  
    27.     //  InitClass.a = "";  
    28.     //  String a = InitClass.a;  
    29.     //  InitClass.method();  
    30.           
    31.     //  主动引用引起类的初始化二:通过反射实例化对象、读取或设置类的静态变量、调用类的静态方法。  
    32.     //  Class cls = InitClass.class;  
    33.     //  cls.newInstance();  
    34.           
    35.     //  Field f = cls.getDeclaredField("a");  
    36.     //  f.get(null);  
    37.     //  f.set(null, "s");  
    38.       
    39.     //  Method md = cls.getDeclaredMethod("method");  
    40.     //  md.invoke(null, null);  
    41.               
    42.     //  主动引用引起类的初始化三:实例化子类,引起父类初始化。  
    43.     //  new SubInitClass();  
    44.   
    45.     }  
    46. }  

            上面的程序演示了主动引用触发类的初始化的四种情况。

     

            类的初始化过程是这样的:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首先按照顺序运行父类中的变量赋值语句和静态语句。先看一个例子,首先建两个类用来显示赋值操作:

    [java] view plaincopy
    1. public class Field1{  
    2.     public Field1(){  
    3.         System.out.println("Field1构造方法");  
    4.     }  
    5. }  
    6. public class Field2{  
    7.     public Field2(){  
    8.         System.out.println("Field2构造方法");  
    9.     }  
    10. }  

    下面是演示初始化顺序的代码:

    [java] view plaincopy
    1. class InitClass2{  
    2.     static{  
    3.         System.out.println("运行父类静态代码");  
    4.     }  
    5.     public static Field1 f1 = new Field1();  
    6.     public static Field1 f2;   
    7. }  
    8.   
    9. class SubInitClass2 extends InitClass2{  
    10.     static{  
    11.         System.out.println("运行子类静态代码");  
    12.     }  
    13.     public static Field2 f2 = new Field2();  
    14. }  
    15.   
    16. public class Test2 {  
    17.     public static void main(String[] args) throws ClassNotFoundException{  
    18.         new SubInitClass2();  
    19.     }  
    20. }  

            上面的代码中,初始化的顺序是:第03行,第05行,第11行,第13行。第04行是声明操作,没有赋值,所以不会被运行。而下面的代码:

    [java] view plaincopy
    1. class InitClass2{  
    2.     public static Field1 f1 = new Field1();  
    3.     public static Field1 f2;  
    4.     static{  
    5.         System.out.println("运行父类静态代码");  
    6.     }  
    7. }  
    8.   
    9. class SubInitClass2 extends InitClass2{  
    10.     public static Field2 f2 = new Field2();  
    11.     static{  
    12.         System.out.println("运行子类静态代码");  
    13.     }  
    14. }  
    15.   
    16. public class Test2 {  
    17.     public static void main(String[] args) throws ClassNotFoundException{  
    18.         new SubInitClass2();  
    19.     }  
    20. }  

            初始化顺序为:第02行、第05行、第10行、第12行,各位可以运行程序查看结果。

           在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。

     

    使用

           类的使用包括主动引用和被动引用,主动引用在初始化的章节中已经说过了,下面我们主要来说一下被动引用:

    • 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
    • 定义类数组,不会引起类的初始化。
    • 引用类的常量,不会引起类的初始化。

    被动引用的示例代码:

    [java] view plaincopy
    1. class InitClass{  
    2.     static {  
    3.         System.out.println("初始化InitClass");  
    4.     }  
    5.     public static String a = null;  
    6.     public final static String b = "b";  
    7.     public static void method(){}  
    8. }  
    9.   
    10. class SubInitClass extends InitClass{  
    11.     static {  
    12.         System.out.println("初始化SubInitClass");  
    13.     }  
    14. }  
    15.   
    16. public class Test4 {  
    17.   
    18.     public static void main(String[] args) throws Exception{  
    19.     //  String a = SubInitClass.a;// 引用父类的静态字段,只会引起父类初始化,而不会引起子类的初始化  
    20.     //  String b = InitClass.b;// 使用类的常量不会引起类的初始化  
    21.         SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引起类的初始化  
    22.     }  
    23. }  


            最后总结一下使用阶段:使用阶段包括主动引用和被动引用,主动饮用会引起类的初始化,而被动引用不会引起类的初始化。

            当使用阶段完成之后,java类就进入了卸载阶段。

     

    卸载

           关于类的卸载,笔者在单例模式讨论篇:单例模式与垃圾回收一文中有过描述,在类使用完之后,如果满足下面的情况,类就会被卸载:

    • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
    • 加载该类的ClassLoader已经被回收。
    • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

            如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。

     

    总结

            做java的朋友对于对象的生命周期可能都比较熟悉,对象基本上都是在jvm的堆区中创建,在创建对象之前,会触发类加载(加载、连接、初始化),当类初始化完成后,根据类信息在堆区中实例化类对象,初始化非静态变量、非静态代码以及默认构造方法,当对象使用完之后会在合适的时候被jvm垃圾收集器回收。读完本文后我们知道,对象的生命周期只是类的生命周期中使用阶段的主动引用的一种情况(即实例化类对象)。而类的整个生命周期则要比对象的生命周期长的多。


    展开全文
  • 在实际的软件开发过程中,对象生命周期管理一直是软件开发过程中的一个重点和难点,在C++标准库中也逐渐的出现了一系列的智能指针(标准库/boost库中都涉及到),但是这种智能指针的设计,只是对Object进行封装,...

    在实际的软件开发过程中,对象的生命周期管理一直是软件开发过程中的一个重点和难点,在C++标准库中也逐渐的出现了一系列的智能指针(标准库/boost库中都涉及到),但是这种智能指针的设计,只是对Object进行封装,接管了相关的生命周期,但是有时候在具体的业务需求中,需要对象自己管理自己的生命周期,那么在对象中需要封装相关的引用计数,当引用计数为0时,需要删除对象本身,为了满足该业务需求,设计该基础工具类,方便后期使用,具体代码如下:

    #ifndef  INTRUSIVEPTR_HPP_
    #define  INTRUSIVEPTR_HPP_
    
    #include <inttypes.h>
    #include <boost/thread.hpp>
    #include <boost/noncopyable.hpp>
    
    namespace ts
    {
        /**
         *@brief 侵入式的引用计数管理对象生命周期[使用者需要继承该类,只能通过new对象来进行初始化资源]
         *侵入式:需要了解基础结构的具体实现,通过继承等方式使用
         *非侵入式:不需要了解基础结构的具体实现,通过配置的方式使用
         */
        template <typename T>
        class IntrusivePtr : boost::noncopyable
        { 
        //! Intrusive reference count
        //! 可以考虑把计数封装为一个独立的处理对象
        size_t _count;
    
        //! Synchornization object
        typedef boost::mutex::scoped_lock lockType;
        boost::mutex m_mutex;
    
        public:
    
        /**
        * Create an IntrusivePtr with a count. intrusive
        */
        IntrusivePtr(size_t InitialCount=1) : _count(InitialCount) {
        }
    
        /**
        * Destroy an IntrusivePtr
        */
        virtual ~IntrusivePtr() {}
    
        /**
        * Add a reference to this object, it will take one more
        * call to delReference() for it to be deleted.
        */
        void addReference() 
        {
            lockType l(m_mutex);
            ++_count;
        }
    
        /**
        * Remove a reference from this object, if the reference count
        * drops to 0 as a result, the object deletes itself.
        */
        void delReference() {
    
        bool result = false;
    
        {
          lockType l(m_mutex);
          result = (--_count == 0);
        }
    
    
        if(result)
          delete this;
        }
        };
    };
    
    #endif // INTRUSIVEPTR_HPP_


    如代码中描述,具体的实现对象需要继承该基础类,使用者通过主动调用”addReference()“和”delReference()“来对引用计数进行管理,当引用计数为0时,对象被释放

    展开全文
  • 堆栈 java对象生命周期

    千次阅读 2018-08-21 10:36:25
    java运行时内存区域的各个部分中,程序计数器、虚拟机栈、本地方法栈这三个区域的生命周期和线程相关,栈中的栈帧随着方法的进入和退出执行着进栈和出栈,每一个栈帧分配多少内存基本上在类的结构确定下来的时候就...

    1、垃圾回收器需要关注的内容

    java运行时内存区域的各个部分中,程序计数器、虚拟机栈、本地方法栈这三个区域的生命周期和线程相关,栈中的栈帧随着方法的进入和退出执行着进栈和出栈,每一个栈帧分配多少内存基本上在类的结构确定下来的时候就已经知道了,因此这3各区域的内存分配和回收都是确定的,因此不必过多考虑这三个区域的内存回收问题,因为方法结束后或者线程结束时,内存就会被自然回收。

    java堆方法区则不一样(比如一个接口会有多个实现类,每个实现类需要的内存会不一样,一个接口方法的多个实现也是不一样的)我们只能在程序运行期间才能够知道会创建哪些对象,这部分的内存分配和回收都是动态的,垃圾回收器所关注的便是这部分内存。

     

    2、对象的“存活”和“死亡”

    堆里存放着java中几乎所有的对象实例,在垃圾回收器对堆进行回收前,判断对象的可用(存活)和不可用(死亡)是一件首要事情。判断对象的死亡有两种算法:1)引用计数算法;2)可达性分析算法;

    java虚拟机中使用的是可达性分析算法,并没有使用引用计数算法,因为引用计数算法很难解决对象之间相互循环引用的问题。

     

    1)引用计数算法

    给对象加一个引用计数器,每当有一个地方引用该对象,引用计数器就增加1,当引用失效的时候就会减1,当引用计数器值为0的时候该对象可能就会处于不可用状态。OC上使用的便是引用计数算法。

     

    2)可达性分析算法

    主流商用语言java,C#的实现中都是通过可达性分析算法来判断对象是否存活的。该算法的基本思想就是通过一些列的称为GC Roots的对象作为起来,从这些节点向下搜索,搜索走过的路径称为引用链,,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象是不可用的。

    java中可作为GC Roots的对象包括以下几种:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的变量
    • 方法去中常量引用的对象
    • 本地方法栈中JNI(native方法)引用的对象

    3、引用

    强引用:一般的new关键之创建的对象的引用

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

    弱引用(WeakReference):描述非必须的对象,之后能存活到下一次GC之前,只要当前垃圾回收器工作,无卵当前内存是否足够,都会被回收。

    虚引用(PhantomReference):一个对象是否有虚引用存在不会影响其生存时间,无法通过虚引用来达到一个对象实例,对象设置虚引用关联的唯一目的是在这个对象被垃圾回收器回收的时候收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。

     

    参考《深入理解Java虚拟机》

    展开全文
  • Java虚拟机之对象生命周期

    千次阅读 2013-01-26 15:51:55
    要在JVM的堆中创建某类型的实例化对象,必须先完成该类型的加载、连接和初始...下面主要介绍对象生命周期中的这三阶段。 1. 类实例化  Java程序中,类可以被显式或者隐式地实例化。  显式实例化的4途径:

           要在JVM的堆中创建某类型的实例化对象,必须先完成该类型的加载、连接和初始化。因此,对象的生命周期只是类生命周期中的使用阶段,而类的生命周期要比对象的生命周期要长。对象的生命周期开始于类的实例化,中间经历使用阶段,结束于对象的垃圾收集。下面主要介绍对象生命周期中的这三个阶段。

    1. 类实例化

        Java程序中,类可以被显式或者隐式地实例化。

        显式实例化的4种途径:

    • 使用new操作符
    • 使用反射,调用java.lang.Class或java.lang.reflect.Constructor的newInstance()方法
    • 使用现有对象的clone()方法
    • 使用反序列化手段,调用java.io.ObjectInputStream的readObject()方法

        隐式实例化的方法:

    • Java的每个命令行参数都会创建相应的String对象,并把它们组织成String数组作为一个参数传递给程序入口main(String args[])方法
    • JVM装载的每个类型,都会隐式地创建描述该类型的Class实例
    • 字符串常量对应一个String对象
    • 字符串操作符“+”的运算结果如果不是编译时常量,会创建一个String对象

        无论采用哪种方式实例化对象,JVM创建对象的步骤如下:

        1) 在堆中为对象分配内存

        2) 实例变量初始化为默认值

        3) 对象初始化,给实例变量赋正确的初始值

     

           Java编译器在编译每个类时都会为该类至少生成一个实例初始化方法——即“<init>()”方法。此方法与源代码中的每个构造方法相对应。如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的“ <init>()”方法。

          <init>()方法内可能包括的代码内容大概为:调用另一个<init>()方法;对实例变量初始化;与其对应的构造方法内的代码。
          如果构造方法是明确地从调用同一个类中的另一个构造方法(一个this()调用)开始,那它对应的<init>()方法体内包括的内容为:一个同类的<init>()方法的调用;对应用构造方法内的所有字节码。
          如果构造方法不是通过调用自身类的其它构造方法开始,并且该对象不是Object对象,那<init>()方法内则包括的内容为:一个对父类<init>()方法的调用;对实例变量初始化方法的字节码;最后是对应构造子的方法体字节码。

          如果构造方法不是通过调用自身类的其它构造方法开始,并且这个类是Object,那么它的<init>()方法则不包括对父类<init>()方法的调用。

          如果构造方法通过明确地调用超类的构造方法(一个super()调用)开始,那它对应的<init>()方法会调用对应的超类的<init>()方法。

          如果构造方法没有明确地从this()或者super()调用开始,对应的<init>()方法默认会调用超类的无参数<init>()方法。

    2. 对象使用

          Java中是通过引用来使用对象。在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。其中,强引用是应用最普遍的引用。

          java.lang.ref.Reference类的三个直接子类:SoftReference、WeakReference和PhantomReference,分别对应着可触及状态的三个比较弱的形式:软引用、弱引用和虚引用。强引用与较弱形式的引用的最基本区别是:强引用禁止引用目标被垃圾收集器回收,而较弱形式的引用不禁止。创建软引用、弱引用和虚引用,只需要简单地把强引用传递到对应的引用对象的构造方法中。下面介绍一下4种级别的引用的特点。

    • 强引用

         如果一个对象具有强引用,垃圾收集器绝不会回收它。当内存空间不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会考随意回收具有强引用的对象来解决内存不足的问题。

    • 软引用

         如果一个对象具有软引用,当内存空间足够时,垃圾收集器不会回收它;当内存不足时,就会回收这些对象的内存。只要垃圾收集器没有回收它,该对象就可以被程序使用。因此,软引用可用来实现内存敏感的高速缓存。

    • 弱引用

         如果一个对象具有弱引用,当垃圾收集器发现只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾收集器是一个优先级很低的线程,因此不一定会很快发现只具有弱引用的对象。

    • 虚引用

         虚引用不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾收集器回收。虚引用主要用来跟踪对象被垃圾回收的活动。

    3. 垃圾收集

          程序可以显式或者隐式地为对象分配内存,但是不能明确地释放内存。当一个对象不再被程序所引用时,JVM会通过垃圾收集器回收该对象的内存空间。

          如果类声明一个返回值为void的finalize()方法,垃圾收集器会在释放这个类的实例对象所占内存之前执行这个方法一次。由于finalize()方法是一个普通的Java方法,它可以直接被程序所调用。这样的直接调用不会影响垃圾收集器的自动调用过程。垃圾收集器最多只会调用一次对象的finalize()方法。如果finalize()方法执行后,对象重新被引用,随后再次变得不被引用,垃圾收集器不会第二次调用finalize()方法。

         finalize()方法具有如下特点:

    • 垃圾收集器何时执行该方法是不确定的
    • finalize()方法可能使对象复活
    • 垃圾收集器自动调用finalize()方法抛出的任何异常都将被忽略

         在垃圾收集器看来,堆中的对象都处于三种状态之一:

    • 可触及:可以从根节点开始通过追踪“触及”到该对象;
    • 可复活:从根节点开始的追踪图中不可触及,但是可能在垃圾收集器执行某些终结方法时触及;
    • 不可触及:从根节点开始的追踪图中不可触及,同时不可能通过任何终结方法复活。垃圾收集器只回收该状态下的对象。

        垃圾收集器的具体回收策略,可以看之前的博客:

    Java虚拟机之垃圾回收 

    展开全文
  • 对象生命周期

    千次阅读 2018-03-23 14:23:43
    在Java中,对象生命周期包括以下几阶段:1. 创建阶段(Created)2. 应用阶段(In Use)3. 不可见阶段(Invisible)4. 不可达阶段(Unreachable)5. 收集阶段(Collected)6. 终结阶段(Finalized)7. 对象空间重分配...
  • 下面回忆下Activity的生命周期: activity常用生命周期主要是: onCreate() —&gt; onStart() –&gt;onResume() –&gt; onPause() –&gt; onStop() –&gt; onDestroy() 如所示: ...
  • 生命周期: 1.创建阶段(Created) 2.应用阶段(In Use):一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段 3.不可见阶段(Invisible):例如在for里面定义的i ,for循环break了就不...
  • 关于Servlet对象生命周期

    千次阅读 2017-02-15 20:18:08
    生命周期表示一个java对象从最初被创建到最终被销毁,经历的所有过程。 2、Servlet对象生命周期是谁来管理的?程序员可以干涉吗? Servlet对象生命周期,javaweb程序员是无权干涉的,包括该Servlet对象的相关...
  • 对象状态及生命周期 1、Transient:瞬时状态,由new命令开辟内存空间的Java对象。 2、Persistent:持久化状态,通过Session的save()或saveOrUpdate()、persist()方法将瞬时对象与数据库相关联,并将数据对应...
  • java类和对象生命周期

    万次阅读 多人点赞 2018-07-09 06:55:00
    Java 对象生命周期在JVM运行空间中,对象的整个生命周期大致可以分为7阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结...
  • activity的生命周期描述

    千次阅读 2014-09-23 13:47:30
    下表详细描述了这些方法,并在activity的整个生命周期中定位了它们。 方法 描述 是否可被杀死(Killable?) 下一个 onCreate() 在activity第一次被创建的时候调用...
  • Mybatis核心对象生命周期

    千次阅读 2017-06-02 18:37:22
    Mybatis核心对象生命周期SqlSessionFactory每 MyBatis 的 应 用 程 序 都 以 SqlSessionFactory 对 象 的 实 例 为 核 心 。 SqlSessionFactory 对 象 的 实 例 可 以 通 过 SqlSessionFactoryBuilder...
  • 描述下Activity的生命周期

    千次阅读 2011-05-27 15:55:00
    Activity 生命周期
  • 下面是某个视图控制对象生命周期图
  • 描述Servlet的生命周期

    千次阅读 2019-07-04 14:58:43
    所以说,Servlet是在接收到第一个处理请求时被创建的,也可以通过配置load-on-startup的值为1设置Servlet随服务器启动就创建对象。(特殊业务需求才使用程序启动自动创建Servlet对象,否则会增加服务器的启...
  • Spring IOC -bean对象生命周期详解

    万次阅读 2016-07-16 15:29:55
    生命周期执行的过程如下: 1)spring对bean进行实例化,默认bean是单例 2)spring对bean进行依赖注入 3)如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法 4)如果bean实现了BeanFactoryAware...
  • JAVA对象实例化过程因为此篇篇幅较长,单独抽出,详情请看 Class初始化理解 此篇中详细介绍了JAVA对象的实例化过程JAVA对象内存分配过程JVM 这里默认使用HotSpot虚拟机。简单回顾一下JVM内存结构,JVM中主要将使用...
  • java对象生命周期

    千次阅读 2013-01-05 22:33:35
    要理解java对象生命周期,我们需要要明白两问题,  1、java是怎么分配内存的 ,2、java是怎么回收内存的。 喜欢java的人,往往因为它的内存自动管理机制,不喜欢java的人,往往也是因为它的内存自动管理。...
  • 描述下Activity的生命周期

    万次阅读 2011-10-02 10:15:03
    描述下Activity的生命周期 1. 创建 onCreate - 启动onStart – 开始 onResume – 暂停 onPause – 结束 onStop – 销毁onDestroy 2. 答:你自己写的Activity会按需要重载这些方法,onCr
  • C# 对象生命周期

    2014-10-11 15:33:55
    C# 中 类存在于栈中,而对象存在于托管堆。
  • JSP生命周期和内置对象

    千次阅读 2015-12-23 14:15:55
    理解JSP底层功能的关键就是去理解它们所遵守的生命周期。...以下是JSP生命周期中所走过的几阶段: 编译阶段: servlet容器编译servlet源文件,生成servlet类 初始化阶段: 加载与JSP对应的servl
  • 转载请注明出处: Java对象生命周期 与 垃圾回收 - ITeye博客 - 老Man 创建对象的方式 用new语句创建对象。使用反射,调用java.lang.Class或java.lang.reflect.Constructor的newInstance()实例方法。调用对象...
  • servlet的对象创建和生命周期

    千次阅读 2015-07-04 17:54:32
    servlet的对象创建和生命周期 1、谁创建servlet? A:由读取web.xml文件的服务器软件来进行创建 2、何时创建servlet对象? A:1)web程序,服务器端只有一个,但是访问的客户端数量没法控制 2)客户端访问...
  • Spring中bean的作用域与生命周期

    万次阅读 多人点赞 2017-06-17 22:29:18
    在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。而bean的定义...
  • servlet生命周期的简单描述

    千次阅读 2013-11-02 20:38:35
    一个servlet的生命周期由不熟servlet的容器来控制,当一个请求映射到一个servlet的时候,该容器执行下列步骤。  1.如果一个Servlet的实例不存在,则Web容器  加载servlet类,创建一个servlet实例,调用init()...
  • 在Hibernate中,持久化对象在操作过程中可以分为三时期,这三时期是与...持久化对象的三种生命周期分别是瞬时态(Transient)、持久态(Persistent)和脱管态(Detached)。 1. 瞬时态 瞬时态的对象时刚刚用n
  • vue生命周期和react生命周期对比。

    千次阅读 2019-07-06 18:04:03
    指的是组件从初始化开始到结束的过程 或者是生命周期描述组件从开始到结束的过程,每组件都具有生命周期,都对组件通过生命周期给予的钩子函数进行管理。 钩子函数 指的是系统某些状态和参数发生改变的时候,...
  • 本文主要描述一下Activty的生命周期,以及launchMode对生命周期的...Activity生命周期图 下面对上图Activity的生命周期进行一下描述 1,启动Activity,系统会先调用onCreate方法,然后调用onStart方法,然后调用onR

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 201,349
精华内容 80,539
关键字:

哪种图描述一个对象的生命周期