分为:加载-》连接(验证、准备、解析)-》初始化-》使用-》卸载
加载:
就像<深入理解java虚拟机>一书说的一样,千万不要混淆“类加载”与“类加载过程”。
进行一个类的加载及初始化 两种方法:继承ClassLoader并重写findClass因为类加载是通过双亲委派方式,重写findClass可以在父加载器无法找到需要加载的类时可以通过类本身查找,这时候就会通过getData找到Class文件并读取二进制流进行加载了、forname()
类加载分两步:
(1)根据类的全限定名(包名.类名)取出class文件中的二进制字节流.
(2)将二进制字节流中的静态数据转为运行时数据结构。
(3)在堆中生产一个class对象,用来访问方法区中存储的类变量,以及类信息。
注意:
1、这块就涉及到了jvm内存分配(堆、栈、本地方法栈、虚拟机栈、方法区)jdk1.8就没有了方法区取而代之的是一个叫元数据的东西。堆存储【对象、数组】、栈存储【局部变量、引用】、方法区存储【类变量、类信息、静态变量、常量】
2、反射中的newInstance方法必须等待类加载完成以后才可以初始化。
3、加载两种方式:Class.forname与ApploadClass.loadClass() (这两种区别在于Class.foraname(xx)加载完默认会自动初始化 、还有一个区别为.loadClass可以加载磁盘任何位置的class文件而forname只能加载项目路径下的,具体可以看文章最下方)如下图:
4、静态代码是与class绑定的,class只会被加载一次,所以静态代码也只会初始化一次。
true的意思就是加载完直接进行初始化。
连接:
连接内部分为(验证、准备、解析)
验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成(比如int类型为0,long类型为0L)
解析:将符号引用解析为直接引用。
初始化:
到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。在这个阶段,JVM 会根据语句执行顺序对类对象进行初始化,一般来说当 JVM 遇到下面 5 种情况的时候会触发初始化:
1、遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2、使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3、当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5、当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
下面一句话很重要!!!!!!!
(1)在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化
(2)类初始化阶段是执行类构造器()方法的过程。类初始化方法。编译器会按照其出现顺序,【收集类变量的赋值语句、静态代码块,最终组成类初始化方法】。类初始化方法一般在类初始化的时候执行。
(3)对象初始化方法。编译器会按照其出现顺序,【收集成员变量的赋值语句、普通代码块,最后收集构造函数的代码】,最终组成对象初始化方法。对象初始化方法一般在实例化类对象的时候执行
为了更好理解上面的意思请看下面的面试题:
class Grandpa
{
static
{
System.out.println("爷爷在静态代码块");
}
public Grandpa() {
System.out.println("我是爷爷~");
}
}
class Father extends Grandpa
{
static int a = 2;
static
{
System.out.println("爸爸在静态代码块");
}
public Father()
{
System.out.println("我是爸爸~");
}
}
class Son extends Father
{
static
{
System.out.println("儿子在静态代码块");
}
public Son()
{
System.out.println(a);
System.out.println("我是儿子~");
}
}
public class InitializationDemo
{
public static void main(String[] args)
{
System.out.println(Son.a);
//new Son(); //入口
}
}
各位可以看看上面会输出什么数据:
爷爷在静态代码块
爸爸在静态代码块
2
分析一下:首先上面是调用的静态变量(Son.a)这一点就只会加载Father类(如果子类调用父类静态变量,只会加载父类,这时候会初始化Father,然后发现Grandpa也没有初始化则会继续初始化Grandpa,这时属于类初始化 所以按照上面说的 只会收集静态)所以会执行 Grandpa静态代码块–》Father静态代码块–》打印a的值
下面再加大深度:
class Grandpa
{
static
{
System.out.println("爷爷在静态代码块");
}
{
System.out.println("我是Grandpa普通代码块");
}
public Grandpa() {
System.out.println("我是爷爷~");
}
}
class Father extends Grandpa
{
static int a = 2;
static
{
System.out.println("爸爸在静态代码块");
}
{
System.out.println("我是Father普通代码块");
}
public Father()
{
System.out.println("我是爸爸~");
}
}
class Son extends Father
{
static
{
System.out.println("儿子在静态代码块");
}
{
System.out.println("我是Son普通代码块");
}
public Son()
{
System.out.println(a);
System.out.println("我是儿子~");
}
}
public class InitializationDemo
{
public static void main(String[] args)
{
new Son(); //入口
}
}
结果
爷爷在静态代码块
爸爸在静态代码块
儿子在静态代码块
我是Grandpa普通代码块
我是爷爷~
我是Father普通代码块
我是爸爸~
我是Son普通代码块
2
我是儿子~
解析一下:这个和上面的区别就是 new Son(); 这是进行对象的实例化,这时候就会先执行类的加载与初始化(不明白的可以看上面的解释说明),这时候就需要按照顺序来 类的初始化(类初始化构造器)–》对象实例化(对象实例化构造器) 根据上面的解释 执行 Grandpa静态代码块–》Father静态代码块–》Son静态代码块–》Grandpa普通代码块–》Grandpa构造方法–》Father普通代码块–》Father构造方法–》Son普通代码块–》Son构造方法。
使用:
package com.hansight.atom.system.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class TestShicz extends ClassLoader{
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class shicz = null;
byte[] classData = getData();
shicz = defineClass(name,classData,0,classData.length);
return shicz;
}
private byte[] getData() {
File file = new File("D://test.class");
if (file.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int size = 0;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return out.toByteArray();
}else{
return null;
}
}
}
package com.hansight.atom.system.util;
import java.lang.reflect.Method;
public class Ceshi {
public static void main(String[] args) throws Exception{
TestShicz testShicz = new TestShicz();
Class<?> Shicz = testShicz.loadClass("com.hansight.atom.system.Test");
System.out.println("类加载器是:" + Shicz.getClassLoader());
//利用反射获取main方法
Method method = Shicz.getDeclaredMethod("sy", String.class);
Object object = Shicz.newInstance();
String name = "123";
method.invoke(object, (Object) name);
}
}
下面为需要加载的类 可以进行javac 编译后将Class文件放在D盘
package com.hansight.atom.system;
public class Test {
static {
System.out.println("22222222222222222222");
}
public void sy(String name){
System.out.println("++++++++++++:"+name);
}
}
然后执行Ceshi 的main方法 可以看到控制台打印了加载器为TestShicz
类加载器是:com.hansight.atom.system.util.TestShicz@2d38eb89
22222222222222222222
++++++++++++:123