ps: 这篇博客是乱写的,笔记的形式,后面有空再整理。
1.加载:生成Class对象,不放在堆中,存放在方法区中的元空间。
2.准备:正式为类变量(static),分配内存,并设置默认初始值(数据类型的0值,比如,int 为 0);
3.至此,类加载结束。但初始化还是要看时机的。
- 实例化的时候 new
- 调用其中的静态字段或者静态方法。
- 反射调用的时候。
4.什么时候被动引用,不引发初始化呢?
子类调用父类静态字段的时候,只初始化父类,不初始化子类。静态字段被调用,只会影响它的当前类。
通过数组定义的时候,不会触发此类的初始化
StringTest[] strs=new StringTest[10];被final修饰的static字段,由于一开始被编译器优化,存储到NotInitiallization常量池中。
5.初始化发生什么呢?
为所有类变量赋值,并执行静态代码快。是通过编译过程中<clinit>收集的类变量顺序来执行的。
public class StringLoader {
private static StringLoader stringLoader=new StringLoader();
private static int a;
private static int b=0;
private StringLoader(){
a++;
b++;
}
public static StringLoader getStringLoader(){
return stringLoader;
}
public static void main(String[] args) {
/* 调用静态方法,类开始初始化,先执行private static StringLoader stringLoader=new StringLoader(); 因此 此时 a=1 b=1 继续初始化类变量,因为a没有初始值,那么不再给他赋值。 它就是1了,而b赋值,所以b=0; */
StringLoader.getStringLoader();
System.out.println(a +" :" + b);
}
}
这段代码的执行结果是1 : 0;
6.实例化不是初始化,实例化指的是,创建对象。根据方法区中的类信息,在对内存中创建类对象,先把实例变量初始化,然后调用构造函数,调用父类构造函数,再调用子类构造函数。
7.java的引用并不是地址,和指针是两个概念。引用存储的是地址的值,可以有多个引用。而所谓的引用传递,就是复制一个引用。值传递就是复制一个值。复制到方法中,也就是栈中。不知道那本书上写的什么,基本类型在栈中是共享的,int a=1;int b=1;a 和 b 指向同一个地址。也是服了,基本类型不管是成员变量还是局部变量,不管存在栈中,还是存在堆中。都是会开辟出一个新的内存空间出来的。例如 int 是4个字节的,那么int a=1 ,就是开辟4个字节的空间存放1。而int 的包装类型 Integer 实现了常量池技术,如果Integer a=1; Integer b=1;
注意 : “==” 对于基本类型比较的是值是否相等。 但对于引用类型比较的就是存储的地址是否相同。
/*基本类型的包装类型中 Float 和 Double 没有实现常量池技术*/
public class IntDemo {
private Integer a=3;
private Integer a1=3;
public static void main(String[] args){
IntDemo intDemo=new IntDemo();
intDemo.test();
}
public void test(){
Float a=3f;
Float a1=3f;
System.out.println(a==a1); //false
System.out.println(this.a==this.a1); //true
}
}
8.java规范的方法区是:存放已经被虚拟机加载的类信息、常量、静态变量、以及编译后的代码。
在jdk7 之前 这些东西都是放在 永久代中的。
jdk7开始了移除永久代计划,把常量(String的字符串信息)和 静态变量放入到堆中。类信息还保留在永久代中。
9.同时jdk7 开始了 一项 支持动态语言的更新。
java虚拟机的字节码指令集的数量增加了一个invokedynamic
java 通常在编译其间,就已经把方法比如 print 的完整的符号引用生成出来了。已经确定这个方法的所属类型值了。
而动态语言的一个很重要的特征就是:变量无类型而变量值才有类型。动态语言一般在运行时才确定类型。
为什么java虚拟机对动态类型语言不支持?
java7之前的字节码指令集中 ,指令的第一个参数都是被调用的方法的符号引用。而方法的符号引用只有在编译时产生放在 .class文件中。jdk7 提供了一个新的动态确定目标方法的机制, MethodHandle
MethodHandle 操作方法,把方法作为参数传给别的方法。这类似于c++函数指针的方法。
MethodHandle 类似于 Reflection,但反射是在模拟java代码层次的方法调用。而MethodHandle是模拟字节码层次的方法调用。 MethodHandle 本质就是 invokedynamic
10.在jdk8 之后,完全完成了永久代的移除计划。常量和静态变量的去处仍旧不变。类信息全部放到了元空间中。 元空间是和本地内存相关的。默认上限大小是本地内存,可以用 -XX:MetaspaceSize=2M 来限制大小。
元空间中把类加载后的信息,分别存放在各自的类加载器空间中,也就是说每个类加载器都有自己独立的空间,且GC不再单独回收某个类,而是在类加载器中已经没有相关引用的时候,回收整个类加载器空间。
11.java8 中的常量池和 String.intern() 方法的改变。
针对常量的常量池技术,也发生了一些改变。常量池不仅仅可以放字符串,同时也可以放字符串的相应的引用。这是一个非常好的处理方式。
例如:
public class Test{
public static void main(String[] args){
String str1=new String("zzzzk");
/*java7之后,字符串常量池中可以放引用了,因此采用 str1.intern() 后回去常量池中找是否有这个字符串, 如果有,返回常量池中的字符串引用。 如果没有,把堆中字符串常量的引用放到常量池中去。 反正,常量池已经移到堆中去了。 */
String a=str1.intern();
System.out.print(a == str1);
}
}
来分析一下这段代码:
编译过后,Test.class 文件中,常量池中有 zzzzk 这个常量。
当 Test.class 加载后,把zzzk放到字符串池(堆中一个StringTable,类似于HashMap集合)中。
在运行过程中:
- 执行到new String(“zzzzk”); //堆中生成一个String对象。
- 运行到str1.intern(); //去字符串池中找是否有”zzzzk”这个字符串对象,一检查有了。那么返回字符串池中的”zzzzk” 对象的引用给a
因此,运行结果是false.
再来看一段代码:
public class Test{
public static void main(String[] args){
String str1=new String("zzzz")+new String("k");
String a=str1.intern();
System.out.print(a == str1);
String s="zzzzk";
}
}
按照前面的分析 由于编译期间“zzzzk”已经被放在常量池中去了。那么明显这里应该也输出false。
但结果是输出的是 true. 这说明编译时并没有把“zzzzk”放到常量池中去,而是在 运行到 String s=”zzzzk” 时才把“zzzzk”放到常量池中去。
然而用 javap -verbose Test 得到字节码查看class文件中的常量池中,却是有“zzzzk”
Constant pool:
#1 = Methodref #16.#38 // java/lang/Object."<init>":()V
#2 = String #39 // zzzzk
#3 = Fieldref #15.#40 // StringDemo.s:Ljava/lang/String;
#4 = Class #41 // java/lang/StringBuilder
#5 = Methodref #4.#38 // java/lang/StringBuilder."<init>":()V
#6 = Class #42 // java/lang/String
#7 = String #43 // zzzz
很多资料说的并没有错,确实在编译阶段,就把“zzzzk”放到class文件中的常量池中去了。但是,class文件中的常量池和运行时常量池是不一样的。
再来看这段代码
public class StringDemo {
static String s="zzzzk"; //创建了一个对象放到了
public static void main(String[] args) {
String str1=new String("zzzz")+new String("k");
String a=str1.intern();
System.out.print(a == str1); //输出结果false
}
}
通过上面一段代码,可以确认只有在运行到String s=”zzzzk” 的时候,才会把编译收集到的常量信息,放到运行时常量池中去。
(具体细节,没有找到资料描述-。-)