JVM

image-20230416081803876

分为3个部分:栈,堆,方法区。


方法区:类加载信息,常量,静态变量

:放对象的地方。在JVM启动时创建。这里对象有类属性但没有类里卖弄的哪些方法。

栈:放基本数据类型。每个线程一个栈,负责方法调用存储一些局部变量,方法参数返回值什么的。方法被调用的时候就有一个栈帧,这里面放说的那些数据。然后调用方法,栈帧入栈,结束调用就出栈。

无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。

只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。

因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。

什么是实例,什么是对象?

Object o = new Object();

左边的是实例,在栈中。

右边的是对象在堆中。

操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。

栈中的数据和堆中的数据销毁并不是同步的。

方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。

因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁。

而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。

每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,


类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套。

对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。


栈有一个很重要的特性:栈中的数据可以共享

当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

常量池技术

基础数据类型和基础数据类型的包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class test {
public static void main(String[] args) {
objPoolTest();
}

public static void objPoolTest() {
int i = 40;
int i0 = 40;
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
Double d1=1.0;
Double d2=1.0;

System.out.println("i=i0\t" + (i == i0));
System.out.println("i1=i2\t" + (i1 == i2));
System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));
System.out.println("i4=i5\t" + (i4 == i5));
System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));
System.out.println("d1=d2\t" + (d1==d2));

System.out.println();
}
}

i=i0 true
i1=i2 true
i1=i2+i3 true
i4=i5 false
i4=i5+i6 true
d1=d2 false

i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

****4.i****4和i5均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。

以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。

创建对象内存分配流程

  1. 方法区:加载类信息。
  2. 堆:初始化对象。(默认初始化,显式初始化,构造器初始化)
  3. 栈:把队中对象的地址赋给栈中变量名。