一 实例变量的初始化
1 当java虚拟机创建一个类的新实例时,不管是明确的还是隐含的,首先都需要在堆中为对象分配内存,所有在对象的类中和它的超类中声明的变量(■包括隐藏的实例变量)都要分配内存.当然还有其他一些内容需要分配内存.
2 一旦虚拟机为新对象分配了堆内存,就立即把实例变量初始化为默认的初始值.为实例变量赋默认初始值后,它随后就会为实例变量赋正确的初始值,正确的初始值的可能来源有下:
(1)从被clone的对象中拷贝
(2)反序列化时从输入流中读入的值
(3)否则,虚拟机调用对象的实例初始化方法<init>
3 关于<init>
(1)java编译器为它编译的每一个类都至少生成一个实例初始化方法<init>,每一个类的构造方法对应一个<init>方法,包括无明确声明时的默认构造方法
(2)<init>可能包含三种代码:
①调用另一个<init>方法(包括this的和super的),
②实例变量初始化语句,
③构造方法方法体的代码
它们是有顺序的,具体分为两种情形:
当构造方法中调用同类中另一个构造方法时,它对应的<init>方法由两部分组成: 即同类的<init>方法 + 构造方法的字节码;
当构造方法中没有调用同类的构造方法时,它对应的<init>方法由三部分组成: 一个超类的<init>方法(指定的或默认的,但是对于没有父类的Object第一部分不存在) + 实例变量初始化语句 + 构造方法体字节码
总之,调用一个<init>方法必然会导致父类<init>方法调用,必然会导致实例变量初始化语句执行,只是直接还是间接关系,并且父类<init>方法(指定的或默认的,假如父类没有默认构造方法,那么子类构造方法必需显式调用父类构造方法)必然第一时间执行,接下来是实例变量初始化语句执行.
此外还要注意,<init>方法不允许捕捉由它们所调用的<init>方法抛出的任何异常,<init>方法执行只能被异常意外中止
二 垃圾收集和对象的终结
如果类声明了一个名为finalize()的返回void的方法,垃圾收集器会在释放这个实例所占据的内存空间之前执行这个方法一次.也就是说在程序中主动调用finalize()不影响垃圾收集线程对其的调用,并且当终结方法被垃圾收集线程执行过一次后,如果对象重新被引用,随后两次变得不被引用,那么垃圾收集器不会第二次调用终结方法.
垃圾收集线程自动调用finalize()方法抛出的任何异常都将被忽略.
三 类型卸载
1 所有被装载的类型都在方法区占据内存空间,当它们不再被引用(不可触及)之后,占据的内存空间可以通过卸载类型而释放
2 使用启动类装载器装载的类型永远是可触及的,所以永远不会被卸载,只有使用用户定义的类装载器装载的类型才会变成不可触及的.(■那么指定classpath的是使用应用类装载器装载的,应该也会变成不可触及吧)
3 如果某个类型的Class实例被发现无法通过正常的垃圾收集堆触及,那么这个类型就是不可触及的,具体有两种判断方式:
(1)如果程序保持对Class实例的明确引用,它是可触及的
(2)如果在堆中还存在一个可触及的对象,那么它对应的Class实例是可触及的
(3)具体的形式划这样的:
堆中可触及对象A ---> 方法区中A的类型数据 ---> 堆中A类型的Class实例
方法区中A的类型数据还保存了父类父接口的类型数据,从而其父类或风父接口的类型数据所指向的Class实例也是可触及的.■对象不是直接持有一个Class实例的引用吗
(4)★如果要使动态加载的类型在不再使用时变得不可触及,那么:
st1: 要把类型的对象所有引用设为null,使对象不可触及,从而使对应在方法区的类型数据不可触及
st2: 把类的Class实例的引用设为null
st3: 把类的加载器的引用设为null,从而使其对象回收,不再持有有类的Class实例,以及不再持有在解析类的符号引用时额外加载的其他类的Class实例