《Thinking In Java》读书笔记——第五章 初始化与清理

1 构造器

构造器的名称必须与类名完全相同,且没有返回类型的指定。

如果不写,类中会提供一个默认空参数列表的构造器,如果创建了构造器,此默认构造器则不存在。

new对象时默认会调用构造器,并分配内存空间。

当创建子类对象时,需要先调用父类构造器(Object是顶级父类),如果有静态块则优先执行,如果有初始化块则优先于构造器执行

执行结果:

静态初始化块和非晶态初始化块的区别?

非静态初始化块主要是用于对象的初始化操作,在每次创建对象的时都要调用一次,其执行顺序在构造方法之前。在初始化块之前有static修饰,则为静态初始化块。由于非静态成员不能再静态方法中使用,同样也不能在静态初始化块中,因此,静态初始化块主要用于初始化静态变量和静态方法,静态初始化块只调用一次,是在类的第一次加载到内存时,并非一定要创建对象才执行,静态初始化块比非静态初始化块先执行。

2 方法重载

同一个类,多个同名方法。

修改参数列表,方法名加上参数列表称为方法签名。

3 this关键字

引用书上例子:

class 香蕉 {
  void 剥皮(int i) {
    // 剥皮逻辑
  }
}

public class 带皮的香蕉 {
  香蕉 j1 = new 香蕉(1); // 1号香蕉
  香蕉 j2 = new 香蕉(2); // 2号香蕉
  j1.剥皮(1);
  j2.剥皮(2);
}

如果剥皮的方法只有一个,它是怎么知道是被哪一根香蕉调用的呢?

编译器内部会把调用代码变成类似:

香蕉.剥皮(j1, 1);
香蕉.剥皮(j2, 1);

它会把方法的调用对象的引用放到第一个参数位置,并且使用了this关键字指向这个引用。因此,在方法体中可以使用this代表当前对象(调用方法的那个对象)。

看上去,既然编译器会把调用对象默认地传到方法的参数列表中,是不是所有方法都会有this呢?当然不是,因为static修饰的方法,由于不需要new,是面向类的而不是面向对象的,因此调用static方法时,是没有this的。

static方法的调用,是通过向类型(class)发送消息,而不是向类的实例(instance)发送消息。从这个角度也解释了,为什么静态方法可以直接调用,而不需要new(实际上内存分配方式和普通方法也是不同的)。

为什么成员方法可以直接调用静态方法,静态方法不能直接调用成员方法?因为静态方法实际上是随便调用,因为它不需要知道是谁在调用。返过来就不行了,静态方法里面不一定有对象的实例,成员方法就不知道是谁在调用了,也就不清楚this到底是谁。

 

4 清理-finalize

初级java面试的时候经常会问,finally、final、finalize有什么区别?前两个一个是对程序执行流程的控制,一个是对不可变类型的修饰,使用final在编译期可以完成计算,减轻运算负担。在这里我们只对finalize进行分析。

垃圾回收器(GC),只知道回收new出来的内存,它并不知道如何回收非堆上的内存,因此定义了finalize方法。当垃圾回收器准备回收内存时,会先调用finalize方法,并且在下一次垃圾回收动作时,才会真正回收对象占用的内存,因此finalize可以当作垃圾回收器的一个埋点。

因此,finalize的作用其实很有限,也不是说垃圾回收只要调用这个函数就ok了。实际上99%的回收还是GC做的,充其量你只能控制那1%弱的非堆内存。

那这个函数还有什么作用呢?

java支持native调用,用的比较多的就是调用C或C++(Windows/Linux --> dll/so)程序,这些程序分配内存是不由java管理的,C会调用malloc函数进行分配内存,所以需要在finalize中用本地方法销毁内存。

 

5 垃圾回收

经常问的一道面试题,GC调用之后会不会立刻释放内存?如果我有一个对象已经标记为无效,会不会立刻回收?为什么?

首先,垃圾回收的代价还是很高昂的,其次垃圾回收只会发生在内存不足的情况下。不然高频率启动垃圾回收机制,会影响程序效率,在垃圾回收和程序稳定运行之间有一个平衡点,需要保证程序稳定运行的前提下,尽量少用GC。如果内存频繁不足,且程序不存在内存泄漏的情况,那么还是加一条内存吧。

在创建对象的时候,会在堆(heap)上分配一块空间用于存储此对象,在栈(stack)上存一个对堆对象的引用。栈,顾名思义,是先进后出的(出口和入口是一个,相比下队列(Queue)就有入口也有出口)

栈示意图:

 

堆内存可以想象成一个传送带,有对象就放到带上,但是当垃圾回收之后,带上就会空着,就不能再使用了。内存不久就不够用了,这时候GC就会发挥作用,将对象重新紧凑排列,并和stack上的引用重新对应地址。

GC会从静态区和栈区开始遍历引用,找到有引用关系的对象,那些没有引用的对象就作为垃圾。

GC的几种回收方式:

停止-复制(stop-copy),先暂停程序运行,然后把有引用的对象全部复制一份,之前那份旧内存就可以删了。这种操作很大的问题就是程序需要暂停,并且需要多出一倍的内存来维护,相比较C++手动维护的方式就要多使用至少一倍的内存。

标记-清扫(mark-and-sweep),先遍历一遍,把有引用的对象打上标记,遍历完成后再遍历一次,把没有标记的全删掉。

由此可见,一次回收操作需要遍历大量的引用和对象,在清空内存后,为了让内存连续还需要修改堆内存地址,重新修改栈中引用的指向地址,还要规避各种由于内存操作带来的潜在风险,因此代价是十分高的。

最新的虚拟机应该已经有更新的操作了,这些操作都是比较老的,但是还有一定的参考意义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值