目录
1、Java内存分配
栈: 局部变量中的基本数据类型直接存入栈中
局部变量中对对象的引用(指针)存在栈中
堆: 成员变量全部存在堆中(包括:基本数据类型、对象引用和对象引用的实例)
【成员变量属于类,类最终都是要被new 出来的,所以成员变量全部存储在堆中】
public class Test_Subject {
//成员变量
private String hello; //存储于堆中
//成员变量
private Person per=new Person(); //per是对Person对象的引用,存储于堆中,new出来的Person对象也存储于堆中
class Person{
//实例变量
String name; //name存储在Person对象所属的堆空间中
}
public void showAge(int a){ //参数a也是局部变量 存储于栈中
//局部变量
int age=23; //age存储在栈中
//p也是局部变量
Person p=new Person(); //p存储在栈, new Person()这个对象存储在堆中
}
}
2、方法栈
存放方法的栈中保存了方法的状态,包括执行到了那一行代码,还有所有的局部变量。
下面是各个方法互相调用时,它们存放于栈中的状态。(正在执行的方法都会被放在栈顶。)
3、一个对象的内存
一个对象的内存=所有实例变量使用的内存+对象本身的开销(一般是16字节)
对象本身的开销=一个指向对象类的引用+垃圾回收信息+同步信息
另外一般内存的使用都会被填充为8字节(64位计算机中的机器字)的倍数。不够8的倍数,填充为8的倍数。
Integer对象内存(24字节)= 对象开销(16字节)+int(4字节)+填充字节(4字节)
Date对象内存(32字节)=对象开销(16字节)+3*int(12字节)+填充字节(4字节)
Counter对象内存(32字节)=对象开销(16)+String(8)+int(4)+填充字节(4)、
Node内存(40)=对象开销(16)+额外开销(8)+Item的引用(8)+Node的引用(8)
因为Node是一个非静态的内部类,所以它持有对外部类的引用,额外开销就是来之于对外部类的引用。
对对象的引用,其实就是一个机器地址,一个机器地址所需内存一般是8字节(64位架构的计算机,32为架构的是4字节)
4、构造函数
- 构造函数会在new的时候被调用,它快于赋值给引用它的变量的赋值过程。
- 构造函数不会有返回类型。
- 构造函数不会被继承。
- 构造函数只有在你没有写任何构造函数的时候,才会给你创建一个没有参数的构造函数。
- 如果你写了有参数的构造函数,如果你还想要有一个没有参数的构造函数,就必须自己写出来,编译器不会给你创建。
4.1、继承与构造函数
- 一个子类占用的内存大小除了自身的对象开销+所有实例变量的大小以外,还要加上父类的内存。
- 当你在new一个子类的时候,第一件事会调用父类的构造函数,而且一定会调用(因为完整的对象也需要完整的父类,你想要调用父类的所有方法和状态,就必须构造父类)。
- 在构造函数中,通过super()函数来调用父类的构造函数。
- 抽象的类也有构造函数,虽然你不能new一个抽象类,但是当你在new它的子类的时候会调用抽象类的构造函数。
4.2、如果我们没调用super()函数会怎样?
编译器会帮我们加上super()的调用。添加的方式会有两种:
1、如果你没有写构造函数,它会默认会给你添加一个无参的构造函数,并添加上super()函数。
2、如果你有构造函数,那么它会对每个重载版本的构造函数上添加上super()函数。
注意:编译器添加的一定是没有参数的构造函数和super()函数,假如父类有多个版本的构造函数,那么只有无参的版本会被调用。
对于super()的调用必须是构造函数的第一条语句。
public class Test_Constructor {
static class Animal{
public String name;
public final int age=1;
public Animal() {
System.out.println("你好");
}
}
static class Dog extends Animal{
public Dog() {
System.out.println("汪汪");
}
public Dog(String name) {
System.out.println("汪汪");
}
}
public static void main(String[] args) {
Dog a=new Dog();
Dog b=new Dog("小花");
}
}
//输出结果
你好
汪汪
你好
汪汪
4.3、this()函数
- this()函数是从某个构造函数调用同一个类的另外一个构造函数,只需要改变this()的参数就可以调用;
- this()函数只能在构造函数中,且只能是第一条语句;
- super()与this()不能兼得,只能出现一个。
5、对象的存活
public class Test_Object {
static class Person {
String name; // 实例变量的寿命与Person对象相同,如果该对象存活,该变量也会存活
}
public void showAge(int a) {
int age = 23; // 局部变量只会存活在该方法调用的时候,当该方法执行完毕,那么该变量消失
showName();
}
/**
* 1、当showAge方法在调用ShowName方法的时候,showAge方法以及它的变量age都是存活的,只是不处于栈顶;
* 2、这个时候的age变量是无法被使用的,只有在栈顶的showName函数中的name变量可以被使用;
* 3、当这两个函数都执行完成之后,showAge函数和它的变量才会消失。
*/
public void showName() {
String name = "zhangSan";
System.out.println("name");
}
public static void main(String[] args) {
/**
* Perosn对象的生命周期取决于它的引用变量p,只要p一直引用着Person对象,那么该对象就会一直存活。
*/
Person p=new Person();
}
}
6、释放对象的3种方式
public class Test_Clear_Object {
class Person {
String name;
}
/**
* 方式一:将对象的引用变量放在方法中,这样在方法执行完成之后,p1引用变量就会消失,
* 这样对Person对象的引用就没有了,该对象就可以被GC回收了。
*/
public void clear1(){
Person p1=new Person();
}
/**
* 方式二:引用被赋值到其他对象上,这样new的第一个Person对象就没有了引用,可以被回收。
*/
public void clear2(){
Person p2=new Person();
p2=new Person();
}
/**
* 方式三:直接将引用置空,这样Person对象就没被引用了。
*/
public void clear3(){
Person p3=new Person();
p3=null;
}
}