什么叫类?类在什么时候进行加载?类加载过程发生了什么?或者叫jvm做了哪些工作?
- 类的概念(.java/.class的区别)和范围扩充
- java语言和Java虚拟机的概念
java可以运行在java虚拟机上,Groovy,JRuby,Jython,Scala,Fantom等语言也可以运行在Java虚拟机上,但是这些语言的文件类型并不是【.java】,其中包含【.rb】【.groovy】等,可见本质上,java虚拟机和【.java】文件并没有联系,运行的是【.class】文件。Java虚拟机实质上运行的是【.class】文件。 - 【.java】文件和【.class】文件
语言无关性
【.java】文件通过【javac】命令(Java语言的编译器)后就会变成【.class】文件。
同样【.rb】文件通过jrubyc编译器后会生成【.class】文件,【.groovy】文件通过groovyc编译器后会生成【.class】文件。最终生成的【.class】文件才是java虚拟机运行的实质。
- java语言和Java虚拟机的概念
- 类加载的实质
类加载的本质是将字节码文件通过类加载器加载到内存中 - 类加载的时机(有且仅有的五种情况必须对类进行初始化)
- ①遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。
(上代码说明)正常的【new】关键字,肯定要加载类。
- ②使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- ①遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或者设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)时、以及调用一个类的静态方法的时候。
package ClassLoader;
public class ClassLoaderMethod {
public static void main(String[] args) throws ClassNotFoundException {
//使用Class.forName()来加载类,默认会执行初始化块
Class.forName("ClassLoader.Test"); //因为我的包名为ClassLoader
//所以为ClassLoader.Test,正常情况下【包.类】
}
}
class Test {
private Test() {
System.out.println("私有构造方法模块");
}
static{
System.out.println("静态初始化块执行了!");
}
}
输出:
静态初始化块执行了!
通过反射【Class.forName】加载类,会初始化此类【例:Test】
- ③当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
package ClassLoader;
class parent{
public static int parent=1;
static{
System.out.println("parent static块");
}
}
class child extends parent{
static{
System.out.println("child static块");
}
}
public class MainLoader {
public static int i=1;
static{
System.out.println("MainLoader static i:"+i);
System.out.println("MainLoader static 块");
}
public static void main(String[] args) {
System.out.println(child.parent);
}
}
输出:
MainLoader static i:1
MainLoader static 块
parent static块
1
当我们调用子类的常量parent时,可以看到父类已经初始化了,但是子类child并没有初始化,因为其静态代码块并没有执行。
- ④当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类。
package ClassLoader;
public class MainLoader {
public static int i=1;
public static final int j=1;
static{
System.out.println("static i:"+i+"j:"+j);
i=2;
System.out.println("静态代码块");
}
public static void main(String[] args) {
}
}
输出:
static i:1j:1
静态代码块
main方法中什么都没有做,但是static静态代码块中已经执行。可见包含main的类,已经加载。
- ⑤当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
- 类加载的五大步骤
static是为了初始化,构造方法是为了构造对象,实例化对象,final会在准备阶段赋值
- ①加载(Loading)
加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由JVM自行定义。然后在内存中实例化一个java.lang.Class对象,这个对象将作为程序访问方法区中这些类型数据的外部接口。此对象属于Class类的实例【包含实际写的类信息】。(对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是存放在方法区里面) - ②验证(Verification)
验证的目的是防止一些高手,可以直接修改.class文件,导致运行文件的时候虚拟机出现故障。 - ③准备(Preparation)
static final修饰的变量会直接赋值
例如
public static int i=1;
public static final int j=1;
这两种形式,在准备的时候【i=0;j=1】;在初始化的时候执行【putstatic】指令后:【i=1;j=1】。 - ④解析(Resolution)【可例外】
②③④统称连接(Linking) - ⑤初始化(Initialzation)
参考上一条【2. 类加载的时机(有且仅有的五种情况必须对类进行初始化)】
以下两点算作是类加载的后续部分 - ⑥使用(Using)(对应上一篇文章new等)
- ⑦卸载(Unloading)(对应上一篇文章的垃圾回收)
- ①加载(Loading)
- 由谁来加载类?(三种系统ClassLoader)
- 类加载的五大步骤
package ClassLoader;
public class LoaderParent {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
LoaderParent instance= new LoaderParent();
Class c = instance.getClass();
ClassLoader loader = c.getClassLoader();
System.out.println(loader) ;
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
//AppClassLoader>ExtClassLoader
//BootstrapLoader为空。
//原因是Bootstrap Loader(启动类加载器)是用C语言实现的,
//找不到一个确定的返回父Loader的方式,于是就返回null。
}
}
输出:
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
直接【new LoaderParent】得到实例,【getClass()】方法,
得到java.lang.Class类信息对象,
然后得到是哪一个类加载器加载的类,通过打印得到
是【sun.misc.Launcher$AppClassLoader@73d16e93】,
所以这个类信息是AppClassLoader加载的。
- 参考文献
9个杀手级JVM编程语言 [五大基于JVM的脚本语言][5] 《深入理解Java虚拟机》周志明
热门评论
“Class对象比较特殊,它虽然是对象,但是存放在方法区里”这句话有误导,Class对象还是存在堆区中的。
说的好 听说你开始了
都说反射会导致执行效率的降低,但究竟是哪个环节降低了呢?