对于JVM里面的内存模型,可以用这个图来表示:(图片来源-JVM内存模型)
程序计数器:在计算机组成结构这门课里面,我们就学到CPU里面有个PC寄存器,这个寄存器主要指CPU当前运行的指令。
在这里,其实也是一样的,对于每一个线程,都有一个PCR,用来记录程序在当前线程执行的位置(准确的说是在Java代码中是:虚拟机字节码指令的地址;在native方法里面PC就是Undefined的)。当线程阻塞后然后再重新运行,就可以在PC记录的位置继续执行了。
线程之间的PC互不影响,所以称为线程私有的。同时,PC是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
虚拟机栈(VM Stack):线程私有,生命周期与线程一样。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。栈顶为当前执行的方法。
主要解释下局部变量表:主要存放各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)。我们笼统的把Java内存分为Heap,Stack,这里就是Stack咯。
方法区(Method Area)线程共享的。主要存放类型信息,包括
很多时候,对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。即使是HotSpot虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”至Native Memory来实现方法区的规划了。
本地方法栈(Native Method Stacks),就是native方法咯。
堆(Heap),线程共享的。可以参加我的另一篇博客
首先看一下图(图片来源)
可以看到Java的Heap分为了2个部分(其实Permanent Generation常常被人称为方法区),下面一一介绍:
//所有的类型信息都会在MethodArea里面进行存储
public class A
{
int a = 0; //局部变量表里面
static int b = 9;//Method Area的静态变量里面
int sayHello(int word)//局部变量表里面
{
}
}
同时应该注意StackOverFlow与OutofMemory的区别
String
这个很容易搞混淆咯。先看代码
首先应该了解一些基础知识:
String a = "AAA"
,首先检查常量池中有没有"AAA"
对象,有的话,返回其引用,没有的话创建一个然后返回引用。所以会创建0个或者1个对象String b = new String("AAA");
首先检查常量池中有没有"AAA"
对象,没有的话就创建一个,然后每一次都在堆内存里面也创建一个"AAA"
对象,返回堆内存的对象。。所以会创建2个或者1个对象String
一旦确立的,就是不可改变的。而常量池是在编译期间确立的存放在.class文件里面。 String
的构造函数:public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
当把”AAA”传到构造器里面的时候,会先去常量池检查或者创建对象,然后再去放回堆内存里面的对象。
下面看几个例子熟悉一哈其他的属性:
//String a = "AAA","AAA"对象在常量池咯
//String b = new String("AAA"); "AAA"在堆内存里面
//所以a的地址和b不一样啊
@Test
public void test1() {
String a = "AAA";
String b = new String("AAA");
System.out.println(a == b); // false
}
//a = "DDD";a指向了"DDD",抛弃了"AAA"
@Test
public void test2() {
String a = "AAA";
String b = "AAA";
System.out.println(a == b); // true
a = "DDD";
System.out.println(a == b); // false
System.out.println(a); // DDD
System.out.println(b); // DDD
}
//因为1会被转化为String类型的,而且是在编译阶段进行的(因为1已经确定了啊)
@Test
public void test3() {
String a = "AA1";
String b = "AA" + 1;
System.out.println(a == b); // true
}
// i 是变量,在编译阶段不能确定,在运行阶段再能确定,所有不行咯
@Test
public void test4() {
String a = "AA1";
int i = 1;
String b = "AA" + i;
System.out.println(a == b); // false
}
//final会让i在编译阶段确定
@Test
public void test5() {
String a = "AA1";
final int i = 1;
String b = "AA" + i;
System.out.println(a == b); // true
}
//intern()方法动态扩展常量池
@Test
public void test6() {
String a = "AAA";
String b = new String("AAA");
System.out.println(a == b.intern()); // true String#intern() 返回的是在常量池里面的地址
}
@Test
public void test7() {
String fre = "A";
String a = "AA" + fre; //并没有把"AAA"加入到常量池里面
String b = new String("AAA");
String d = "AAA";
//全为false
System.out.println(a == b); //a b 是2个不同的堆对象
System.out.println(a == b.intern()); // 返回的是在常量池里面的地址
System.out.println(a == d); //a指向堆中 d指向常量池
System.out.println(b == d);//b 指向堆中 d指向常量池
}
@Test
public void test8() {
final String fre = "A"; //fianl 使得fre在编译的期间是确定的
String a = "AA" + fre; //将"AAA"添加到了常量池
String b = new String("AAA");
System.out.println(a == b); //a 指向堆中 b指向常量池
System.out.println(a == b.intern()); // b.intern()返回的是在常量池里面的地址
}
//和String一样的
@Test
public void test() {
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b); //false
}
// 当基本类型包装类与基本类型值进行==运算时,包装类会自动拆箱。即比较的是基本类型值。
// 具体实现上,是调用了Integer.intValue()方法实现拆箱。
@Test
public void test1() {
int a = 1;
Integer b = 1;
Integer c = new Integer(1);
System.out.println(a == b); //true
System.out.println(a == c); //true
System.out.println(c == b); //false
}
对于这个我们反编译一哈可以看到这样
@Test
public void test1() {
byte a = 1;
Integer b = Integer.valueOf(1);
Integer c = new Integer(1);
System.out.println(a == b.intValue());
System.out.println(a == c.intValue());
System.out.println(c == b);
}
出现了Integer.valueOf(1)
这是啥?
Returns an {@code Integer} instance representing the specified {@code int} value,就是这个咯。
// Integer a = 1; 会调用这个 Integer a = Integer.valueOf(1);
// Integer已经默认创建了数值【-128-127】的Integer常量池
// 在之间的值就直接在里面取,所以test2可以,test3就不行了
@Test
public void test2() {
Integer a = 127;
Integer b = 127;
System.out.println(a == b); //true
}
@Test
public void test3() {
Integer a = 128;
Integer b = 128;
System.out.println(a == b); //false
}
// Java的数学计算是在内存栈里操作的
// c1 + c2 会进行拆箱,比较还是基本类型
@Test
public void test4() {
int a = 0;
Integer b1 = 1000;
Integer c1 = new Integer(1000);
Integer b2 = 0;
Integer c2 = new Integer(0);
System.out.println(b1 == b1 + b2); //true
System.out.println(c1 == c1 + c2); //true
System.out.println(b1 == b1 + a); //true
System.out.println(c1 == c1 + a); //true
}
上面的主要是自动装箱与拆箱,装箱主要是把int基本类型变为Integer等包装类型Integer.value()
,拆箱就是相反的咯(Integer.intValue()
)。