目录
图解1:使用无参构造器实例化一个类,并且不对该对象进行任何后续操作
图解2:使用有参构造器实例化一个类,并且不对该对象进行任何后续操作
图解3:使用有参构造器创建一个对象,并对该对象进行一些后续操作
在进行内存分析之前需要知道的三个概念
栈内存
- 栈是一种先进后出(或者说是后进先出)的结构,即最先进入栈的元素最后才能被访问
- 当线程进入一个java方法函数时,就会在当前线程的栈中压入一个栈帧,这个栈帧用来保存当前线程的状态,例如保存一些参数、局部变量、计算的中间过程、一些其他的数据
- 当退出函数方法时,通过修改栈的指针便可以将栈中的内容销毁
堆内存
- 首先需要说明的是,通常所说的“堆栈”并不是一个内容,而是两个内容,即“堆”和“栈”
- 堆存在的唯一目的就是存放对象实例
- 每一个java应用都唯一对应一个jvm实例,每一个jvm实例都对应一个堆,并且由堆内存被该java应用所有的线程所共享
- 关于进程和线程的关系,这边可以简单地认为“一个进程可以包含多个线程,而该进程的所有线程共享该进程的资源”,若要深入了解进程和线程,请学习操作系统
- 当初始化对象、非static类型的成员变量、字符串常量池、所有的对象实例、数组时,都需要在堆上进行内存单元的分配
方法区
- 方法区存放的是一些在整个程序中永远唯一的元素
- 例如一个类或者是static关键字定义的静态属性
一张图概括
图解1:使用无参构造器实例化一个类,并且不对该对象进行任何后续操作
现在有如下的代码段:
//class1.java
public class class1 {
int age;
String name;
public static void main(String[] args) {
class1 c1 = new class1();
}
}
这个代码段很简单
在class1.java文件中定义了一个名为class1的类,这一个类有两个成员变量一个静态的方法
接下来对这个代码段逐步进行内存分析
图解2:使用有参构造器实例化一个类,并且不对该对象进行任何后续操作
有如下代码段:
//class1.java
public class class1 {
int age;
String name;
public class1(int age, String name) {
this.age = age;
this.name = name;
}
public static void main(String[] args) {
class1 c1 = new class1(23, "张三");
}
}
这个代码段实现了如下的操作:
- 定义了一个类,名为class1
- class1中有两个成员变量,一个有参构造器,一个静态的程序入口(main方法)
- 在程序入口方法中,实例化了一个class1类,名为c1,并且调用了有参构造器进行初始化
下面进行内存分析:
图解3:使用有参构造器创建一个对象,并对该对象进行一些后续操作
有如下代码段:
//class1.java
public class class1 {
int age;
String name;
public class1(int age, String name) {
this.age = age;
this.name = name;
}
}
//class2.java
public class class2 {
public static void main(String[] args) {
int age = 24;
String name = "李四";
class1 c1 = new class1(age, name);
age = 25;
name = "王五";
createObject(c1)
}
public static void createObject(class1 c2) {
c2 = new class1(26, "王五");
}
}
这个代码段进行了如下的操作
- class1.java文件中有一个类,类名为class1,在这个类中有两个成员变量,有一个有参构造器
- class2.java文件中有一个类,类型为class2,在这个类中有一个静态的方法,一个程序的入口
- 在main函数中,有两个变量,一句使用有参构造器创建对象的语句,两句对变量的重新赋值语句
- 在createObject函数中,使用参数传递的方式创建了一个对象
下面进行内存分析:(过程有点长,耐心观看,并不难)
多说两句题外话,如果在上面的图解中难以理解为什么调用createObject函数的效果是创建了一个新的对象而不是在原来的对象上进行修改,则可以运行以下的代码进行验证:
//class1.java
public class class1 {
int age;
String name;
public class1(int age, String name) {
this.age = age;
this.name = name;
}
}
//class2.java
public class class2 {
public static void main(String[] args) {
int age = 24;
String name = "李四";
class1 c1 = new class1(24, "李四");
age = 25;
name = "王五";
System.out.println(c1);
System.out.println(createObject(c1));
}
public static class1 createObject(class1 c1) {
c1 = new class1(26, "王五");
return c1;
}
}
这边为了证明所有情况下都是一样的效果,形参和实参名都是c1,但是读者应该要很明晰,第一个打印语句中的c1是main函数中创建的c1对象的地址;第二个打印语句中的c1也是main函数中创建的c1对象的地址;在createObject函数的形参中,形参c1获得的是一个对象的地址,这一个地址是传入的实参c1的副本。
当然,通过上面这一段验证的代码也可以发现java在参数传递时,实际上是按值传递的,无论参数类型是值类型还是引用类型,实参传递给形参的都是一个数值(或者是实参地址的副本)。
总结
java内存分析,当然IDE也是内置了内存分析工具的,但是对于初学者而言,还是建议好好地学习一下不依赖分析工具进行内存分析的方法。
虽然如何不依赖分析工具进行内存分析是一段十分晦涩难懂的内容,但是对于初学者来说是十分必要的,多花一些时间好好地梳理梳理内存分析的流程,对更好地了解java以及深入学习java都有莫大的好处(当然对于其他语言也是如此,如何使用语言是十分简单的,但是理解为什么可以如此使用是有一定难度的),建议从最简单的代码入手,亲自进行几次内存分析以此来更好地理解这一块内容。