java变量存储、初始化、回收

1、保存到什么地方
①寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配的。我们对此没有直接的控制权限,也不可能在自己的程序里找到寄存器存在的任何踪迹。
② 堆栈。驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放哪些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,编译器必须准确的知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于他必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些java数据保存在堆栈里--特别是对象句柄,但对象并不放到其中。
③ 堆。一种常规用途的内存池(也在RAM区域),其中保存了对象。和堆栈不同,“内存堆”或“堆”(heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长时间。因此用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需要用new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间需要花费更长的时间!
④静态存储。这儿的“静态(static)”是指“位于固定位置”(尽管也在RAM里)。程序运行期间,静态存储的数据将随时等候调用。可用static关键字之处一个对象的特定元素是静态的。但对象本身永远都不会置入静态存储空间。
⑤常量存储。常数值通常直接置于程序代码的内部。这样做是安全的,因为它们永远都不会改变。有的常数需要严格的保护,所以可以考虑将他们置入只读存储器(ROM)。
⑥非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。即使程序中止运行,他们仍然保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是他们能存在于其他媒体中。一旦需要,甚至能将他们恢复成普通的、基于RAM的对象。 
2、类成员变量初始化
①一个对象在创建时,或者类的static方法/static字段首次访问时,解析器必须找到该类(在事先设好的类路径里搜索)
②找到类后,加载器加载该类,并去静态存存储区检索该类的static字段和static初始化块。如果该类已经加载过,则这部分跳过不在执行;如果该类尚未加载过,则首先为static字段赋初值,然后执行static初始化块操作。如果子类和父类都有static成员变量和static静态初始化块,则首先为父类的static字段赋初值、然后执行父类的static静态初始化块,在为子类的static字段赋初值,在执行子类的static静态初始化块。因此无论创建多少个对象,static字段和static初始化块,只会在第一次类加载的过程中执行。(注意这部分是在类加载的过程中执行)。
③然后创建该类的一个实例化对象,该类的对象的构造进程首先会在内存堆(heap)里为该对象分配足够多的存储空间。
④这种存储空间会清为零,为对象中的所有基本类型设为他们的默认值。
⑤进行字段定义时发生的所有初始化都会执行。这里主要包括两部分:一个是字段赋初值;一个是初始化块进行初始化;具体的执行顺序与构造器也有一定的关系。
⑥执行构造器。一般来说构造器会最后执行,但是如果存在继承的情况,那么父类的构造器会优先于子类的初始化(变量赋初值和初始化块),但是晚于父类自己的初始化过程。具体的执行过程是父类的成员变量赋初值、父类的初始化块、父类的构造函数、子类变量赋初值、子类初始化块、子类构造函数。
总结:因此综上所述,累的初始化过程如下

    父类--静态变量
    父类--静态初始化块
    子类--静态变量
    子类--静态初始化块
    父类--变量
    父类--初始化块
    父类--构造器
    子类--变量
    子类--初始化块
    子类--构造器
3、内存和其他资源的回收
       在讲这一部分,那就不得不提三部分知识要点,分别是:类提供的finalize()方法的作用,System.gc()的作用,以及垃圾回收器的工作原理。通过这些,我们需要做一下延伸扩展,知道jvm堆大小的调整。
       java可用垃圾回收器回收不再使用的对象占据的内存,用完后简单的释放一个对象并非总是安全的。假如对象分配一个“特殊”内存区域,没有使用new。垃圾回收器就不知道如何释放这个特殊内存区域。因为垃圾回收器只知道有new分配的内存,不知道如何释放这些特殊的内存。为了解决这个问题,java提供了一个名为finalize()的方法,在理想情况下,他的工作原理应该是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,他首先调用finalize(),而且只有在下一次垃圾回收过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。牢记finalize不等同于C++的的destructor;我们的对象可能不会当做垃圾被收掉。垃圾回收器只与内存有关,而finalize就是用来处理一些特殊的内存或者其他资源的回收,而且finalize只在垃圾回收器准备回收资源的时候执行。
        System.gc()是通知垃圾回收器应该进行垃圾回收了,但是jvm并不一定去真的回收。该方法只是通知jvm,jvm到底做不做由它自身说了算。JVM垃圾回收只有在内存到达影响JVM后续工作的时候,他才会去执行垃圾回收操作。Runtime.getRuntime().gc()与System.gc()功能相同。

        JVM的垃圾回收器的目的在于清理不在使用的对象。gc通过确定对象是否被活动的对象应用来确定是否收集该对象。常用的方法是引用计数和对象引用遍历。
        引用计数
算法使用引用计数器来区分存活对象和不再使用的对象。一般来说,堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时,引用计数器置为1。当对象被赋给任意变量时,引用计数器每次加1当对象出了作用域后(该对象丢弃不再使用),引用计数器减1,一旦引用计数器为0,对象就满足了垃圾收集的条件。
     
对象引用遍历从根集(所谓根集就量正在执行的Java程序可以访问的引用变量的集合包括局部变量、参数、类变量,程序可以使用引用变量访问对象的属性和调用对象的方法开始,沿着整个根集对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。
        JVM堆大小的调整。sun hotspot使用分代收集器,把堆分成三个主要的域:新域、旧域以及永久域。JVM生成的所有新对象放在新域中。一旦 对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。在永久域中存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。
        下面介绍如何控制这些域的大小:可使用-Xms和-Xmx控制整个堆的原始大小或最大值。使用-XX:NewRation设置新域和旧域的比列。-XX:NewSize和-XX:MaxNewSize设置新域在堆中的初始值和最大值。-
Xmn设置新域的最大最小值为同一个数值。-XX:PerSize标志设置永久域的初始值。-XX:MaxPerSize标志永久域最大值。

你可能感兴趣的:(java,变量,存储,初始化,回收)