java异常机制 -- 异常,垃圾回收

        在程序设计中,对异常进行处理是非常关键和重要的一部分。一个程序的异常处理框架的好坏直接影响到整个项目的代码质量以及后期维护成本和难度。

        如果一个项目从头到尾没有考虑过异常处理,当程序出错从哪里寻找出错的根源?但一个项目异常处理设计地过多,又会严重影响到代码质量以及程序的性能。因此,如何高效简洁地设计异常处理是一门艺术

异 常:

        异常:就是不正常。程序在运行时出现的不正常情况。其实就是程序中出现的问题。这个问题按照面向对象思想进行描述,并封装成了对象。在java中用类的形式对不正常情况进行了描述和封装对象。描述不正常的情况的类,就称为异常类。

        因为问题的产生有产生的原因、有问题的名称、有问题的描述等多个属性信息存在。当出现多属性信息最方便的方式就是将这些信息进行封装。异常就是java按照面向对象的思想将问题进行对象封装。这样就方便于操作问题以及处理问题。

        其实异常就是java通过面向对象的思想将问题封装成了对象.用异常类对其进行描述。不同的问题用不同的类进行具体的描述。异常出现的问题有很多种,比如角标越界,空指针等都是。就对这些问题进行分类。

        而且这些问题都有共性内容比如:每一个问题都有名称,同时还有问题描述的信息,问题出现的位置,所以可以不断的向上抽取。形成了异常体系

        问题很多,意味着描述的类也很多,将其共性进行向上抽取,形成了异常体系。最终问题(不正常情况)就分成了两大类。

        Throwable:无论是error,还是异常问题,问题发生就应该可以抛出,让调用者知道并处理。

java.lang.Throwable:

Throwable可抛出的。 

        |--Error:错误,一般情况下,不编写针对性的代码进行处理,通常是jvm发生的,需要对程序进行修正。Java应用程序本身无法恢复的严重错误,应用程序不需要捕获、处理这些严重错误。当程序发生这种严重错误时,通常的做法是通知用户并中止程序的执行。 Error是无法处理的异常,比如OutOfMemory Error ,一般发生这种异常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常

        |--Exception:异常,可以有针对性的处理方式,异常可分为运行时异常(RuntimeException)和检查时异常(CheckedException)两种,

        无论是错误还是异常,它们都有具体的子类体现每一个问题,它们的子类都有一个共性,就是都以父类名才作为子类的后缀名

异常体系的特点

    这个体系中的所有类和对象都具备一个独有的特点;就是可抛性可抛性的体现: 就在于Throwable及其所有的子类都具有可抛性。就是这个体系中的类和对象都可以被throws和throw两个关键字所操作。

        在开发时,如果定义功能时,发现该功能会出现一些问题,应该将问题在定义功能时标示出来,这样调用者就可以在使用这个功能的时候,预先给出处理方式。

如何标示呢?

        通过throws关键字完成,格式:throws 异常类名,异常类名...这样标示后,调用者,在使用该功能时,就必须要处理,否则编译失败。

处理方式有两种:1、捕捉;2、抛出。

对于捕捉:java有针对性的语句块进行处理。

try {
    //需要被检测的代码;
} catch (Exception e) {
    //异常类 变量名, e用于接收try检测到的异常对象。
    //异常处理代码;
    System.out.println("message:" + e.getMessage());
    //获取的是异常的信息。
    System.out.println("toString:" + e.toString());
    //获取的是异常的名字+异常的信息。
    e.printStackTrace();
    //打印异常在堆栈中信息;异常名称+异常信息+异常的位置。
} finally {
   //一定会执行的代码;
}

 try  catch  finally的几种结合方式:

try   catch  finally
try   catch
try   finally

    这种情况,如果出现异常,并不处理,但是资源一定关闭,所以try  finally集合只为关闭资源记住:finally很有用,主要用户关闭资源。无论是否发生异常,资源都必须进行关闭。

   System.exit(0); //退出jvm,只有这种情况finally不执行。

可抛性到底指的是什么呢?怎么体现可抛性呢?

        其实是通过两个关键字来体现的。throws throw ,凡是可以被这两个关键字所操作的类和对象都具备可抛性.

1,一般不可处理的。 Error

        特点:是由jvm抛出的严重性的问题。这种问题发生一般不针对性处理。直接修改程序

2,可以处理的。 Exception

        该体系的特点:子类的后缀名都是用其父类名作为后缀,阅读性很强。

异常处理原则

        功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。

        特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。

        引发异常时,会按顺序查看每个 catch 语句,并执行第一个与异常类型匹配的catch语句,其后 catch 语句被忽略

        在捕获异常的时候,应按照“从小到大”的顺序捕获异常,即先子类后父类。先子类异常,后父类异常。无论是否发生异常,finally块总被执行

        Java异常在try/catch块后加入finally块,可以确保无论是否发生异常 finally块中的代码总能被执行

        try…catch…finally异常处理结构中,try语句块是必须的,  catch和finally语句块为可选,但两者至少出现一个语句块

异常分两种

常见异常类型:

1、脚标越界异常(IndexOutOfBoundsException)包括数组、字符串;空指针异常(NullPointerException)

2、类型转换异常:ClassCastException

3、没有这个元素异常:NullPointerException

4、不支持操作异常;

异常要尽量避免,如果避免不了,需要预先给出处理方式。比如家庭备药,比如灭火器。

编译时异常

        编译时被检查的异常,只要是Exception及其子类都是编译时被检测的异常CheckedException :检查时异常,又称为非运行时异常,这样的异常必须在编程时进行处理,否则就会编译不通过。例如我们在前面的学习过程中,经常在编译的时候发生类找不到的情况,这就是一个典型的检查时异常。

运行时异常

        运行时异常,其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常。

        RuntimeException:运行时异常,即程序运行时抛出的异常,这种异常在写代码时不进行处理,Java源文件也能编译通过。

try{
   //可能抛出异常的语句块
}catch(SomeException1 e){ // SomeException1特指某些异常
   //当捕获到SomeException1类型的异常时执行的语句块
} catch(异常类 变量){
   //当捕获到SomeException2类型的异常时执行的语句块
   System.exit(0);//退出jvm。
}finally{
   //无论是否发生异常都会执行的代码,通常用于关闭(释放)资源。
}

区别:

        编译被检查的异常在函数内被抛出,函数必须要声明,否编译失败。

声明的原因:是需要调用者对该异常进行处理。

        运行时异常如果在函数内被抛出,在函数上不需要声明。

不声明的原因:不需要调用者处理,运行时异常发生,已经无法再让程序继续运行,所以,不让调用处理的,直接让程序停止,由调用者对代码进行修正。

异常处理关键词

        定义异常处理时,什么时候定义try,什么时候定义throws呢?

                功能内部如果出现异常,如果内部可以处理,就用try;

                如果功能内部处理不了,就必须声明出来,让调用者处理。

        异常可以被两个关键字所操作, throws 与throw

异常的分类:

        1,编译时被检测异常:只要是Exception和其子类都是,除了特殊子类RuntimeException体系。这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。这样的问题都可以针对性的处理。

        2,编译时不检测异常(运行时异常):就是Exception中的RuntimeException和其子类。这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用者的原因导致的而或者引发了内部状态的改变导致的。

        那么这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对代码进行修正。所以自定义异常时,要么继承Exception, 要么继承RuntimeException。

Throw

Throw用在方法内,用于抛出具体异常类的对象

1,抛给自己

运行时异常:1.无视异常:2.使用try-catch包裹throw:3,:throws抛出但不是必须的

检查时异常:1.必须自己try catch解决2.throws抛出这个异常

2,抛给方法调用者

public class NianLingException1 {

    public static void main(String[] args) {

        new NianLingException1().printAge(1000);

    }



    public void printAge(int age) {

        if (age >= 150 || age <= 0) {

            try {

                throw new NianLingException("操作终止,年龄输入异常");

            } catch (NianLingException e) {

                e.printStackTrace();

            }



        } else {

            System.out.println("年龄" + age);

        }

    }

}



class NianLingException extends Exception {// 检查时异常

    public NianLingException(String message) {

        super(message);

    }

}

Throws

        throws用于声明方法可能抛出的异常,其后为异常类,可以有多个,异常类之间用英文逗号间隔。

        如果throws抛出检查时异常类,此时方法调用者只能有两种处理方式:①、使用try-catch处理异常;②、继续上抛该异常类

        如果throws抛出的是运行时异常类,则方法调用者有三种方式来处理该异常:①、无视该异常;②、使用try-catch处理异常; ③、继续上抛该异常类

public class NianLingException1 {

    public static void main(String[] args) throws NianLingException {
        new NianLingException1().printAge(1000);
    }



    public void printAge(int age) throws NianLingException {
        if (age >= 150 || age <= 0) {
            throw new NianLingException("操作终止,年龄输入异常");
        } else {
            System.out.println("年龄" + age);
        }
    }
}



class NianLingException extends Exception {// 检查时异常
    public NianLingException(String message) {
        super(message);
    }
}

throws 和throw的区别:

        1, throws使用在函数上。throw使用在函数内。

        2, throws抛出的是异常类,可以抛出多个,用逗号隔开。throw抛出的是异常对象。

异常处理的原则:

        1,函数内容如果抛出需要检测的异常,那么函数上必须要声明。否则必须在函数内用try catch捕捉,否则编译失败。

        2,如果调用到了声明异常的函数,要么try catch要么throws,否则编译失败。

        3,什么时候catch,什么时候throws 呢?

                功能内容可以解决,用catch。解决不了,用throws告诉调用者,由调用者解决 。

        4,一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性的处理。内部又几个需要检测的异常,就抛几个异常,抛出几个,就catch几个。

自定义异常:

        当开发时,项目中出现了java中没有定义过的问题时,这时就需要我们按照java异常建立思想,将项目的中的特有问题也进行对象的封装。这个异常,称为自定义异常。

        对于角标是整数不存在,可以用角标越界表示,对于负数为角标的情况,准备用负数角标异常来表示。负数角标这种异常在java中并没有定义过。

        那就按照java异常的创建思想,面向对象,将负数角标进行自定义描述。并封装成对象。这种自定义的问题描述成为自定义异常。

        注意:如果让一个类称为异常类,必须要继承异常体系,因为只有称为异常体系的子类才有资格具备可抛性。

        对于除法运算,0作为除数是不可以的。java中对这种问题用ArithmeticException类进行描述。对于这个功能,在我们项目中,除数除了不可以为0外,还不可以为负数。可是负数的部分java并没有针对描述。所以我们就需要自定义这个异常。

区别:

        throw  用于抛出异常对象,后面跟的是异常对象,throw用在函数内。

        throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。

        通常情况:函数内容如果有throw,抛出异常对象,并没有进行处理,那么函数上一定要声明,否则编译失败。但是也有特殊情况。

异常的自定义步骤

        1:定义一个子类继承Exception或RuntimeException,让该类具备可抛性。

        2:通过throw 或者throws进行操作。

异常的转换思想

        当出现的异常是调用者处理不了的,就需要将此异常转换为一个调用者可以处理的异常抛出。

异常的子父类进行覆盖的特点:

        1:当子类覆盖父类的方法时,如果父类的方法抛出了异常,那么子类的方法要么不抛出异常要么抛出父类异常或者该异常的子类,不能抛出其他异常。

        2:如果父类抛出了多个异常,那么子类在覆盖时只能抛出父类的异常的子集。

注意:

        如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws。

        如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。

*/

异常代码

认识异常

public class ExceptionDemo{

    public static void main(String[] args) {

//    int[] arr = new int[1024*1024*800];//java.lang.OutOfMemoryError: Java heap space
        //arr = null;
        //System.out.println(arr[3]);
        sleep(-5);
    }

    public static void sleep2(int time) {
        if (time<0) {
            // 处理办法。
        } else if (time>100000) {
            // 处理办法。
        }else{
            System.out.println("我睡。。。 "+time);
        }
    }

    public static void sleep (int time) {
        if (time<0) {
            try {
                throw new FuTimeException("时间为负" + time);
            } catch (FuTimeException e) { 
                e.getMessage();//获取的是异常的信息。
                e.toString();//获取的是异常的名字+异常的信息。
                e.printStackTrace();//打印异常在堆栈中信息;异常名称+异常信息+异常的位置。
            }
            // 抛出 new FuTime();//就代码着时间为负的情况,这个对象中会包含着问题的名称,信息,位置等信息。
        } else if (time>100000) {
            // 抛出 new BigTime();
        } else {
            System.out.println("我睡。。。 "+time);
        }
    }
}

class FuTimeException extends Exception{
    public FuTimeException(String string) {
        super(string);
        System.out.println(string);// 时间为负-5

    }
}

自定义异常

class FuShuIndexException extends Exception {
    FuShuIndexException (String msg) {
        super(msg);
    }
}



class ExceptionDemo {
    public int method (int[] arr, int index) throws FuShuIndexException {
        // throws NullPointerException//FuShuIndexException
        if (arr == null) {
            throw new NullPointerException("数组的引用不能为空! ");
// Exception in thread "main" java.lang.NullPointerException: 数组的引用不能为空!
//      at com.ExceptionDemo.method(ExceptionDemo3.java:27)
//      at com.ExceptionDemo3.main(ExceptionDemo3.java:44)
        }

        if (index >= arr.length) {
            throw new ArrayIndexOutOfBoundsException("数组的角标越界啦,哥们,你是不是疯了?: " + index);
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 数组的角标越界啦,哥们,你是不是疯了?: 30
//      at com.ExceptionDemo.method(ExceptionDemo3.java:30)
//      at com.ExceptionDemo3.main(ExceptionDemo3.java:48)
        }

        if (index < 0) {
            throw new FuShuIndexException("角标变成负数啦!! ");
//  Exception in thread "main" com.FuShuIndexException: 角标变成负数啦!!
//     at com.ExceptionDemo.method(ExceptionDemo3.java:33)
//     at com.ExceptionDemo3.main(ExceptionDemo3.java:52)
        }
        return arr[index];
    }
}

public class ExceptionDemo3 {
    public static void main (String[] args) throws FuShuIndexException { 
        // throws FuShuIndexException
        int[] arr = new int[3];
        ExceptionDemo d = new ExceptionDemo();
//        int num = d.method(null, 30);
//        int num1 = d.method(arr, 30);
        int num2 = d.method(arr, -30);
        System.out.println("over");
    }
}

异常处理练习:

/** 
 * 毕老师用电脑上课。
 * 问题领域中涉及两个对象。
 * 毕老师,电脑。
 * 分析其中的问题。
 * 比如电脑蓝屏啦。冒烟啦。
 **/
class LanPingException extends Exception {
    LanPingException(String msg) {
        super(msg);
    }
}

class MaoYanException extends Exception {
    MaoYanException(String msg) {
        super(msg);
    }
}

class NoPlanException extends Exception {
    NoPlanException(String msg) {
        super(msg);
    }
}

class Computer {
    private int state = 1;

    public void run() throws LanPingException, MaoYanException {
        if (state == 1) {
            throw new LanPingException("电脑蓝屏啦!! ");
        }
        if (state == 2) {
            throw new MaoYanException("电脑冒烟啦!! ");
        }
        System.out.println("电脑运行");
    }

    public void reset() {
        state = 0;
        System.out.println("电脑重启");
    }
}

class Teacher {
    private String name;
    private Computer comp;

    Teacher(String name) {
        this.name = name;
        comp = new Computer();
    }

    public void prelect() throws NoPlanException {
        try {
            comp.run();
            System.out.println(name + "讲课");
        } catch (LanPingException e) {
            System.out.println(e.toString());
            comp.reset();
            prelect();
        } catch (MaoYanException e) {
            System.out.println(e.toString());
            test();
            // 可以对电脑进行维修。
            // throw e;
            throw new NoPlanException("课时进度无法完成,原因: " + e.getMessage());
        }
    }

    public void test() {
        System.out.println("大家练习");
    }
}

public class ExceptionDemo3 {
    public static void main(String[] args) {
        Teacher t = new Teacher("毕老师");
        try {
            t.prelect();
        } catch (NoPlanException e) {
            System.out.println(e.toString() + "......");
            System.out.println("换人");
        }
    }
}

内存的管理

        使用Java无须担心如何销毁对象。换句话说,就是在Java运行时,无须负责Java对象的内存管理。当Java不再使用某个对象时,它会自动进行垃圾回收。

        垃圾回收是一个比较复杂的过程,当程序运行时会自动检查整个内存,检查内存中哪些对象引用不再被使用。一旦检查出来后,便会安全删除这些对象。然而,由于垃圾回收需要占用系统的资源,所以它可能会影响应用程序代码的运行,即如果在执行应用程序代码的过程中,执行垃圾回收,则应用程序代码的执行时间可能延长,这会导致程序运行的延迟。由于不知道何时会进行垃圾回收,因此延迟的时间也是不可预知的。实时应用程序对时间的要求非常严格,即它们必须在确定的、已知的延迟条件下,执行应用程序代码,因此垃圾回收所引起的不可预知的延迟,就成为一个实时程序致命的问题。

        垃圾回收的主要问题是程序无法估计时间延迟导致程序执行的延迟。能否避免这种问题的发生呢?通过更频繁地进行垃圾回收,就可以限制最大延迟时间,因此使垃圾回收成为可预知的。

垃圾回收

1.何时对象被丢弃

        Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾收集算法一般要做两件基本的事情:

(1)发现无用信息对象;

(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。

        大多数垃圾回收算法使用了根集(root set)这个概念;所谓根集就是正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量),程序可以使用引用变量访问对象的属性和调用对象的方法。垃圾收集首先需要确定从根开始哪些是可达的和哪些是不可达的,从根集可达的对象都是活动对象,它们不能作为垃圾被回收,这也包括从根集间接可达的对象。而根集通过任意路径不可达的对象符合垃圾收集的条件,应该被回收。

        Java的垃圾回收机制一般包含近10种算法。对这些算法中的多数我们不必予以关心。其中最简单的一个:引用计数法,该方法是唯一没有使用根集的垃圾回收的算法,该算法使用引用计数器来区分存活对象和不再使用的对象。也就是说,当应用程序创建引用以及引用超出范围时,JVM必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。在使用JVM的垃圾回收机制对堆空间做实时检测的时候,发现当某对象的引用计数为0时,就将该对象列入待回收列表中。

2.对象被丢弃,是否立即回收

        如果一个对象赋值为null或者重新定向了该对象的引用者,则该对象被认定为没有存在的必要了,那么它所占用的内存就可以被释放。被回收的内存可以用于后续的再分配。但是,并不是对象被抛弃后立即被回收的。用JVM进程做空间回收是有较大的系统开销的。在实际的项目开发中,丢弃一个对象,创建一个对象,这样的操作不计其数。如果每当某一应用进程丢弃一个对象,JVM就立即回收它的空间,势必会使整个系统的运转效率非常低下。

        前面说过,JVM的垃圾回收机制有多个算法。除了引用计数法是用来判断对象是否已被抛弃外,其他算法是用来确定何时及如何进行回收。JVM的垃圾回收机制要在时间和空间之间做个平衡。

        因此,为了提高系统效率,垃圾回收器通常只在满足两个条件时才运行:有对象要回收且系统需要回收。切记垃圾回收要占用时间,因此,Java运行时系统只在需要的时候才使用它。因此用户无法知道垃圾回收发生的精确时间。

3.垃圾回收

        许多人对Java的垃圾回收不放心,希望在应用代码里控制JVM的垃圾回收运作。这是不可能的事。对垃圾回收机制来说,应用只有两个途径发消息给JVM。第一个前面已经说了,就是将指向某对象的所有引用变量全部移走。这就相当于向JVM发了一个消息:这个对象不再需要了。第二个是调用库方法System.gc(),多数书里说调用它让Java进行垃圾回收。

        GC即垃圾收集机制是指JVM用于释放那些不再使用的对象所占用的内存。Java语言并不要求JVM有GC,也没有规定GC如何工作。不过常用的JVM都有GC,而且大多数GC都使用类似的算法管理内存和执行收集操作。

        如果第一种方式是一个通知,那么调用System.gc()也仅仅算是一个请求。JVM接受这个消息后,并不是立即进行垃圾回收,而只是对几个垃圾回收算法进行加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

        在大多数的系统中认为希望JVM及时回收垃圾,是一种提高内存运行效率的需求。其实,还有些系统会提出相反的一种需要:在某段时间内最好不要回收垃圾。例如要求运行速度最快的实时系统,特别是嵌入式系统,往往希望如此。

        Java的垃圾回收机制是为所有Java应用进程服务的,而不是为某个特定的进程服务的。因此,任何一个进程都没有权利去命令垃圾回收机制做什么、怎么做或做多少。

问题

对象在什么地方变得适用于垃圾收集

对于最初由gc4所引用的对象,该对象在如下程序中的什么地方变得适用于垃圾收集?
public class GcDemo {  //1

    public static void main (String[] args) {//2
        String gc1; //3
        String gc2 = "This name was called with gc2 ";//4
        String gc3 = " This name was called with gc3";//5
        String gc4 = new String((args.length > 0) ? "" + args[0] + "'" //6
: "< no argument>");//7
        gc1 = gc4;//8
        gc4 = null;//9
        gc1 = gc2 + gc1 + gc3;//10
        gc2 = null;//11
        System.out.println(gc1);//12
        gc1 = null;//13
        gc3 = null;//14
        args = null;//15
    }//16   
}//17
  1. 从标签为10的那一行以后,如果一个对象赋值为null或者重新定向了该对象的引用者,则该对象被认定为变得适用于垃圾收集。在第9行之前,最初被gc4引用的String对象由gc1和gc4来表示。在第9行之后,该String对象就只由gc1来表示。在第10行之后,引用gc1被赋予一个null值,即不再指向该对象的引用。而第10行之后,该对象已经没有引用了。
在下面的代码段中,在1~4行中第几行的obj符合垃圾收集器的收集标准?在5~8行中第几行的内存空间符合垃圾收集器的收集标准?
1 obj = new Object ( ) ;
2 obj. Method ( ) ;
3 obj = new Object ( ) ;
4 obj. Method ( ) ;
5 Object nobj = new Object ( ) ;
6 Object nobj = null ;
7 Object nobj = new Object ( ) ;
8 nobj = new Object ( ) ;

参考答案

(1)第3行,因为第3行的obj被赋了新值,产生了一个新的对象,即换了一块新的内存空间,也相当于为第1行中obj赋了null值。

(2)第5行和第7行。因为第6行为nobj赋值为null,所以在此第5行的nobj符合垃圾收集器的收集标准。而第8行相当于为nobj赋值为null,所以在此第7行的nobj也符合垃圾收集器的收集标准。

下面哪一种说法是正确的,请选择一个正确的答案。

A.利用关键词delete可以明确地销毁对象

B.对象变得不可达后马上被垃圾收集掉

C.如果对象obja对于对象objb而言是可妥的,对象objb对于对象obja而言也是可妥的,则obja和objb都不适用于垃圾收集

D.对象一旦变得适用于垃圾收集,则在被销毁之前它会保持着这种适用性

E.如果对象obja可以访问适用于垃圾收集的对象objb,那么obja同样适用于垃圾收集

试题分析

        在本面试题中,如果所有声名的对象引用都是来自其他也适合进行垃圾收集的对象,这个对象就是最适合进行垃圾收集的。所以,如果对象objb适合进行垃圾收集,而且对象obja包含执行objb的引用,那么对象obja也必须进行垃圾收集。Java没有delete关键词。对象在变得不可达之后不必马上作为垃圾被收集,该对象只是适合垃圾收集。

        只要对象可以被任何存活线程访问,就不适合进行垃圾收集,一个已经成为垃圾收集目标的对象还可以消除这种适合性。当对象的finalize()方法创建了一个指向该对象的可达引用时,就可以出现这种情况。

参考答案E

对垃圾回收的正确描述

关于垃圾回收的叙述,下面哪些选项是正确的?

        A.保持对不可达对象的跟踪

        B.可以直接回收具体的对象

        C.能够保证Java程序永远不会耗尽内存

        D.作为一个低优先级的线程在后台运行

试题分析

        Java的垃圾回收并不能保证内存的耗尽;垃圾回收只是一个低优先级的后台线程,而且跟踪可达或者不可达对象。

参考答案AD

关于垃圾收集的哪些叙述是正确的?

        A.垃圾收集将检查并释放不再使用的内存

        B.垃圾收集允许程序开发者明确指定并立即释放该内存

        C.程序开发者必须自己创建一个线程进行内存释放的工作

        D.垃圾收集能够在期望的时间内释放被Java对象使用的内存

试题分析

        Java语言将内存分配和释放的工组交给了自己,程序员不必做这些工作,它提供一个系统级的线程跟踪每个内存的分配,在JVM的空闲处理中,垃圾收集线程将检查和释放不再使用的内存(即可以被释放的内存)。垃圾收集的过程在Java程序的生存期中是自动的,不需要分配和释放内存,也避免了内存泄漏。可以调用System.gc()方法建议JVM执行垃圾收集以使得可被释放的内存能立即被使用,当此方法返回的时候,JVM已经做了最大的努力从被丢弃的对象上回收内存空间。程序员不能指定收集哪些内存,一般而言也不用关心这个问题,除非是程序的内存消耗很大,特别是有很多临时对象时可以"建议"进行垃圾收集以提高可用内存。需要指出的是调用System.gc()方法不能保证JVM立即进行垃圾收集,而只能是建议,因为垃圾收集线程的优先级很低(通常是最低的)。

        在JVM垃圾收集器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了默认机制来终止化该对象并释放资源,这个方法就是finalize( )。因此选项A是正确的。

        垃圾收集器不可以被强制执行,但程序员可以通过调用System. gc方法来建议执行垃圾收集器。因此选项B是错误的。

        Java的垃圾回收机制是为所有Java应用进程服务的,而不是为某个特定的进程服务的。因此,任何一个进程都没有权利去命令垃圾回收机制做什么、怎么做或做多少。因此选项C是错误的。

        Java运行时系统只在需要的时候才使用垃圾收集。因此用户无法知道垃圾回收发生的精确时间。同样没有办法预知在一组均符合垃圾收集器收集标准的对象中,哪一个会被首先收集。因此选项D是错误的。

参考答案A

finalize()方法

        JVM垃圾收集器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了默认机制来终止化该对象并释放资源,这个方法就是finalize()。它的原型为:

        protected void finalize() throws Throwable

        finalize是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。

        Java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。由于finalize函数没有自动实现链式调用,我们必须手动地实现,因此finalize函数的最后一个语句通常是super.finalize()。通过这种方式,我们可以从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。

        根据Java语言规范,JVM保证调用finalize函数之前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。之所以要使用finalize(),是由于有时需要采取与Java的普通方法不同的一种方法,通过分配内存来做一些具有C风格的事情。这主要可以通过"固有方法"来进行,它是从Java里调用非Java方法的一种方式。

        很多Java初学者会认为这个方法类似于C++中的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三:其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象做很多附加的工作。其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用finalize会降低GC的运行性能。其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。

关于finalize()的正确描述

下面有关Java垃圾收集的说法,哪一个是正确的?

A.所有的对象都有一个finalize()方法

B.可以通过明确调用finalize()方法而销毁对象

C.finalize()方法声明时可以带有任何可访问性修饰符

D.对于适用于垃圾收集的对象而言,如果它的finalize()方法在执行期间抛出了异常,则Java会忽略该异常并销毁该对象

E.如果代码中所定义的覆盖finalize()方法并没有明确调用父类中的被覆盖finalize()方法,则编译器将无法编译

试题分析

        Object对象有一个finalize()方法,由于所有的类都是从Object类继承来的,因此,所有的对象都有一个finalize()方法,所以A选项是正确的。

        类可以覆盖finalize()方法,而且和普通的方法覆盖规则一样,不能降低finalize()方法的可访问性。调用finalize()方法本身不会破坏该对象。所以B选项和C选项是错误的。

        当JVM的拦截收集器调用一个合适对象的finalize()方法时,它会忽略任何由finalize()方法抛出的异常。在其他情况下,finalize()方法中的异常处理并没有特殊的规定,同普通方法处理异常是一样的,所以D选项是错误的。子类继承父类,则自动拥有父类的全部的public的属性和方法,所以E选项的说法显然也是错误的。

参考答案A

深入学习

当JVM处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块可以回收的无用的内存块。垃圾收集器线程是一种低优先级的线程,在一个Java程序的生命周期中,它只有在内存空闲的时候才有机会运行。

垃圾收集器的主要特点有:

(1)垃圾收集器的工作目标是回收已经无用的对象的内存空间,从而避免内存渗漏体的产生,节省内存资源,避免程序代码的崩溃。

(2)垃圾收集器判断一个对象的内存空间是否无用的标准是:如果该对象不能再被程序中任何一个"活动的部分"所引用,此时我们就说,该对象的内存空间已经无用。所谓"活动的部分"是指程序中某部分参与程序的调用,正在执行过程中,且尚未执行完毕。

(3)垃圾收集器线程虽然是作为低优先级的线程运行,但在系统可用内存量过低的时候,它可能会突发地执行来挽救内存资源。

4)垃圾收集器不可以被强制执行,但程序员可以通过调用System.gc方法来建议执行垃圾收集器。

(5)不能保证一个无用的对象一定会被垃圾收集器收集,也不能保证垃圾收集器在一段Java语言代码中一定会执行。因此在程序执行过程中被分配出去的内存空间可能会一直保留到该程序执行完毕,除非该空间被重新分配或被其他方法回收。由此可见,完全彻底地根绝内存渗漏体的产生也是不可能的。但是请不要忘记,Java的垃圾收集器毕竟使程序员从手工回收内存空间的繁重工作中解脱了出来。设想一个程序员要用C或C++来编写一段10万行语句的代码,那么他一定会充分体会到Java垃圾收集器的优点!

6)同样没有办法预知在一组均符合垃圾收集器收集标准的对象中,哪一个会被首先收集。

7)循环引用对象不会影响其被垃圾收集器收集。

8)可以通过将对象的引用变量(reference variables,即句柄handles)初始化为null值,来暗示垃圾收集器来收集该对象。但此时,如果该对象连接有事件监听器(典型的 AWT组件),那它还是不可以被收集。所以在设一个引用变量为null值之前,应注意该引用变量指向的对象是否被监听,若有,则要首先除去监听器,然后才可以赋空值。

9)每一个对象都有一个finalize( )方法,这个方法是从Object类继承来的。

10)finalize( )方法用来回收内存以外的系统资源,就像是文件处理器和网络连接器。该方法的调用顺序和用来调用该方法对象的创建顺序是无关的。换句话说,编写程序时该方法的顺序和方法的实际调用顺序是不相干的。请注意这只是finalize( )方法的特点。

11)每个对象只能调用finalize( )方法一次。如果在finalize( )方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。

12)垃圾收集器跟踪每一个对象,收集那些不可到达的对象(即该对象没有被程序任何"活的部分"所调用),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用finalize( )方法,通过让其他对象知道它的存在,而使不可到达的对象再次"复苏"为可到达的对象。既然每个对象只能调用一次finalize( )方法,所以每个对象也只可能"复苏"一次。

13)finalize( )方法可以明确地被调用,但它却不能进行垃圾收集。

14)finalize( )方法可以被重载(overload),但只有具备初始finalize( )方法特点的方法才可以被垃圾收集器调用。

15)子类的finalize()方法可以明确地调用父类的finalize()方法,作为该子类对象的最后一次适当的操作。但Java编译器却不认为这是一次覆盖操作(overriding),所以也不会对其调用进行检查。

16)当finalize( )方法尚未被调用时,System. runFinalization( )方法可以用来调用finalize( )方法,并实现相同的效果,对无用对象进行垃圾收集。

17)当一个方法执行完毕,其中的局部变量就会超出使用范围,此时可以被当作垃圾收集,但以后每当该方法再次被调用时,其中的局部变量便会被重新创建。

18)Java语言使用了一种"标记交换区的垃圾收集算法"。该算法会遍历程序中每一个对象的句柄,为被引用的对象做标记,然后回收尚未做标记的对象。

19)Java语言允许程序员为任何方法添加finalize( )方法,该方法会在垃圾收集器交换回收对象之前被调用。但不要过分依赖该方法对系统资源进行回收和再利用,因为该方法调用后的执行结果是不可预知的。

通过以上对垃圾收集器特点的了解,你应该可以明确垃圾收集器的作用以及垃圾收集器判断一块内存空间是否无用的标准。简单地说,当你将一个对象赋值为null并且重新定向了该对象的引用者,此时该对象就符合垃圾收集器的收集标准。

判断一个对象是否符合垃圾收集器的收集标准,这是Sun公司程序员认证考试中垃圾收集器部分的重要考点(也可以说,这是唯一的考点)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java垃圾回收机制Java虚拟机(JVM)自动管理内存的一种机制。在Java程序中,程序员不需要手动分配和释放内存,而是由JVM负责自动回收不再使用的对象。 Java垃圾回收机制的基本原理是通过“可达性分析”来确定哪些对象是存活的,然后回收那些不再被引用的对象。可达性分析是从一组称为“GC Roots”的起始对象开始,通过引用链来遍历和标记可达对象。无法通过引用链访问到的对象则被标记为可回收的垃圾对象。 Java垃圾回收机制的工作过程分为三个阶段:标记、清除和整理。在标记阶段,垃圾回收器标记所有可达对象。在清除阶段,垃圾回收器回收标记为垃圾的对象,并且释放它们所占用的内存空间。在整理阶段,垃圾回收器将存活的对象向一端移动,以便在内存布局中形成连续的内存块。这样可以解决内存碎片问题,提高内存利用率。 Java垃圾回收机制具有以下优点: 1. 自动化管理:程序员不需要手动释放内存,减少了内存泄漏和悬挂对象(对象已被释放但仍然被引用)的可能性。 2. 高效性能:Java垃圾回收机制采用分代回收算法,根据对象的存活时间将内存分为多个代,根据不同的代使用不同的垃圾回收算法,提高了回收效率。 3. 防止空指针异常垃圾回收器可以检测并处理不再使用的对象,避免了程序运行过程中的空指针异常错误。 4. 提高开发效率:不需要手动管理内存,减少了程序员的工作量,提高了开发效率。 然而,Java垃圾回收机制也存在一些问题,比如垃圾回收过程可能会导致程序的暂停时间变长,对实时性要求较高的程序可能会受到影响。此外,垃圾回收机制的性能也会受到堆内存的大小和程序的运行负载等因素的影响。 总的来说,Java垃圾回收机制是一种非常成熟和高效的内存管理机制,它解放了程序员手动管理内存的负担,提高了开发效率和程序的健壮性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值