Java堆、栈、方法区、常量池

前言:该文章为转载,觉得作者写的非常好,就想整理一下转发,想留给自己方便日后复习浏览

1、Java栈

Java在函数中定义的基本类型(int,long,short,byte,float,double,boolean,char)的变量(局部变量和函数的形参)的引用和数据,以及对象的引用都放在栈中存储。

1、栈的特点

1、存取速度快。仅次于CPU中的寄存器。

2、每个线程都会有一个栈空间,不同栈之间不能直接访问,所以线程之间不能共享栈中的数据。

3、存在栈中的数据是可以共享的。

比如我们定义int a=3;int b=3;a=4;
编译器先处理int a=3;首先会在栈中创建一个变量a的引用,然后在栈中查找有没有字面值为3的地址,如果有则将a指向这个地址;如果没有则开辟一块内存放字面值3,然后将a引用指向这个地址。
接着处理int b=3;,在栈中创建b的引用变量,由于在栈中字面值为3的地址已经存在,所以直接将b指向这个地址。这样a和b同时指向了字面值为3的这个地址。
接着处理a=4;,会在栈中查找字面值为4的地址,如没有就开辟内存存放字面值4,让a指向这个地址,如有就将a直接指向这个地址。
此时b依然等于3,不会等于4。这点和对象的引用不同,需要注意。

4、如果栈内存中没有足够的空间可以使用,JVM会抛出java.lang.StackOverFlowError异常。

5、栈中定义的变量,在超出变量的作用域后,Java会自动释放为变量所分配的内存空间,该内存空间可以立即被另作他用。

2、堆

堆中主要用于存放new出来的对象和数组。下面举例看下对象的实例化过程。

Person a=new Person ("123")Person b=new Person ("123")

编译器会先执行Person a=new Person ("123");,会在堆中开辟内存存放创建的对象Person ("123"),在栈中创建变量a,将a指向对象的内存首地址,a就是该对象的引用。
接着执行Person b=new Person ("123");,虽然前面已经创建了对象Person ("123")``,但是只要使用关键字new,就会在堆中开辟一块新的内存存放创建的对象,在栈中创建变量b,将b指向新对象的首地址。所以a和b指向并不是同一个对象。
如果定义Person c = a;a.setName("234")那么String name=c.getName()就等于“234”
执行Person c = a;则变量a和c都会指向同一个对象,所以使用a变量修改对象中的内容时,c指向的对象的内容也会改变。

2.1、特点

1、存取速度比栈中的慢。

2、一个JVM只有一个堆内存,所以线程间是可以共享堆内存中的数据的。

3、如果堆中内存不足,则会抛出java.lang.OutOfMemoryError异常。

4、定义在栈中的变量a指向堆中的对象Person的首地址,在超出变量的作用域后,Java会自动释放a所分配的内存空间,此时就没有引用指向对象了,但是对象并不会马上被回收,需要等某个时间通过垃圾回收来回收内存。

3、常量池

常量池分为两种:静态常量池运行时常量池

静态常量池
静态常量池指的是在编译期确定,保存在class文件中的一些数据。常量池主要用于存放两大类常量:字面量(Literal)和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串、声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

1、类和接口的全限定名;2、字段的名称和描述符;3、方法的名称和描述符。

运行时常量池
在运行时常量池是方法区的一部分,在JDK1.7之后运行时常量池从方法区中移出,放在堆中。常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

3.1、字符串常量池

对于字符串,其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

String s1 = "china";
String s2 = "china";
String s3 = "china";

String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
s1==s2==s3; //true
ss1!=ss2!=ss3!=s1!=s2!=ss3; //true
ss1.eqauls(ss2)//true
ss1.eqauls(ss3)//true
ss1.eqauls(s1)//true
ss1.eqauls(s2)//true
ss1.eqauls(s3)//true

可以看出s1、s2、s3在编译期就被创建,并存入到了常量池中。编译器在执行

String s1 = "china";

时会先在常量池中查找是否存在字符串常量“china”,如果不存在就在常量池中new一个china字符串,存在就不new,然后让栈中的变量指向这个china字符串。因此常量池中只有一个china字符串对象,然后在执行

String s2 = "china";String s3 = "china";

时,会在常量池中找到china字符串,并让s2、s3指向它。

对于

String ss1 = new String("china");

在编译时并不会创建,在运行时,通过new产生一个字符串(假设为“china”)时,会先去常量池中查找是否已经存在字符串“china”,如果不存在则在常量池中创建一个“china”字符串对象,然后在堆中再创建一个常量池中的“china”对象的拷贝对象;如果常量池中存在,就直接在堆中创建一个常量池中此“china”对象的拷贝对象。

3.2、包装类实现了常量池技术

对于8种基本数据类型大部分都有自己的包装类,其中Byte,Short,Integer,Long,Character,Boolean都实现了常量池技术,;而Byte,Short,Integer,Long类型在装箱时会缓存了范围[-128,127]的数据到数组中,Character会缓存[0,127]范围的数据到数组中进行缓存。

1、对于Integer来说,范围是[-128,127]的数在自动装箱时全部被自动加入到了常量池里面,具体可查看Integer.valueof(int i)方法。Integer.valueOf()

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}



//IntegerCache类



private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
        VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
        try {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
            // If the property cannot be parsed into an int, ignore it.
        }
    }
    high = h;

   cache = new Integer[(high - low) + 1];
   int j = low;
    for(int k = 0; k < cache.length; k++)
        cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}


实例

public class test {
    public static void main(String[] args) {
        Integer i1=10;
        Integer i2=10;
        System.out.println(i1==i2);
                System.out.println(i1.equals(i2));
    }
}
输出:
true
true


2、使用new关键字创建Integer时,即使数据在范围[-128,127],也不会去缓存中查找,直接在堆中创建一个新的Integer对象。并不会像new String("123")可能需要在中或常量池中各创建一个对象。

Integer i1=new Integer(10);
Integer i2=new Integer(10);
Integer i3=10;
System.out.println(i1==i2); //false
System.out.println(i1.equals(i2)); //true
System.out.println(i1==i3); //false
System.out.println(i1.equals(i3)); //true


3、当整数不在[-128,127]范围内时,就会在中创建对象。
看下面例子在内存中的分配。

 public void test() {int a1 = 9; //自动拆箱 Integer.intValue()int b1 = 9; //自动拆箱 Integer.intValue()final int A2 = 9;final int B2 = 9;Integer a3 = new Integer(9);Integer b3 = new Integer(9);Integer a4 = 9; //自动装箱 调用Integer.valueOf(int)Integer b4 = 9; //自动装箱 调用Integer.valueOf(int)}


4、方法区

方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息常量静态变量即编译器编译后的代码等数据静态变量常量在方法区,所有方法,包括静态非静态的,也在方法区。

5、成员变量和局部变量在内存中的分配

局部变量的数据存在于内存中。栈内存中的局部变量随着方法的消失而消失。 成员变量存储在中的对象里面,由垃圾回收器负责回收。

class BirthDate {
    private int day;
    private int month;
    private int year;
 
    public BirthDate(int d, int m, int y) {
        day = d;
        month = m;
        year = y;
    }
    // 省略get,set方法………
}
 
public class Test {
    public static void main(String args[]) {
        int date = 9;
        Test test = new Test();
        test.change(date);
        BirthDate d1 = new BirthDate(7, 7, 1970);
    }
 
    public void change(int i) {
        i = 1234;
    }
}


对于以上这段代码,date局部变量i,d,m,y都是形参为局部变量day,month,year成员变量。下面分析一下代码执行时候的变化:

main方法开始执行:int date = 9; date局部变量基础类型引用和值都存在中。Test test = new Test();test为对象引用,存在中,对象(new Test())存在中。test.change(date); i为局部变量,引用和值存在中。当方法change执行完成后,i就会从中消失。BirthDate d1= new BirthDate(7,7,1970); d1为对象引用,存在中,对象(new BirthDate())存在中,其中d,m,y为局部变量存储在中,且它们的类型为基础类型,因此它们的数据也存储在中。day,month,year为成员变量,它们存储在中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从中消失。main方法执行完之后,date变量,test,d1引用将从中消失,new Test(), new BirthDate()等待垃圾回收.

  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值