Java 内存管理分为:内存分配和内存回收实例变量 和 类变量
局部变量
特点:作用时间短,存储在方法的栈内存中
种类:
成员变量
类体内定义的变量,如果该成员变量没有使用 static 修饰,那该成员变量又被称为非静态变量或实例变量,如果使用 static 修饰,则该成员变量又可被称为静态变量或类变量。
实例变量和类变量的属性
使用 static 修饰的成员变量是类变量,属于该类本身,没有使用 static 修饰的成员变量是实例变量,属于该类的实例,在同一个类中,每一个类只对应一个 Class 对象,但每个类可以创建多个对象。
由于同一个 JVM 内的每个类只对应一个 CLass 对象,因此同一个 JVM 内的一个类的类变量只需要一块内存空间;但对于实例变量而言,该类每创建一次实例,就需要为该实例变量分配一块内存空间。也就是说,程序中创建了几个实例,实例变量就需要几块内存空间。
这里我想到一道面试题目
运行结果
静态代码块只执行一次,而代码块每创建一个实例,就会打印一次。
实例变量的初始化时机
程序可在3个地方对实例变量执行初始化:
上面第一种和第二种方式比第三种方式更早执行,但第一、二种方式的执行顺序与他们在源程序中的排列顺序相同。
同样在上面那个代码上加上一个变量 weight 的成员变量,我们来验证下上面的初始化顺序:
1、定义实例变量指定初始值 在 非静态初始化块对实例变量指定初始值 之后
结果是
2、定义实例变量指定初始值 在 非静态初始化块对实例变量指定初始值 之前
结果为
大家有没有觉得很奇怪,我来好好说清楚下:
只说原理,大家肯定不怎么信,那么还有拿出源码来,这样才有信服能力的吗?是不?
这里我直接使用软件将代码的字节码文件反编译过来,看看里面是怎样的组成?
第一个代码的反编译源码如下:
第二个代码反编译源码如下:
通过反编译源码,可以看到第一种情况下(定义类变量时指定初始值在静态初始化代码块中对类变量指定初始值之后):
我们在静态初始化代码块中对类变量指定初始值已经不存在了,只有一个类变量指定的初始值static double height = 10.0D;, 而在第二种情况下(定义类变量时指定初始值在静态初始化代码块中对类变量指定初始值之前)和之前的源代码顺序是一样的,没啥区别。
上面的代码中充分的展示了类变量的两种初始化方式 :每次运行该程序时,系统会为 A 类执行初始化,先为所有类变量分配内存空间,再按照源代码中的排列顺序执行静态初始代码块中所指定的初始值和定义类变量时所指定的初始值。
父类构造器
当创建任何 Java 对象时,程序总会先依次调用每个父类非静态初始化代码块、父类构造器(总是从 Object 开始)执行初始化,最后才调用本类的非静态初始化代码块、构造器执行初始化。
隐式调用和显示调用
当调用某个类的构造器来创建 Java 对象时,系统总会先调用父类的非静态初始化代码块进行初始化。这个调用是隐式执行的,而且父类的静态初始化代码块总是会被执行。接着会调用父类的一个或多个构造器执行初始化,这个调用既可以是通过 super 进行显示调用,也可以是隐式调用。
当所有父类的非静态初始代码块、构造器依次调用完成后,系统调用本类的非静态代码块、构造器执行初始化,最后返回本类的实例。至于调用父类的哪个构造器执行初始化,分以下几种情况:
注:super 和 this 必须在构造器的第一行,且不能同时存在。
推荐一篇博客:Java初始化顺序文章从无继承和继承两种情况下分析了 Java 初始化的顺序。
Java初始化顺序如图:
访问子类对象的实例变量
调用被子类重写的方法
父子实例的内存控制
继承成员变量和继承方法的区别
方法的行为总是表现出它们实际类型的行为;实例变量的值总是表现出声明这些变量所用类型的行为。
内存中的子类实例
父、子类的类变量
final 修饰符
final 可以修饰变量、方法、类
final 修饰的实例变量只能在如下位置指定初始值
final 修饰的类变量只能在如下位置指定初始值:
网友评论