1、Java内存区域
(1)线程私有
虚拟机栈:主要是来描述java方法的内存模型。每个方法在执行时都会创建一个栈帧,用户存储局部变量表,操作数栈,动态链接,方法出口的信息。每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈:为虚拟机使用到的Native方法服务
程序计数器:当前线程所执行的字节码的行号指示器
(2)线程公有
堆:JVM所管理的内存中最大的一块。唯一目的就是存放实例对象。几乎所有对象实例都在这里分配。java堆是垃圾收集器管理的主要区域
方法区:用户存储已被虚拟机加载的类信息常量静态常量,即时编译器编译后的代码等数据
常量池:用户存放编译器生成的各种字面量和符号引用。


2、内存分配

java程序需要通过栈上的reference数据来操作堆上的对象

对象的访问定位:句柄访问、直接指针访问

(1)句柄访问

Java堆中会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对实例数据与类型数据的各自具体的地址信息

java new 连续内存分配 java内存如何分配_常量池


(2)直接指针访问

reference中存储的直接就是对象地址

java new 连续内存分配 java内存如何分配_java new 连续内存分配_02


方法区中的常量池
1、静态常量池:即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串字面量,还包含类、方法的信息,占用class文件绝大部分空间。
2、运行时常量池,则是jvm虚拟机在完成类装载后,将class问文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
例子如下:

String s1 = "Hello";
        String s2 = "Hello";
        String s3 = "Hel" + "lo";
        String s4 = "Hel" + new String("lo");
        String s5 = new String("Hello");
        String s6 = s5.intern();
        String s7 = "H";
        String s8 = "ello";
        String s9 = s7 + s8;

        System.out.println(s1 == s2);  // true
        System.out.println(s1 == s3);  // true
        System.out.println(s1 == s4);  // false
        System.out.println(s1 == s9);  // false
        System.out.println(s4 == s5);  // false
        System.out.println(s1 == s6);  // true

s1 == s2 : s1、s2在赋值时,均使用的字符串字面量。在编译期间,这种字面量会直接放入class文件中的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一内存地址,所以相等。
s1 == s3:s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮拼好,因此s3 = “Hel” + “lo” 最终被优化成s3 = “Hello”,所以相等。
s9 是由s7与s8两个变量拼接好的。s7与s8在方法区中,拼接后s9被分配到了堆内存中。所以s1与s9不等
s1 == s6是因为intern方法,s5在堆中,intern会将hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了hello字符串,所以intern方法直接返回地址。所以s1与s6相等

必须要关注编译期的行为,才能更好的理解常量池
运行时常量池中的常量,基本来源与class文件中的常量
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则JVM不会自动添加常量到常量池。