该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

众所周知, JAVA 这门语言最大的特点之一就是去除了 指针(pointer)的概念

不过, 这个特点只是对于开发者而言的. 因为引用的本质其实是指针的另一种表现形式.

我们知道, 指针的概念着实让不少初学者(乃至中级开发者)陷入使用的困境, 比如 :

1) 地址和值的概念容易混淆

2) 危险的对空指针(野指针)解引用操作导致程序崩溃

3) 指针所指向的堆上的内存由于开发者忘记去释放而导致内存泄漏

4) 多个指针指向同一个Heap中的对象, 当释放这个对象时造成其余指针变成悬垂指针

所以, JAVA设计者通过C++里的智能指针(smart_ptr)来缓解以上4个问题.

i) 建立对象的引用的概念 : String x = new String();

此时 x 是一个栈上的String类型的引用, 此引用与堆上的某个String类型的对象资源相关联.

我们不需要用C/C++中的: * (解引用) 或者 &(取指针) 操作符来使用这个引用。 这样, 开发者完全不必要担心地址和值的概念混淆. 因为JAVA把地址和值完全封装了起来,使得使用者无法接触.

ii) 增加垃圾回收机制(garbage collection 以下简称GC) :

所谓的GC , 即是当堆中的某个对象资源不存在任何引用指向它时, GC会自动释放此对象资源.

这样就可以缓解开发者忘记释放对象而导致内存泄漏的危险.

iii) 增加引用计数机制 (Reference Count 以下简称 RC)

在刚才我们讨论指针让开发者面临的困境时, 我列举了4条, 我觉得尤其应该关注第4条. 因为悬垂指针是最容易发生的错误.

假若我们定义一个引用计数类, 其中包含一个引用计数变量 count , 与一个指向对象资源的指针(JVM底层就是通过C/C++中的指针来实现) :

template // 假设该引用计数管理的资源类型是 Type (泛型)
class ref_cnt {
unsigned int count ; // 引用计数变量, 类型为无符号整数
Type *m_Buffer ; // 指向对象资源的指针. 类型为 Type *
};
然后我们还需要给它增加几个方法:
class ref_cnt {
public int Add_Ref() ; // 引用计数加1
public int Dec_Ref() ; // 引用计数减1
public void Destroy() ; // 释放堆中对象的资源
};
其中的方法定义就是:
public int Add_Ref() {
return ++count ; // 返回增加后的引用计数变量
}
public int Dec_Ref() {
if( --count == 0 ) //假若引用计数归零
this.Destroy(); // 释放管理的对象
}
public void Destroy() {
if( m_Buffer )
delete m_Buffer ; // 释放堆中资源
}
/

这样以后, 我们就可以轻松理解引用机制了.

1) String x = new String() ;
// 在堆中申请String类型的对象, 并让引用 x 指向它 , 那么底层类似于这样的实现:
x.ref_cnt = new ref_cnt(); // 申请一个引用计数对象
x.ref_cnt.m_Buffer = 堆中的地址;
x.ref_cnt.Add_Ref() ; // 增加该对象的引用计数 , 引用计数变量 == 1
2) String y = x ;
// 然后让引用 y 也指向它 :
y.ref_cnt = x.ref_cnt ; // y 和 x 共享一个引用计数对象
// y.ref_cnt.m_Buffer = x.ref_cnt.mBuffer ; 这行代码之所以被我注释掉的原因大家可以动脑筋想想看
y.ref_cnt.Add_Ref() ; // 此时引用计数变量 == 2
3) 若变量 y 的生命周期结束, 代码执行离开了它的作用域:
// 那么减少对象的引用计数
y.ref_cnt.Dec_Ref() ; // 此时引用计数变量 == 1
4) 若变量 x 的生命周期结束, 代码离开了它的作用域:
x.ref_cnt.Dec_Ref() ; // 此时引用计数变量 == 0 , 所以引用计数类会释放此对象.