句柄和对象、

一、用句柄操纵对象

String s = “xie4ever”;

之前我一直没管String有什么意义,只是单纯的以为String是一个类型而已。

现在才知道这个String的名词叫句柄,每种编程语言都有自己的数据处理方式,就像c中可以对一个对象直接声明 int a = 2 , 也可以间接使用这个对象(使用指针等),比如 a->b;

这里的String,int都可以认为是句柄,可以使用遥控器和电视机来进行比喻。

(1)我们很多情况下都是使用遥控器来操作电视机

如以下情况:String s = “xie4ever”;s.length();

(2)我们可以单独拥有遥控器,但是没有电视机

这就意味着我们没办法看电视(没有初始化这个句柄)。

  1. String s;
  2. s.length();

这个时候如果我们希望对句柄进行操作,显然是不可能的,因为此时对象还不存在,会获得一个错误(运行期)。

(3)我们也可以单独拥有电视机,但是没有遥控器

电视机上也有按钮,这意味着我们可以直接操作电视机,不一定非要使用遥控器。

new String(“xie4ever”).length();

但是这意味着,我们使用电视机的时候始终不方便。我们想随时随地操作电视机,当然不可能一直把电视机带在身上,当然选择带着遥控板。这个就是句柄存在的意义。

(4)既然有句柄,为了使用,我们必须把句柄和对象关联

这个过程也可以称为对象初始化。

通常用new关键字达到这一目的。new的意思是:“把我变成这些对象的一种新类型”。

比如String,有两种初始化的方法:

  1. String xie = new String(“xie4ever”);
  2. String xie = “xie4ever”;

这两种初始化方式看起来达到的效果是一样的,但是在实际创建过程上是一样的吗?

但是这里要特别注意,千万别把基本数据类型和类弄混了!这两种东西的创建机制是完全不一样的。

  1. int xie = 1;
  2. Interger xie = 1;

这两种声明方式相同吗?有什么区别?

Java中的数据类型有两种。

1.基本类型

共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。

这种类型的定义是通过诸如int xie = 3; long xie = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int xie = 1; 这里的xie是一个指向int类型的引用,而不是句柄。

这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

栈里面主要存放一些基本类型的变量和对象句柄,比如上文中的String xie = “xie4ever”中的String。

2.包装类

如Integer, String,Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

总之我在这里妄言一句,只要是new动态创建的,那就都在堆分配内存去了。如果是基本类型,就在栈里分配内存。

(这里证明是我搞错了,String xie = “xie4ever”这种情况,没有使用new关键词,而且xie4ever这个对象是放在堆中的)

String xie = new String(“xie4ever”);因为是用new来创建对象,这个对象会在堆中分配内存。每调用一次就会创建一个新的对象。这里的句柄xie应该是放在栈里的,然后指向堆中的对象。(这里真不知道自己说对了没有,到时回头看看有没有错误)

String xie = “xie4ever”;会先在栈中创建一个对String类的对象引用变量xie,然后查找栈中有没有存放”xie4ever”,如果没有,则将”xie4ever”存放进栈,并令xie指向”xie4ever”,如果已经有”xie4ever” 则直接令xie指向”xie4ever”。

(看这里,我当初就是理解错了(创建顺序错了,位置也错了)。String xie = “xie4ever”会先在堆中查看(用equals)是否已经存在值等同于”xie4ever”的对象,如果没有,那就在堆中新开辟一块区域,放xie4ever对象。然后在栈中创建一个引用(这里应该不能称之为句柄)xie,之后把xie指向堆中的这块区域。如果有,那就创建引用xie,然后把xie指向已经存在的对象区域

不知道有没有说错,如果之后发现错了再回来改吧。

(这样解释粗略来讲是没错的,但是问题是这样实在是太粗略了,没有触及实际问题的核心,即在jvm中这是一个什么过程)

其实这里是非常复杂的,详细可见(https://www.zhihu.com/question/29884421

我之后打算单独研究下,看看能不能做一个总结。

二、对象保存在哪里?

对象是如何创建的?创建在哪里?

这些问题似乎在《深入理解java虚拟机》中有更详细的原理解释,这里只是稍微涉及。

程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据:

(1)寄存器

这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

(2)堆栈

驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放到其中。

(3)堆

一种常规用途的内存池(也在RAM区域),其中保存了Java对象。和堆栈不同,“内存堆”或“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!

(4)静态存储

这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。

(5)常数存储

常数值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。

(6)非RAM存储

若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。Java 1.1提供了对Lightweight persistence的支持。未来的版本甚至可能提供更完整的方案。

这个问题我觉得以后单独分析比较好。

三、特殊情况:主要类型

主要类型共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。

有一系列类需特别对待;可将它们想象成“基本”、“主要”或者“主”类型,进行程序设计时要频繁用到它们。

之所以要特别对待,是由于用new创建对象(特别是小的、简单的变量)并不是非常有效,因为new将对象置于“堆”里。对于这些类型,Java采纳了与C和C++相同的方法。也就是说,不是用new创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。

Java决定了每种主要类型的大小。就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是Java程序具有很强移植能力的原因之一。

个人理解:

主类型比较小比较简单,而且需要频繁调用。如果用new去动态生成并且在堆中分配空间,开销比较大划不来,干脆就直接在栈里面进行分配吧,可以高效存储。

至于int xie = 1,这里的xie就不是句柄了,只是一个指向栈中的1的指针(或者说是变量?)而已。

主类型不属于对象,不存放在heap堆区,而是直接存在stack堆栈区。

四、总结

这个第一部分内容有些涉及到jvm的知识了,已经提到了堆与栈的不同区别。感觉已经开始有点深入java底层了。

以前根本没有想过这些东西的实现原理,现在需要好好巩固一下知识。

发表评论

电子邮件地址不会被公开。 必填项已用*标注