标签:内存分配 特殊 收集 等于 重要 创建 检查 大量 函数
1、初始化与清理的重要性:
1、许多C程序的错误都源于程序员忘记初始化变量,特别是使用程序库时,如果不知道如何初始化库的构件更容易出错
2、当使用完一个元素时,这个元素就不会有什么影响了,所以很容易就会忘记,但是这个元素占用的资源一直存在,如果资源一直得不到释放,就会造成资源(内存)耗尽
2、构造器确保初始化:
在java中,通过提供构造器,可以确保每个对象都会得到初始化。创建对象时,如果类对象有构造器,java就会在操作对象之前自动调用相应的构造器,从而保证了初始化进行。
3、static的含义
static方法就是没有this的方法,在static方法的内部不能调用非静态方法,并且可以在没有创建任何对象的前提下,通过类本身调用static方法。
static方法中为什么不能使用this关键字?
当new 一个对象时并不是先在堆中开辟内存空间,而是先将类中的静态方法加载到方法区,然后再在堆内存中创建对象。所以说静态方法会随着类的加载而加载。当new一个对象时,该对象存在堆内存中,this关键字一般指该对象。
程序最终都在内存中执行,变量只有在内存中存在时才会被访问,类的静态成员属于类本身,在类加载的时候就会被分配内存,可以通过类名直接访问,非静态成员属于类对象,只有在类创建对象的时候才会被分配内存,然后通过类的对象进行访问。
static 方法不依赖任何对象就可以访问,因此对于静态方法来说,是没有this的,因为它不依赖于任何对象,既然都没有对象,所以就不会有this
4、清理:终结处理和垃当圾回收
finalize()方法:假定对象(并非使用new)获得了一块 "特殊"的内存区域,由于垃圾回收器只知道释放那些由new 分配的内存, 所以它不知道该如何释放这块"特殊"的内存,为了应对这种情况,java允许在类中定义一个finalize()方法。它的工作原理是: 一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
有些程序员刚开始时可能会误把finalize()方法当做C++中的析构函数(C++中销毁对象必须用到这个函数)。所以有必要明确区分一下:C++中对象一定会被销毁,java里面的对象并非总是被垃圾回收。
换句话说:
(1) 对象可能不被垃圾回收
(2) 垃圾回收并不等于 "析构"
这意味着当你不再需要某个对象之前,如果必须执行某些动作,那么得自己去做。
(3) 垃圾回收只与内存有关
使用垃圾回收器的唯一原因是为了回收程序不再使用的内存,但如果对象中含有其他对象,finalize()方法是不是就应该明确释放那些对象,不是的,无论对象是如何创建的垃圾回收器都会负责释放对象占据的内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。(这段话有点拗口),但是java中一切都是对象,那特殊是指什么。
特殊情况是说由于在分配内存时可能采用了类似C语言中的做法,而不是java中的通常做法。这种情况主要发生在使用 "本地方法" 的情况下,本地方法是一种在java中调用非 java代码的方式。
通常,不能指望finalize()进行清理,必须创建其他的 "清理" 方法,并且明确调用。当对某个对象不再感兴趣,也就是说可以被清理了,这个对象应该处于某种状态,是它占用的内存可以被安全地释放。例如,要是对象代表了一个打开的文件,在对象被回收前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在隐晦的缺陷。finalize()可以用来最终发现这种情况。如果某次finalize()的动作使得缺陷被发现,那么就可以据此找出问题所在。
5、垃圾回收器如何工作
引用计数:引用记数是一种简单但速度很慢的垃圾回收技术,每个对象都含有一个引用计数器,当有引用连接到对象时,引用计数加1,当引用离开作用域或被设置为null 时,引用计数减1。虽然管理引用记数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为0时,就释放其占用的空间(引用记数模式经常会在计数值为0时立即释放对象)。这种方法有个缺陷,如果对象之间存在循环引用,可能会出现 "对象应该被回收,但引用计数不为0的情况"。对垃圾回收器而言,定位这样的交互自引用的对象组所需的工作量巨大。引用记数常用来说明垃圾收集的工作方式。
在一些更快的模式中,垃圾回收器并非基于引用记数记数,其依据的思想是: 对任何 "活" 的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链可能会穿过数个对象层次。所以,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有 "活" 的对象,对于发现的每个引用,必须追踪它所引用的对象,然后是此对象所包含的所有引用,如此反复进行,直到 "根源于堆栈和静态存储区的引用" 所形成的网络全部被访问为止。你所访问过的对象必须都是 "活" 的。这就解决了 "交互自引用的对象组" 的问题。
在上述方式下,java虚拟机采用一种自适应的垃圾回收技术。如何处理找到的存活对象,取决于不同的java虚拟机实现。有一种做法叫 停止-----复制。显然这意味着先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单、直接地分配新空间。当把对象从一处搬到另一处时,所有指向它的那些引用都必须修正(对象的引用从A处将指向B处)。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到(可以想象成有个表格,将旧地址映射到新地址)
对于这种方法来说,效率会降低,这有两个原因,首先,得有两个堆,然后得在这两个分离的堆之间来回复制,从而得维护比实际需要多一倍的空间。第二个问题是复制。程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾,尽管如此,复制式回收器仍然会将所有内存自一处复制到另一处,这很浪费。 为了避免这种情况,一些java虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种模式。这种模式成为标记-----清扫。对一般用途而言,标记---清扫方式速度相当慢,但是如果只产生少量垃圾甚至不产生垃圾时,速度就会很快了。
标记----清扫所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当找到一个存活的对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
停止----复制的意思是这种垃圾回收动作不是在后台进行的,垃圾回收动作发生的同时,程序将会被暂停。当可用内存数量较低时,Sun版本的垃圾回收器会暂停运行程序,同样,标记----清扫工作也必须在程序暂停的情况下才能进行。
在java虚拟机中,内存分配以较大的 "块" 为单位,如果对象较大,会占用单独的块。严格来说,停止----复制 要求在释放旧对象之前,必须先把所有存活对象从旧堆复制到新堆,这会导致大量内存复制行为。有了 "块" 之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的代数来记录它是否还存活。通常,如果块在某处被引用,其代数会增加,垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作----大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到标记-----清扫方式,同样,java虚拟机会跟踪标记----清扫的效果,要是堆空间出现很多碎片,就会切换到停止-----复制方式,这就是 "自适应" 技术。
6、成员初始化
对象的创建过程: 假设有个dog 类
1、即使没有显式的使用static关键字,构造器实际上也是静态方法,因此,当首次创建类型为dog的对象时(构造器可以看成静态方法),或者dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,定位 dog.class文件
2、载入dog.class (创建一个class 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在 class 对象首次加载的时候进行一次。
3、当 new Dog() 创建对象的时候,首先将在堆上为 Dog对象分配足够的存储空间。
4、这块存储空间会被清零,就自动的将Dog对象中所有基本类型数据都设置成了默认值,引用则被设置成了 null。
5、执行所有出现于字段定义处的初始化动作
6、执行构造器
标签:内存分配 特殊 收集 等于 重要 创建 检查 大量 函数
原文地址:https://www.cnblogs.com/zhangchu/p/12587205.html