关于JVM的堆栈的思考

 接上篇,梳理了jvm 的运行内存区。主要是对应了OOM的异常及GC有关。
有些问题还是需要强化对比:
1.堆、栈对比
在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理

堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;

栈的优势是存取速度比堆要快,缺点是存在栈中的数据大小与生存期必须是确定的无灵活性。

这里,在看网上有文章介绍“ 存在栈中的数据可以共享 ”。我不认同,他举得例子a=3;b=3;a==b。这个例子是赋值的是有局部变量表操作栈完成的 ,在字节码里,指令把值在局部变量表和操作数栈之间频繁移动, 跟a,b之前的共享没关系。
写个测试类:

public class IntTest {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int a=3;
int b=3;
a=4;
System.out.print(a);
}

}

编译后用javap -verbose  IntTest
关于JVM的堆栈的思考 - bohu83 - bohu83的博客
对比下代码,可以看出:
iconst_3// 将 3 入栈到操作数栈的顶端
istore_1   // 从操作数3栈顶端弹出并保存到局部变量 1
iconst_3 / 将 3 入栈到操作数栈的顶端
istore_2 // 从操作数3栈顶端弹出并保存到局部变量 2
iconst_4// 将 4 入栈到操作数栈的顶端
istore_1   // 从操作数栈3顶端弹出并保存到局部变量 1
 此时局部变量表数据为4,3 后面是读取打印的

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

面试题:String s = new String(“xyz”); 产生几个对象? 一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。

对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。

2.1Java自动拆箱和装箱:在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。           

2.2String是一个特殊的包装类数据。
String str = "abc"创建对象的过程 1 首先在常量池中查找是否存在内容为"abc"字符串对象 2 如果不存在则在常量池中创建"abc",并让str引用该对象 3 如果存在则直接让str引用该对象
java语言规范里要求完全相同的字符串字面量,应该包含同样的Unicode字符序列,并且必须是同样的字符串实例。 在Hotspot JVM里,字符串表维护了内部的字符串,它是一个哈希表结构,从对象指针映射到符号(例如Hashtable<oop, Symbol>),并且是维护在持久代里. 类被加载的时候,字符串的字面量由编译器自动的内部化,并且加入到符号表里。此外,通过调用String.intern()方法,String类的实例能够明确的被内部化。调用String.intern()方法时,如果这个符号表里已经包含了这个字符串,那么将返回指向它的引用,如果不包含,那这个字符串就会被加入到字符串表并且返回它的引用。

public static void main(String[] args) {
// TODO Auto-generated method stub
String str1 = "abc";
String str2 = new String("abc").intern();
System.out.println(str1==str2); //true,  intern 特性

String str3 = new String("abc");
System.out.println(str1==str3);//false,对象不同
String str4 ="ab"+"c";
System.out.println(str1==str4); //true,常量池存在
String str5 =str4+"";
System.out.println(str1==str5); //false,涉及到变量str4(不全是常量)的相加,所以会生成新的对象,其内部实现是先new一个StringBuilder,然后 append ,再tostring.

int a =3;
int b =3;
System.out.println(a==b);//true
int c = 300;
int d = 300;
System.out.println(c==d);//true
Integer e = 321;
Integer f = 321;
System.out.println(e==f);//false 超出缓存,新生成对象了
}

建议:

1当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

2.由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。 

说道优化这里,顺便说一下最好指定容器类大小。

3 对于ArrayList、HashMap等等,在扩容的时候都需要做ArrayCopy,对于系统压力很大,要尽量避免。

4.对于对象池,唯一合适的场景就是当池中的每个对象的创建开销很大时,缓存复用才有意义,例如每次new都会创建一个连接,或是依赖一次RPC。否则也没啥优化效果。

5.避免集中创建对象尤其是大对象


参考:
http://www.cnblogs.com/xiohao/p/4296088.html
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值