声明final关键字(常量池)
在编译阶段这个类的常量就会被存入到调用这个常量的方法所在的类的常量池当中。(调用MyParent2.str)static 模块不会调用到
package com.jvm.classloader;
/**
* 常量在编译阶段会存入到调用这个常量的方法所在的类的常量池中
* 本质上,调用的类并没有直接引用到定义常量的类,因此并不会触发定义常量类的初始化
*注意:这里指的是将常量存放到了MyTest2的常量池中,之后的MyTest2与MyParent2就没有任何关系了
* 甚至,我们可以将Myparent2的class文件删除
*
* 助记符:
* ldc:表示将int,float或是String类型的常量值从常量池中推送至栈顶
* bipush:表示将单字节(-128~127)的常量值推送至栈顶
* sipush:表示将短整型常量值(-32768~32767)推送至栈顶
* iconst_1:表示将int类型1推送至栈顶(iconst_m1 ~ iconst_5)
**/
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.i);
}
}
class MyParent2{
public static final String str = "hello world";
public static final short s = 7;
public static final int i = 128;
public static final int m = 1;
static{
System.out.println("MyParent2 static lock");
}
}
如果定义的常量在编译期间不知道其确定的值,则这个final对于的类会被初始化
package com.jvm.classloader;
import java.util.UUID;
/**
* 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中
* 这时c程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化
**/
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
public static final String str = UUID.randomUUID().toString();
static{
System.out.println("MyParent3 static code");
}
}
当创建一个数组实例的时候并不表示着对数组元素的主动使用。仅仅表示我创建了数组的实例而已。
这个实例是虚拟机在运行期间创建出来的。
package com.jvm.classloader;
/**
* 对于数组实例来说,其类型是由JVM在运行期间动态生成的,表示为class [Lcom.jvm.classloader.MyParent4;
* 这种形式。动态生成的类型其父类型就是Object
*
* 对于数组来说,JavaDoc经常将构成数组的元素为Compoment,实际上就是将数组降低一个维度后的类型。
*
* 助记符:
* anewrray:表示创建一个引用类型的(如类,接口,数组)数组,并将其压入栈顶
* newarray:表示创建一个指定的原始类型(如int,float,char 等)的数组,并将其引用值压入栈顶
**/
public class MyTest4 {
public static void main(String[] args) {
// MyParent4 myParent4 = new MyParent4();
MyParent4[] myParent4s = new MyParent4[1];
System.out.println(myParent4s.getClass());
MyParent4[][] myParent4s1 = new MyParent4[1][1];
System.out.println(myParent4s1.getClass());
System.out.println(myParent4s.getClass().getSuperclass());
System.out.println(myParent4s1.getClass().getSuperclass());
System.out.println("============================");
int[] ints = new int[1];
System.out.println(ints.getClass());
System.out.println(ints.getClass().getSuperclass());
char[] chars = new char[1];
System.out.println(chars.getClass());
Boolean[] booleans = new Boolean[1];
System.out.println(booleans.getClass());
Short[] shorts = new Short[1];
System.out.println(shorts.getClass());
byte[] bytes = new byte[1];
System.out.println(bytes.getClass());
}
}
class MyParent4{
static{
System.out.println("MyParent4 static block");
}
}
interface接口中定义的常量默认前缀为public static final
package com.jvm.classloader;
import java.util.Random;
/**
* 当一个接口在初始化时,并不要求其父接口都完成了初始化
* 只有在真正使用到父接口的时候(如引用接口中所定义的常量时)。才会被初始化
**/
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5{
public static Thread thread = new Thread(){
{
System.out.println("MyParent5 invoked");
}
};
}
//interface默认是public statuc final
class MyChild5 implements MyParent5{
public static int b = 5;
}
类的初始化步骤
一个类的初始化顺序是从上到下的
先进入准备阶段(赋默认的值),再执行初始化阶段
package com.jvm.classloader;
/**
* @Author 晨边#CB
* @Date:created in 2019/10/21 22:13
* @Version V1.0
**/
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("counter1:"+Singleton.counter1);
System.out.println("counter2:"+Singleton.counter2);
}
}
class Singleton{
public static int counter1;
public static int counter2 = 0;//位置在这的话结果为1,1
public static Singleton singleton = new Singleton();
private Singleton(){
counter1++;
counter2++;//准备阶段的重要意义
}
//把上面的counter2的位置声明放到这 结果会变成1,0
// public static int counter2 = 0;
public static Singleton getInstance(){
return singleton;
}
}
类加载顺序:(图)
类的加载
- 类的加载的最终产品是位于内存中从class对象
- Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口
有两种类型的类加载器(类加载器并不需要等到某个类被“首次主动使用”时再加载它)MyTest1()
- Java虚拟机自带的加载器
- 根类加载器(Bootstrap) 没有父加载器。负责加载虚拟机的核心类库
- 扩展类加载器 (Extension) 它的父加载器为根类加载器
- 系统(应用)类加载器 (System) 它的父加载器为扩展类加载器
- 用户自定义的类加载器
- java.lang.ClassLoader的子类
- 用户可以自己定制类的加载方式
类被加载之后,就进入到连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行环境中去。
类的验证
- 类文件的结构检查
- 语义检查
- 字节码验证
- 二进制兼容性验证
类的准备
类的初始化步骤:
- 假如这个类还没有被加载和连接,那就先进行加载和连接
- 假如类存在直接父类,并且这个父类还没被初始化,那就先初始化直接父类
- 假如类中存在初始化语句,那就依次执行这些初始化语句