Java初始化
变量初始化
一个变量如果不给他初始化就直接使用,会报错吗?
这个很快就可以验证一下,是无法通过编译的,也就是说必须要先初始化才能使用吗?
可是如下所示,如果是类成员变量的话,我们直接可以使用,并且初始化值是0,。
总结
一个变量如果是局部变量那么如果不初始化编译器就会报错,如果是类成员变量Java会给变量一个初始值,
下面代码说明了一个变量初始化的值,可以看到数字类型都是0;应用类型为null,boolean默认为false;char默认是空的;同样的如果变量在{}块里面,不初始化,也是会有一个默认值的,编译器不报错的。
public class InitTest {
short a;
int b;
long c;
boolean d;
char e;
byte f;
float g;
double h;
String i;
InitTest(){
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(e);
System.out.println(f);
System.out.println(g);
System.out.println(h);
System.out.println(i);
}
public static void main(String[] args) {
InitTest init = new InitTest();
}
}
初始化顺序
- 静态属性:static 开头定义的属性
- 静态方法块: static {} 圈起来的方法块
- 普通属性: 未带static定义的属性
- 普通方法块: {} 圈起来的方法块
- 构造函数: 类名相同的方法
- 方法: 普通方法
在一个Java类中成员是按照这个顺序进行初始化的,show you the code
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
// 变量
public String field = "变量";
// 静态初始化块
static {
System.out.println(staticField);
System.out.println("静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
// 构造器
public InitialOrderTest() {
System.out.println("构造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
Java清理
在Java类中可以有一个finalize()方法,一旦垃圾回收器准备好释放对象占用的空间,将首先调用finallize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用内存,这有点像C++中的析构函数,但是区别在于如果我在finallize()中我不想将这个对象回收,就可以在这个方法中执行一些操作,让这个对象不被回收,而C++中析构函数调用,就一定会被销毁。
而finallize()的真实用途是: 由于分配对象的时候使用的Java之外的创建对象的方法,即可能调用本地方法C,或者C++来创建对象(Native方法),而这些本地方法需要他们自己的释放方法,他们并不会由java垃圾回收所回收,所以万一本地方方法在使用的时候并没有释放空间,如C中的free(),那么就需要在finallize()调用他来释放空间。
垃圾回收
哪些是垃圾
Java中回收机制,先要对垃圾进行定义,引用计数器和对象可达法,引用计数法即每个对象都维护着一个表示有多少引用的一个值,当这个值归零了之后就释放其所占用的空间,这个方法有个缺陷,即两个对象互相引用且只有一个引用的时候就无法回收,而对于JVM来说这两个对象是无用的,所以这是会存在很大的内存泄漏的,所以它未被应用与任何一个Java虚拟机的实现中。取而代之的是对象可达法,即一个存活的对象(不会被回收)一定能最终追溯到其存活在堆栈或静态区中的引用,所以,要查找所有存活的对象,只要从根节点即堆栈或者静态区开始往下查找,知道访问为止,所有被查找到的对象都为存活对象。
如何回收
回收这里就讲两种算法:复制算法以及标记清除算法,以及这两种算法的自适应。
复制算法:当JVM标记存活的对象之后,会将存活的对象复制一份到另一个空间,比如我们把一块内存区域分成两块一样大的空间,A和B,当A空间满了之后就对其进行垃圾回收,将A中存活的对象复制到另一个空间B,并且是整齐划一的排列在B空间,然后将所有指向存活对象的指针进行修正,最后再将A空间中的内存全部回收。这里在复制这个动作的时候是暂停其他所有的线程的,虽然Java说回收线程优先级很低,但是在回收的时候复制算法是Stop The World的,所以这个回收算法的消耗在于复制的时候以及修正引用的时候。
标记清除算法:当JVM标记玩所有存活对象之后, 进行清理,在清理过程中将所有非存活的对象进行释放,不会发生任何复制操作,所以剩下的空间也会是不连续的,如果垃圾回收器想得到连续的空间,就需要重新整理剩下的对象。所以这个算法的主要消耗在于:清除这一步,并且清除这一步也是停止其他所有线程的(Stop the world)。
在内存分配中都是以块为单位,如果对象较大,会占用单独的块,如果这样的块很多的话,采用复制算法,将会导致频繁的大量复制,所以JVM在每个块上都有相应的代数(用来记录引用它是否存活),如果块在某处被引用,那么这个代数就会增加,这样垃圾回收器就只对上次刚分配的对象进行处理,这对大量短生命周期的对象很有帮助,垃圾回收会定期清理-----大型对象仍然不会被复制(只是代数会增加),JVM也会进行监视,复制算法回收器的效率很低的话,就会进行切换,切换成标记清除算法,同样的会监视标记清除算法,如果出现了很多的碎片,就会切换成复制算法,这就是自适应。
垃圾回收触发的条件是,分配空间的时候找不到足够的空间,所以如果空间很大的话,也会减少垃圾回收的次数,所以复制算法只利用到了一半的空间,本身就与垃圾回收的初衷所违背的,也就是说,如果空间很小的话,每次回收只回收了百分之20的空间,然后没过几分钟又要进行回收了,就会进入一个死循环,所以复制算法的最大用途还是在于它的整理空间的能力,所以这两种算法进行自适应是很好的一种方法,可以叫他----自适应的,分代的,停止-复制,标记清除式垃圾回收。