类的加载及初始化
一、类加载内存分析

类的加载过程:
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
·验证:确保加载的类信息符合JVM规范,没有安全方面的问题
·准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
·解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:
·执行类构造器()方法的过程。类构造器 ()方法是由编译期自动收集类中所有类变量的赋值动作和静态 ·代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。·虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步。
//类的加载
public class Test {
/**
* 1、加载到内存,会产生一个类对应class对象
* 2、链接,结束后给m附初始值
* 3、初始化
* <clinit>(){
* System.out.println("A类静态代码块初始化");
* m = 300;
* m =100;
* }
* m = 100
* a = 100
*/
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
}
}
class A {
static {
System.out.println("A类静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造初始化");
}
}
内存图分析
二、类的初始化
类的主动引用(一定会发生类的初始化)
·当虚拟机启动,先初始化main方法所在的类
·new一个类的对象
·调用类的静态成员(除了final常量)和静态方法
·使用java.lang.reflect包的方法对类进行反射调用
·当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
·当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
·通过数组定义类引用,不会触发此类的初始化
·引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
//测试类什么时候初始化
public class Test {
static {
System.out.println("main方法类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
/**
* 1、主动引用
* main方法类被加载
* 父类被加载了
* 子类被加载了
*
* Son son = new Son();
*/
//反射也会产生主动调用
//Class.forName("com.qf.test.Son");
//不会产生类的引用方法
/**
* 1、子类调用父类方法
* 结果:main方法类被加载
* 父类被加载了
* 2
* 子类并没有被初始化
*
* System.out.println(Son.b);
*/
/**
* 2、创建数组
* 结果:main方法类被加载
* 只有main类在虚拟机启动被加载,创建数组空间并不会加载类
*
* Son[] sons = new Son[5];
*/
/**
* 3、调用类里的常量
* 结果:main方法类被加载
* 1
* 常量在链接阶段就被初始化在常量池中
*/
System.out.println(Son.M);
}
}
class Father{
static int b = 2;
static {
System.out.println("父类被加载了");
}
}
class Son extends Father{
static {
System.out.println("子类被加载了");
m = 300;
}
static int m = 100;
static final int M = 1;
}
三、类加载器
类加载的作用:
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:
标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。
BootstrapClassLoader(启动类加载器):
用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取
ExtClassLoader (标准扩展类加载器):
负责jre/lib/ext目录下的jar包或-Djava.ext.dirs指定目录下的jar包装入工作库
AppClassLoader(系统类加载器):
负责java -classpath或-Djava.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
//类加载器 public class Test { public static void main(String[] args) throws ClassNotFoundException { //获取系统类的加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取系统类加载器的父类加载器-->扩展类加载器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@7eda2dbb //获取扩展类加载器的父类加载器-->根加载器(C/C++) ClassLoader parent1 = parent.getParent(); System.out.println(parent1);//null //测试当前类是哪个加载器 ClassLoader classLoader = Class.forName("com.qf.test.Test").getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //测试JDK内置的类是哪个加载器 ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader(); System.out.println(classLoader1);//null //如何获取系统类加载器可以加载的路径 System.out.println(System.getProperty("java.class.path")); /* F:\JAVA\jdk1.8.0_144\jre\lib\charsets.jar; F:\JAVA\jdk1.8.0_144\jre\lib\deploy.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\cldrdata.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\dnsns.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\jaccess.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\jfxrt.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\localedata.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\nashorn.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunec.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar; F:\JAVA\jdk1.8.0_144\jre\lib\ext\zipfs.jar; F:\JAVA\jdk1.8.0_144\jre\lib\javaws.jar; F:\JAVA\jdk1.8.0_144\jre\lib\jce.jar; F:\JAVA\jdk1.8.0_144\jre\lib\jfr.jar; F:\JAVA\jdk1.8.0_144\jre\lib\jfxswt.jar; F:\JAVA\jdk1.8.0_144\jre\lib\jsse.jar; F:\JAVA\jdk1.8.0_144\jre\lib\management-agent.jar; F:\JAVA\jdk1.8.0_144\jre\lib\plugin.jar; F:\JAVA\jdk1.8.0_144\jre\lib\resources.jar; F:\JAVA\jdk1.8.0_144\jre\lib\rt.jar; ............ */ } }
双亲委派机制
简介:
当某个类加载器需要加载某个
.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。作用:
1、防止重复加载同一个
.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。2、保证核心
.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。原码分析:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先检查这个classsh是否已经加载过了 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // c==null表示没有加载,如果有父类的加载器则让父类加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { //如果父类的加载器为空 则说明递归到bootStrapClassloader了 //bootStrapClassloader比较特殊无法通过get获取 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) {} if (c == null) { //如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

注:
双亲委派机制转自:https://www.jianshu.com/p/1e4011617650



浙公网安备 33010602011771号