java 内存模型:
JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,
非堆内存(Non-heap Memory)是在
JVM堆之外的内存
堆:对象的具体(属性 方法);
非堆区:
栈:方法执行时 在此操作,局部变量,堆中对象的地址;
静态数据区:
static修饰的数据区(假设 技术不能假设!!):如果经static修饰的数据 在static区,
经static修饰过的变量 比如成员变量
创建一个对象时,这个成员变量不会在堆里再有这个变量了,这就是static 变量的
作用;它是多对象共享的;
代码:
/* * 记住:静态数据区不属于堆,当然更不属于对象 Static : static修饰的,在编译期就有了,其早于对象的建立;属于类; 在静态方法区内,不能调用其他方法(因为其他方法属于对象),也不能用this,super,但可以使用静态变量 反之:在其他方法区内可以调用静态方法 */ public class Sta { private static String name = "duck"; public int age = 21; static int hh = 99; public void say() { System.out.println("我 是一只可爱的鸭子"); } public String getName() { return name; } public static void action() { name = "my name is not duck"; System.out.println("我是静态方法"); } }
package com.stati; /* * * Out.In in = new Out().new In()可以用来生成内部类的对象,这种方法存在两个小知识点需要注意 1.开头的Out是为了标明需要生成的内部类对象在哪个外部类当中 2.必须先有外部类的对象才能生成内部类的对象,因为内部类的作用就是为了访问外部类中的成员变量 */ public class Test { //private int h = 7; public static int a; int b; public static void main(String[] args) { a = 3; // b = 4; //这是错误的,其属于对象成员变量,static修饰的在方法块,早于对象的建立; // TODO Auto-generated method stub /* * Java关键字this只能用于方法方法体内。当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。 * 因此,this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this, * 这在“Java关键字static、final使用总结”一文中给出了明确解释。并且this只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this * Static 修饰方法:不属于对象,属于类,用类名直接调用 */ Sta.action(); Sta sta = new Sta(); System.out.println("我的名字是:" + sta.getName()); Sta s1 = new Sta(); s1.hh = 88; Sta s2 = new Sta(); System.out.println(s2.hh); } }
测试结果:
static修饰方法:我是静态方法 我的名字是:my name is not duck 88
static代码块:/*静态方法可以直接通过类名调用,任何的实例也都可以调用, 因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法), 只能访问所属类的静态成员变量和成员方法。*/ /* 原因 :jvm 会在 没有执行之前 首先对 static的 变量 代码块 函数 进行处理 所以 可以如此 * //静态的方法 变量 代码块 在静态去 在所有的对象创建之前 就存在 因此 用类名引用*/ public static void StaticWay(){ //System.out.println(age); //错误 //action(); //错误 System.out.println(a); // a是静态成员变量 所以在此可以 System.out.println("我是静态方法"); StaticWay2(); // static 是静态方法所以 在此可以 Animal.StaticWay2(); //通过类名 也可以 }
static与final:/*static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内, * JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次*/ static { int ui = 88; //不是成员变量 System.out.println("我是static区1 "); System.out.println(ui); }
static final作用 : 在一块公共的内存 且只读;static final用来修饰成员变量和成员方法,
可简单理解为“全局常量”!对于变量,表示一旦给值就不可修改,并且通过类名可以访问 对于方法,
表示不可覆盖,并且可以通过类名直接访问。
常量区(只读): final修饰的;
final修饰成员变量:final int W_FINAL = 9;//常亮 大写下划线
修饰作用:只读,在常量区,在编译期解决;
final修饰方法: 对于方法,表示不可覆盖,并且可以通过类名直接访问;
final修饰类:表示该类不可以被继承;
java缓存池(相对于封装int与Integer):
装箱:把基本类型用它们相应的引用类型包装起来,使其具有对象的性质。int包装成Integer、
float包装成Float;
拆箱:和装箱相反,将引用类型的对象简化成值类型的数据;
Integer a = 100; 这是自动装箱 (编译器调用的是static Integer valueOf(int i))即:
等同Integer a = Integer.valueOf(100)
int b = new Integer(100); 这是自动拆箱![]()
jvm会编译期就在堆里创建缓存池数值【-128,127】;
Integer i = 12; //会在堆里找12这个数值,找到就把缓存池中该值得地址返回给i;
Integer j= 12; //i的地址与j的地址相同;
//超出范围System.out.println(u==j); false
Integer u = 128;
Integer j = 128;
Integer与Integer间的比较,从jdk1.5开始,有“自动装箱”这么一个机制,在byte-128到127范围内
(ps整型的八位二进制的表示的范围为-128到127),如果存在了一个值,再创建相同值的时候就不会重
新创建,而是引用原来那个,但是超过byte范围还是会新建的对象:代码如下
可以看到对于范围在-128到127的整数,valueOf方法做了特殊处理。/* * 返回一个表示指定的 int 值的 Integer 实例。如果不需要新的 Integer 实例,则 * 通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过 * 缓存经常请求的值而显著提高空间和时间性能。 * @param i an <code>int</code> value. * @return a <tt>Integer</tt> instance representing <tt>i</tt>. * @since 1.5 */ public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i);
采用IntegerCache.cache[i + offset]这个方法。
从名字,我们可以猜出这是某种缓存机制。
进一步跟踪IntegerCache这个类,此类代码如下:这就是valueOf方法真正的优化方法,当-128=<i<=127的时候,返回的是IntegerCache中的数组的值;当 i>127 或 i<-128 时,返回的是Integer类对象/* * IntegerCache内部类 * 其中cache[]数组用于存放从-128到127一共256个整数 */ private static class IntegerCache { private IntegerCache(){} static final Integer cache[] = new Integer[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Integer(i - 128); } }
例子:
package com.third_day; public class Wrapper { public static void main(String[] args) { String s1 = "123"; int i1 = Integer.parseInt(s1); System.out.println(i1); Integer i = 12; int ii = 12; System.out.println(i==ii); Integer a = new Integer(100); //在堆中创建了一个对象 Integer b = 100; //等同Integer。valueOf(100); 即从return IntegerCache.cache[i + offset]; 如果zai /* 堆里找到100 就把 堆里100值得地址 给栈里的b*/ int bb = 100; System.out.println("m11 result " + (a == b)); //false System.out.println("m11 result " + (bb == b)); Integer c = new Integer(101); Integer d = new Integer(101); System.out.println("m21 result " + (c == d)); Integer k = new Integer(101); Integer e = Integer.valueOf(101); //相当于Integer f = 101; Integer f = 101; System.out.println("m41 result " + (e == f)); System.out.println("m41 result " + (k == f)); System.out.println("m41 result " + (k == e)); /*注意::基本数据类型和对象比较的时候,对象会自动拆箱为基本数据类型再比较,比较的就是里面的值而不是地址*/ Integer i11 = new Integer(120); int i2 = 120; Integer i22 = 120; // newInteger与int11: Integer i11 = new Integer(120);会自动拆箱(即:堆里的内容 拿到栈里面)变成 i11=120; 就比较值了 而不是地址了 System.out.println("---newInteger与int11---"+(i11==i2)); //比较的是地址 一个指向缓存池(池范围:-128--127) 也是在堆里的 跟 常量池不同 ,一个指向 新建的堆里的内容 System.out.println("---newInteger与int---"+(i11==i22)); //比较内容 i22 会转换 int i22 = 120; System.out.println("---newInteger与int---"+(i2==i22)); } }
java常量池(拘留池):
String对象的初始化
由于String对象特别常用,所以在对String对象进行初始化时,Java提供了一种简化的特殊语法,格式如下:
String s = “abc”;
s = “Java语言”;
其实按照面向对象的标准语法,其格式应该为:
String s = new String(“abc”);
s = new String(“Java语言”);
只是按照面向对象的标准语法,在内存使用上存在比较大的浪费。例如String s = new String(“abc”);实际上
创建了两个String对象,一个是”abc”对象,存储在常量空间中,一个是使用new关键字为对
象s申请的空间。其它的构造方法的参数,
可以参看String类的API文档。
String str1 = "abc"; 分析---》
公共语言运行库通过维护一个表来存放字符串,该表称为拘留池,它包含程序中以编程方式声明或创建
的每个唯一的字符串的一个引用。
因此,具有特定值的字符串的实例在系统中只有一个。
例如,如果将同一字符串分配给几个变量,
运行库就会从拘留池中检索对该字符串的相同引用,并将它分配给各个变量。
一个初始时为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,
如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),
则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用
(1) String str1 = "abc";
System.out.println(str1 == "abc");步骤:
1) 栈中开辟一块空间存放引用str1;
2) String池中开辟一块空间,存放String常量"abc";
3) 引用str1指向池中String常量"abc";
4) str1所指代的地址即常量"abc"所在地址,输出为true;(2) String str2 = new String("abc");
System.out.println(str2 == "abc");步骤:
1) 栈中开辟一块空间存放引用str2;
2) 堆中开辟一块空间存放一个新建的String对象"abc";
3) 引用str2指向堆中的新建的String对象"abc";
4) str2所指代的对象地址为堆中地址,而常量"abc"地址在池中,输出为false;
私自感觉不错的博客:@http://blog.csdn.net/u010644448/article/details/51980370
小结:注意java编译期(生成.class) 与执行期(jvm解释class 字节码)