Thinking In Java笔记(第五章 初始化与清理(二))

第五章 初始化与清理(二)

5.5 清理:终结处理和垃圾回收

清理的工作常常被忽略,Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定对象(并非使用new)获得了一块”特殊”的内存区域,由于垃圾回收器只知道释放那些由new分配的内存,所以不知道如何释放特殊内存。Java允许在类中定义一个名为finalize()的方法,工作原理”假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

Java的finalize()和C++的析构函数有所不同,C++中对象一定会被销毁(如果程序中没有缺陷的话),而Java中对象并非总是被垃圾回收。即:1.对象可能不被垃圾回收。2.垃圾回收并不等于”析构”

Java并未提供”析构函数”或类似的概念,要做的类似的清理工作,必须自己动手创建一个执行清理工作的普通方法。当”垃圾回收”发生时(不能保证一定会发生),finalize()得到了调用,相应的工作就会进行,如果垃圾回收没有发生,就不会被调用。

只要程序没有濒临存储空间用完的那一刻,对象占用的控件就总也得不到释放,如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,随着程序的退出,资源会全部还给操作系统。这个策略是恰当的,因为垃圾回收本身也占用内存控件,如果不使用,内存开销会变小。

5.5.1 finalize()用途何在

垃圾回收有关的任何行为(尤其是finalize()方法),它们也必须同内存及回收有关。finalize()方法存在的意义是为了回收那些用new创建出来的对象之外的特殊存储空间,是由于在分配内存时,可能采用了类似C语言中的做法,而非Java中的通常做法(new)。这种情况主要发生在使用”本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但它们可以调用其他语言写的代码。

在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用free()函数,否则存储空间将得不到释放,从而造成内存泄漏。free()是C和C++中的函数,所以需要在finalize()中用本地方法调用它。

5.5.2 你必须实施清理

要清理一个对象,用户必须在需要清理的时刻调用执行清理动作的方法。在C++中,所有对象都会被销毁,都应该被销毁。如果在C++中创建了一个局部对象(也就是在堆栈上创建,这在Java中行不通),此时的销毁动作发生在以”右花括号”为边界的、此对象作用域的末尾处。如果对象是用new创建的,那么当程序员调用C++的delete操作符(Java没有这个命令),就会调用相应的析构函数。如果没有调用delete,那永远不会调用析构函数,这样会出现内存泄漏。

Java中不允许创建局部对象,必须使用new创建对象。在Java中,也没有释放对象的delete,垃圾回收器会帮你释放存储空间。

5.5.3 终结条件

通常不能指望finalize(),必须创建其他的”清理”方法,并明确地调用它们。finalize()还有一个又去的用法,并不依赖于每次都要对finalize进行调用,也就是对象终结条件的验证。

当对某个对象不再感兴趣–也就是它可以被清理了,这个对象应该处于某种状态,使它占用的内存可以被安全地释放。下面的例子示范了finalize()可能的使用方法:

class Book {
    boolean checkedOut = false;
    Book(boolean checkOut) {
    checkedOut = checkOut;
    }
    void checkIn() {
        checkedOut = false;
    }
    protected void finalize() {
        if(checkedOut)
            System.out.println("Error : checked out");
    }
}
public class Test {
    public static void main(String[] args) {
        Book novel = new Book(true);
        novel.checkIn();
        new Book(true);
        System.gc();
    }
}

本例的终结条件时:所有Book对象在被当作垃圾回收前都应该被checkIn(),但是在new Book(true)这个对象没有被checkIn,要是没有finalize()来验证终结条件,很难发现这种缺陷。

System.gc()用于强制进行和终结动作。

5.5.4 垃圾回收器如何工作

垃圾回收器对于提高对象创建速度有明显的效果,Java虚拟机在工作的时候,存储空间的释放会影响存储空间的分配,由于垃圾回收器的存在,Java从堆分配空间的速度可以和其他语言从堆栈上分配控件的速度相媲美。

先了解其他系统中的垃圾回收机制将能帮助我们更好的理解Java中的回收机制,引用记数是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用和对象连接的时候,引用记数加1,当引用离开作用域或被置为null时,引用记数减1。

垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用记数为0时,就释放其占用的空间(但是,引用技术模式经常会在计数值变为0的时候立即释放对象)。如果对象之间存在循环饮用,可能会出现”对象应该被回收,但引用记数却不为0”的情况。引用记数常用来说明垃圾收集的工作方式,但似乎从来未被应用与任何一种Java虚拟机实现中。

在更快的一些模式中,垃圾回收器并非基于引用记数技术,而是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区中的引用。如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有”活”的对象,对于每个引用,必须追踪和它关联的对象,然后是此关联对象的所有引用,反复进行,直到全部被访问。

在上述的方式下,Java虚拟机将采用一种自适应的垃圾回收技术。其中有一种找到存活对象的方法名为停止-复制(stop-and-copy)。显然这意味着先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另外一个堆,没有被复制的都是应当被回收的。

当把对象从一处搬到另外一处时,所有之乡它的那些引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到。对于这种复制式回收器而言,效率会降低。1.需要两个堆来回倒腾,某些Java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。2.程序进入稳定状态后,产生少量垃圾,但是复制式回收器还是会不停的复制,对于第二种情况,一些Java虚拟机会进行检查:要是没有新的垃圾产生,就会切换到另一种工作模式(标记-清扫mark-and-sweep),Sun公司早期版本的Java虚拟机使用了这种技术。对一般用途而言,”标记-清扫”方式速度相当慢,但是当只会产生少量垃圾甚至不会产生垃圾的时候,速度就很快了。

标记-清扫 所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当找到一个存活的对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理才会开始。在清理过程中,没有标记的对象将全部被释放,不会有复制动作发生。所剩下的空间是不连续的,垃圾回收器要是希望得到连续的空间的话,就得重新整理剩下的对象。

停止-复制 的意思是这种垃圾回收机制不在后台进行。垃圾回收动作发生的时候,程序会被停止,Sun公司的文档中,许多参考文献将垃圾回收视为低优先级的后台进程,但事实上早起Sun公司Java虚拟机中并非在后台实现垃圾回收,而是当可用内存较少时,Sun版本的垃圾回收器会暂停运行程序,同样的标记-清扫工作也必须在程序暂停的情况下才能进行。

在Java虚拟机中,内存分配以较大的“块”为单位,严格来说,“停止-复制”要求在释放旧对象之前,必须先把所有存活对象从旧堆中复制到新堆,有了“块”之后,垃圾回收器在回收的时候可以将对象拷贝到废弃的块中,每个块都用响应的代数(generation count)来记录它是否还存活。垃圾回收器会定期进行完整的清理动作–大型对象仍然不会被复制(只是其代数会增加),内涵小型对象的块会被复制并整理。Java虚拟机会进行监视,在“标记-清扫”和“停止-复制”之间切换,这就是“自适应”技术。

Java虚拟机中有很多提高速度的附加技术,尤其是与加载器操作有关的,被称为”即时(just-in-time,JIT)”编译器的技术。它可以把程序全部或部分翻译成本地机器码(这本来是Java虚拟机的工作),程序运行速度得到了提升。当需要装在某个类时(通常是创建该类的第一个对象),编译器会首先找到.class文件,然后将该类的字节码装入内存。接下来有两种方案可供选择:

  • 让即时编译器编译所有代码,这种家在动作散落在整个程序声明周期内,累加起来会花费更多的时间,并且会增加可执行代码的长度。
  • 另一种成为惰性评估(lazy evaluation),意思是即时编译器只在必要的时候编译代码,这样不会执行的代码不会被JIT编译。
时间: 2024-10-14 11:45:44

Thinking In Java笔记(第五章 初始化与清理(二))的相关文章

Thinking in JAVA笔记——第五章 初始化与清理

5.1用构造器确保初始化 为了确保安全性,强制在使用前进行初始化 Java构造器与类名相同,无参数构造器有叫做默认构造器. 5.2 方法重载 method overloading:重载,同名不同参 method overriding:重写/覆盖,子类覆盖父类 5.2.1区分重载方法 独一无二的参数类型表,否则编译不通过! 5.2.2涉及基本类型的重载 如果参数类型是int,short,byte,long等,自动定位到int而不会执行short,byte 因为默认常数就是int型,如果没有int,

《Java编程思想》笔记 第五章 初始化与清理

1.构造器 因为创建一个类的对象构造器就会自动执行,故初始化某些东西特好 2.方法重载 方法名相同,参数列表不同. 2.1 区分重载方法 方法重载后区别不同方法的就是方法签名 -->参数类型和个数(参数顺序不同也能区分但一般不考虑顺序) 2.2 涉及基本数据类型的重载 范围由小到大:byte > short > char > int > long > float > double 自动提升: 传入类型小于声明类型,传入值提升至与其最近的类型,该类型参数的方法被调用

Thinking In Java笔记(第五章 初始化与清理(三))

第五章 初始化与清理 5.6 成员初始化 Java尽力保证:所有变量在使用前都能得到恰当的初始化.对于方法的局部变量,Java以编译错误的形式来保证.如下: void f() { int i; i++; //Error:i not initialized } 会得到一条错误的消息,提示i可能没有初始化.编译器可以给i赋初值,但是并没有这么做,因为没有初始化是程序员的疏忽,为了保证程序的稳定性,能帮助程序员找出程序的缺陷所在. 但如果是类的数据成员是基本类型,类的每个基本类型成员变量都会保证有一个

Java编程思想---第五章 初始化与清理(下)

第五章 初始化与清理(下) 5.7 构造器初始化 可以使用构造器来进行初始化,在运行时可以调用方法或执行某些动作来确定初值,但是我们无法阻止自动初始化的进行,它将在构造器被调用之前发生.例如: public class Counter { int i; Counter() { i = 7; } } 那么i首先被置为0,然后变成7.编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化,因为初始化早已得到了保证. 5.7.1 初始化顺序 在类的内部,变量定义的先后顺序决定了初始化

Java 编程思想 第五章 ----初始化与清理(1)

从今天开始每天一小时的java 编程思想的阅读和编码,其实就是把书上的代码抄下来. 5.5 清理:终结处理和垃圾回收 初始化和清理工作同等重要,但是清理工作却被常常忘记,但是在使用对象之后,对对象弃之不顾的做法并不是很安全.Java有自己的垃圾回收器负责回收无用的对象占据的内存资源.但也有特殊情况:假定你的内存区域不是用new获得的,这是无法用垃圾回收器释放所以java中允许在类中定义一个名为 finalize()的方法.       工作原理: 一旦垃圾回收器准备好释放对象占用的存储空间,将首

Java 编程思想 第五章 初始化与清理 上

休整几天,闲了蛋疼也没写文章,这开学了坚持每天写// 必须的天天写.不敢再松懈了.羡慕一好朋友能坚持的静下心来学习. 5.1 用构造器确保初始化 在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化.  创建对象时,如果其类具有构造器,Java就会在用户有能力操作兑现之前自动调用相应的构造器,从而保证了初始化的进行.  当然了 接下来的我们需要的问题是: 1.所取的任何名字都可能与类的某个成员名称相冲突: 2.调用股早期是编译器的责任,所以必须让编译器知道应该调用哪个方法: 采取

java笔记 第五章

循环结构(一) 1  whilc循环 whilc(条件){ //循环语句1 } 条件:布尔类型  变量或表达式.  顺序: 当条件为真,则继续运行循环语句1直到条件为假 在执行后面的语句. 2  do-whilc do{ //循环语句1 }whilc(条件) 条件:布尔类型  变量或表达式. 特点:不管条件为真还是假都会先执行1遍循环语句1. 顺序:先执行一次循环语句1,再去判断条件.如果条件为真则回到do执行循环语句1直到条件为假,跳出循环.

<Thinking in java 第五章> 初始化与清理

P86--构造器中可以用this(arg);来调用另一个构造器,但是却不能调用两个.此外,必须将构造器调用置于最起始处,否则编译器会报错. 关于finalize方法的流程: 当对象变成(GC roots)不可达时候,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收.否则,如对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法.执行finalize方法完毕后,GC会判断该对象是否可达,若不可达,则进行回收,否则

第五章 初始化与清理

1.初始化,变量初始化优先于方法,静态类型与非静态类型初始化的差别在于,前者发生在类加载阶段,而后者发生在创建对象的阶段. 2.数组初始化三种方法: (1)int[] a = {1, 2, 3}; 只能用于定义时初始化,这种方法不够灵活 (2)int[] a = new int[]{ 1, 2 ,3}; 数组大小由{}中元素个数决定 (3)int[] a = new a[10]; 数组大小可以指定. 3.实际上可变参数列表的实现就是数组,当你指定参数,编译器会为你填充数组 原文地址:https: