1.虚拟机内部情况

虚拟机规范定义的运行时数据区域:
image.png
jdk1.8后实际的虚拟机运行时数据区域:
image.png

jdk1.8后的hotspot实现的虚拟机运行时数据区域和规范的几个不同点:

  • 将虚拟机栈和本地方法栈合二为一;
  • 元数据区取代方法区,并且不在虚拟机内存中,而是在本地内存中;
  • 运行时常量池由方法区移到了堆中;

2.虚拟机栈

image.png
虚拟机栈为线程私有的区域,由栈帧组成。每执行一个方法都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接和方法返回地址等信息。每一个方法从被调用到执行完成的过程就是一个栈帧入栈和出栈的过程。如果虚拟机栈的大小固定,那么当请求的栈深度超过虚拟机栈的深度的时候就会抛出StackOverflowError异常(常见与递归调用)。如果虚拟机栈可以无限扩展的时候,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

3.本地方法栈

本地方法栈和虚拟机栈十分类似,唯一的不同就是虚拟机栈存储的是java方法,而本地方法栈存储的是Native方法。jdk1.8后本地方法栈和虚拟机栈已经合二为一。

4.程序计数器

线程私有的区域。记录当前线程执行的地址,因为在并发的情况下,每个线程都是片段执行的,因此需要一个区域记录当前线程执行到的地址,方便重新调用的时候能够快速恢复执行。

5.堆

堆是线程共享的区域。用来存储实例化对象。基本上所有实例化对象和数组都存储在堆上,也是垃圾回收机制作用的主要区域。

6.方法区

用来存储已被虚拟机加载的类信息,常量,静态变量等数据。方法区又称为“永久代”因为对于方法区的垃圾回收条件非常苛刻,往往不会进行回收。在jdk1.8中去掉了方法区的概念,将其中的运行时常量池移到了堆中,其他部分改为Metaspace(元数据区)存储在直接内存中。

7.运行时常量池

用于存放字面量,符号引用和直接引用。首先看以下的代码:

public class Main{
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        System.out.println(str1 == str2); // true
        System.out.println(str1 == str3); // false
        System.out.println(str2 == str3); // false
    }
}

image.png

如果以上面代码中的str1和str2的创建方式,虚拟机会先检查运行时常量池中是否又“hello”对象,显然在str1创建的时候是不存在“hello”对象的,因此会在运行时常量池创建一个“hello”对象,当str2赋值的时候先检查常量池中是否有,发现有,则不进行创建,而是直接使str2指向存在的str1,因此str1 == str2 是true的。
而str3使用new关键字创建,则会在堆中先创建一个str3对象,此时str3是指向堆中的这个对象的而不是指向常量池中。因此str3和str1,str2比较都是false。而创建完对象之后再去检查常量池中是否有“hello”对象,有的话再使堆中的对象指向常量池。如果new创建的对象字符串值在运行时常量池中并没有,则不会再在常量池中对变量值进行维护。

public class Main {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "helloworld";
        String str3 = str1 + "world";
        String str4 = "hello" + "world";
        System.out.println(str2 == str3); // false
        System.out.println(str2 == str4); // true
        System.out.println(str3 == str4); // false
    }
}

image.png

这上面的例子,首先str1和str2显然是指向常量池的两个不同的对象。即现在常量池中有“hello”和“helloworld”两个对象。创建str3的时候因为包含了str1,虚拟机无法判断str3是否是常量,因此与new方法类似,会在堆中创建一个“helloworld”,并在检查后发现常量池中也有一个“helloworld”,因此再有堆中的对象指向常量池,但记住此时的str3指向的依然是堆中的对象而非常量池,因此str2==str3结果是false。而str4是以显式的常量进行创建的,因此在检查过后也是直接指向常量池中的“helloworld”,故str2==str4是true。str3==str4也是显而易见了。


超人不会飞
12 声望4 粉丝

一个想去做开发的研究生