Java基础-JVM内存划分

3,736 阅读5分钟

JVM内存划分

JVM内存划分

一、栈**

栈帧

  • 线程私有
  • 当线程执行一个方法时,就会随之创建一个对应的栈帧
  • 栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 栈中所存储的变量和引用都是局部的(即:定义在方法体中的变量或者引用),局部变量和引用都在栈中(包括final的局部变量)
  • 八种基本数据类型(byte、short、int、long、float、double、char、boolean)的局部变量(定义在方法体中的基本数据类型的变量)在栈中存储的是它们对应的值
  • 如果局部变量在声明的时候,没有显示赋值,栈不会给予默认赋值
  • 栈中还存储局部的对象的引用(定义在方法体中的引用类型的变量),对象的引用并不是对象本身,而是对象在堆中的地址,换句话说,局部的对象的引用所指对象在堆中的地址在存储在了栈中。当然,如果对象的引用没有指向具体的对象,对象的引用则是null。

二、堆**

  • 线程共享
  • 由关键字new产生的所有对象都存储于Java堆(Java Heap)
  • !!! 实例变量(非static修饰的成员变量)和对象关联在一起,所以实例变量也在堆中
  • java数组也在堆中开辟内存空间
  • Java的垃圾回收机制会自动进行处理

三、方法区**

method(方法区)又叫静态区,存放所有的①类(class),②静态变量(static变量),③静态方法,④常量和⑤成员方法。

1.又叫静态区,跟堆一样,被所有的线程共享。

2.方法区中存放的都是在整个程序中永远唯一的元素。这也是方法区被所有的线程共享的原因。

  • 存储常量:static final修饰的成员变量

  • 存储静态变量:static修饰的成员变量

    • 八种基本数据类型(byte、short、int、long、float、double、char、boolean)的静态变量会在方法区开辟空间,并将对应的值存储在方法方法区
    • 对于引用类型的静态变量如果未用new关键字为引用类型的静态变量分配对象(如:static Object obj;)那么对象的引用obj会存储在方法区中,并为其指定默认值null;若,对于引用类型的静态变量如果用new关键字为引用类型的静态变量分配对象(如:static Person person = new Person();),那么对象的引用person 会存储在方法区中,并且该对象在堆中的地址也会存储在方法区中(注意此时静态变量只存储了对象的堆地址,而对象本身仍在堆内存中)
  • 存储方法:静态方法、普通方法

  • !!!方法区中没有实例变量,这是因为,类加载先于对应类对象的产生,而实例变量是和对象关联在一起的,没有对象就不存在实例变量,类加载时没有对象,所以方法区中没有实例变量,实例变量存储在堆中

四、示例

public class  PersonDemo
{
    public static void main(String[] args) 
    {   //局部变量p和形参args都在main方法的栈帧中
        //new Person()对象在堆中分配空间
        Person p = new Person();
        //sum在栈中,new int[10]在堆中分配空间
        int[] sum = new int[10];
    }
}


class Person
{   //实例变量name和age在堆(Heap)中分配空间
    private String name;
    private int age;
    //类变量(引用类型)name1和"cn"都在方法区(Method Area)
    private static String name1 = "cn";
    //类变量(引用类型)name2在方法区(Method Area)
    //new String("cn")对象在堆(Heap)中分配空间
    private static String name2 = new String("cn");
    //num在堆中,new int[10]也在堆中
    private int[] num = new int[10];


    Person(String name,int age)
    {   
        //this及形参name、age在构造方法被调用时
        //会在构造方法的栈帧中开辟空间
        this.name = name;
        this.age = age;
    }

    //setName()方法在方法区中
    public void setName(String name)
    {
        this.name = name;
    }

    //speak()方法在方法区中
    public void speak()
    {
        System.out.println(this.name+"..."+this.age);
    }

    //showCountry()方法在方法区中
    public static void  showCountry()
    {
        System.out.println("country="+country);
    }
}

流程图

最后通过一个流程图,串起所有区域

// AppMain.java
public class AppMain {                         //运行时,JVM把AppMain的信息都放入方法区    

    public static void main(String[] args) { //main成员方法本身放入方法区。    
        Sample test1 = new  Sample( " 测试1 " );   //test1是引用,所以放到栈区里,Sample是自定义对象应该放到堆里面    
        Sample test2 = new  Sample( " 测试2 " );         
        test1.printName();    
        test2.printName();    
    }
    
} 

image:JVM运行流程图

JVM运行流程图

总结:

  • 方法区:保存类的模板
  • 堆:保存类的实例
  • 栈:进行函数计算

扩展

  • 永久代

HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内容,能够省去专门为方法区编写内存管理代码的工作。对于其他虚拟机(如BEA JRockit/ IBM J9)来说是不存在永久代的概念的。

  • 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本/字段/方法/接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期、运行期间生成的各种字面量和符号引用,这部分内容将类在加载后进入方法区的运行时常量池中存放。

String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println("string" == "str" + "ing");// true
System.out.println(str3 == str4);//false

String str5 = "string";
System.out.println(str3 == str5);//true

解释:

  • "abcd"是在常量池中拿对象,new String("abcd")是直接在堆内存空间创建一个新的对象。只要使用new方法,便需要创建新的对象。
  • 连接表达式 +,只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入常量池中。
  • 对于字符串变量的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,其属于在运行时创建的字符串,具有独立的内存地址,所以不引用自同一String对象。

参考: