java内存管理—>内存分配和内存回收
JVM的垃圾回收机制由一条后台线程完成,本身也是非常消耗性能的,因此如果肆无忌惮地创建对象,让系统分配内存,那这些分配的内存都将由垃圾回收机制进行回收。这样做有两个坏处:
*不断分配内存使得系统中可用内存减少,降低程序运行性能
*垃圾回收负担加重,降低程序运行性能
2.1实例变量和类变量
局部变量:形参,方法内部定义的变量,代码块内定义的变量,存储在方法的栈内存中
成员变量:类体内定义;如果没有static修饰,又叫非静态变量或者实例变量,如果使用static修饰,则又被称为静态变量或者类变量。static不能修饰外部类,局部变量,局部内部类
定义成员变量或者类变量时必须采用合法的前向引用,但如果一个是实例变量,一个是类变量时,则实例变量总是可以引用类变量(类变量的定义可以放在实例变量的后面)
1)在同一个JVM内,每个类只对应一个Class对象,但每个类可以创建多个java对象。类变量只需要一块内存空间,但对于实例变量,该类每创建一个实例,就需要为实例变量分配一块内存空间
类也是对象,所有类都是Class的实例。程序可以通过反射来获取某个类所对应的Class实例(person.class,Class.forName("person"))
对象可以访问类变量,但底层依然会转换为通过类来访问类变量
2)实例变量的初始化时机(3个):
*定义实例变量时指定初始值
*非静态初始化块
*构造器
调用构造器来创建java对象时,非静态初始化块也将获得执行的机会,而且在构造器之前获得执行。
前两种按照在源程序的排列顺序执行,会发生覆盖现象,后执行的会覆盖先执行的。编译后,这两种都将被提取到构造器中。
3)类变量的初始化时机
*定义类变量时指定初始值
*静态初始化块中对类变量指定初始值
这两种方式的执行顺序与它们在源程序中排列顺序相同
2.2父类构造器
当创建任何java对象时,程序总会先一次调用每个父类非静态初始化块,父类构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块,构造器执行初始化
1)隐式调用和显式调用
*子类构造器执行体的第一行代码使用super显式调用父类构造器,系统将根据super调用里传入的实参列表来确定调用父类的哪个构造器
*子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表来确定本类的另一个构造器(执行另一个本类构造器即进入第一种情况)
*子类构造器没有显式调用super和this,系统将在执行子类构造器之前,隐式调用父类的无参数构造器。
2)访问子类对象的实例变量
代码:
class Base{
private int i=2;
public Base(){
this.display();
}
public void display(){
system.out.println(i);
}
}
class Derived extends Base{
private int i=22;
public Derived(){
i=222;
}
public void display(){
system.out.println(i);
}
}
public class Test{
main(){
new Derived();
}
}
该程序将输出0
解释:Derived对象将拥有两个i实例变量,系统为其分配两块内存。
this.属性-->当前类的属性
this.方法-->子类方法
3)调用被子类重写的方法
如果父类构造器调用了被子类重写的方法,且子类调用这个父类构造器,就会导致子类重写方法在子类构造器的所有代码之前被执行,从而导致子类的重写方法访问不到子类的实例变量值的情况
2.3父子实例的内存控制
1)继承成员变量和继承方法的区别
父类定义的成员变量,系统依然将其保留在父类中,并不会将他转移到子类中。所以父子类可以同时拥有同名的实例变量。
如果子类重写了父类的方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法。对于实例变量则不存在这样的现象。即使子类定义同名变量,这个变量不可能覆盖父类的实例变量。
所以对于一个引用类型的变量而言,如果访问实例变量,值取决于声明该变量的类型,访问方法,则取决于它实际所引用的对象的类型
2)内存中的子类实例
系统内存中并不存在父类的对象,程序内粗中只有一个子类对象,只是它还保存了所有父类所定义的全部同名的实例变量
java中允许通过return this,返回调用该方法的java对象,但是没有return super
程序不允许直接把super当成变量使用。例如super==a(一个引用变量)是不允许的。
3)父,子类的类变量 情况类似
2.4final修饰符
1)可以修饰*变量(不能重新赋值) *方法(不能被重写) *类(不能被继承)
被final修饰的实例变量必须显示指定初始值,而且只能在如下三个位置指定初始值:定义时,非静态初始化块,构造器。这三种方式最终都会被抽取到构造器中赋初始值。final类变量只能在两个地方赋初始值,定义时和静态初始化块中。
final修饰的变量将成为宏变量(定义时赋初始值),所有出现该变量的地方,系统将直接把它当成对应的值处理。
2)执行宏替换的变量
final修饰符的一个重要用途就是定义宏变量,定义final时就赋一个明确的值,那就成为宏变量,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。如果赋值为表达式,只要是基本的算术运算表达式或者字符串连接运算,没有访问变量,调用方法,同样当成宏变量。
String str="疯狂java"+99;
String str2="疯狂java"+String.valueOf(99);
syso(str=str2);
3)final方法不能被重写
*父子类不在一个包下,父类方法使用默认及以下,子类不能重写父类的这个方法。
4)内部类中的局部变量
匿名内部类中访问和使用局部变量,那么这个局部变量必须使用final修饰符修饰。(隐式闭包)
内部类会扩大局部变量作用域。