JVM系列2 类加载

参考:
JVM类加载机制:https://www.jianshu.com/p/a9d8c1a37b8c
JVM类加载机制及类加载器:https://www.jianshu.com/p/f997fa5d1ce9
浅谈双亲委派模型:https://www.jianshu.com/p/353c26c744df

一.JVM类加载机制

1.类加载的定义
  • 把类型数据从Class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM使用的Java类型
  • Java中类型的加载、连接和初始化过程在程序运行期间完成,这将会令类加载时稍微增加一些性能开销,但也为Java提供了高度灵活性,实现了Java的动态扩展特性
2.类的生命周期
类的生命周期
  • 加载 - 连接(验证-准备-解析) - 初始化 - 使用 - 卸载
  • 加载/验证/准备/初始化的顺序固定,但解析可能在初始化后开始,以支持Java的运行时绑定
  • 此处的加载仅是类加载的一个步骤,不是类加载
3.类加载的条件
  • 当且仅当类的5种主动引用场景,JVM触发类加载
    1)创建:使用new关键字实例化对象,和读取(getstatic)或设置(putstatic)一个类的静态字段及调用(invokestatic)一个类的静态方法时
    2)反射:使用java.lang.reflect包的方法对类进行反射调用时,如果类没有进行过初始化,会先触发类的初始化
    3)继承:初始化一个类时,若该类的父类还未初始化,则触发父类的初始化
    4)执行主类JVM启动时,先初始化用户指定的执行主类(包含mian()方法的类)
    5)动态语言(jdk1.7):如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
  • 以下3种被动引用场景,不会触发JVM类加载
    1)子类引用父类的静态字段,不会导致子类初始化
public class SuperClass {  
    static{  
        System.out.println("SuperClass init!");  
    }  
    public static int value = 123;  
}  
public class SubClass extends SuperClass {  
    static{  
        System.out.println("SubClass init!");  
    }  
}  
public class NotInitialization {  
    public static void main(String[] args) {  
        System.out.println(SubClass.value);  
    }  
} 
//输出
SuperClass init!  
123 

2)通过数组定义引用类,不会触发此类的初始化

public class SuperClass {  
    static{  
        System.out.println("SuperClass init!");  
    }  
    public static int value = 123;  
}  
public class NotInitialization {  
    public static void main(String[] args) {  
        SuperClass[] scs = new SuperClass[10];  
    }  
}
//不会执行SuperClass类中static块里的System.out.println("SuperClass init!");  
输出结果为空

3)常量在编译阶段会存入调用类的常量池,不会触发定义该常量的类的初始化

public class ConstClass {
    static{
        System.out.println("ConstClass init!");
    }
    public static final String HELLOWORLD = "hello world";
}
public class NotInitialization {
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLOWORLD);
    }
}
hello world

上述代码在编译阶段经常量传播优化,已将ConstClass类的HELLOWORLD常量值存入NotInitialization类的常量池。以后NotInitialization对于常量ConstClass.HELLOWORLD的引用都被转化为NotInitialization类对自身常量池的引用了。实际上NotInitialization的Class文件之中已经不存在ConstClass类的符号引用入口了。

4.类加载过程
  • 加载
    1)通过类的全限定名获取定义此类的二进制字节流
    2)将这个字节流所代表的静态存储结构转换为方法区内的运行时数据结构
    3)在内存中生成一个代表此类的java.lang.Class对象,作为该类在方法区的各种数据的访问入口(HotSpot虚拟机特殊,将Class对象存于方法区)
  • 验证
    1)文件格式验证:字节流是否符合Class文件的格式规范,是否能被当前版本的虚拟机处理
    2)元数据验证:对字节码的描述信息进行语义分析,保证其描述信息符合Java语言规范。如这个类是否有父类?这个类的父类是否继承了不允许继承的final类?如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法等
    3)字节码验证:通过分析数据流和控制流,确保程序语义合法且符合逻辑
    4)符号引用验证:发生在解析阶段
  • 准备
    1)为且仅为类变量在方法区分配内存并设置初始值
    2)通常情况类变量初始值为零值,若类变量的字段属性表存在ConstantValue属性,则将被初始化为ConstantValue属性指定的值
  • 解析
    1)JVM将常量池内的符号引用替换为直接引用的过程
    2)符号引用以一组符号描述所引用的目标,直接引用是直接指向目标的指针(或间接定位到目标的句柄)
  • 初始化
    1)对类变量进行赋值执行静态代码块
    2)本质是执行类构造器clinit方法,该方法是有编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并而来
    3)继承中,父类的clinit方法先于子类的clinit方法执行,意味着父类的静态变量赋值和静态语句块先于子类执行。如下例输出结果是2而不是1
public class Parent {  
    public static int A = 1;  
    static{  
        A = 2;  
    }  
}    
public class Sub extends Parent{  
    public static int B = A;  
}   
public class Test {  
    public static void main(String[] args) {  
        System.out.println(Sub.B);  
    }  
} 
父类子类加载顺序

二.类加载器

  • 分为两大类(启动类其他类),三小类(其他类加载又分为扩展类加载器和应用程序类加载器)
1.启动类加载器(Bootstrap ClassLoader)
  • 由C++语言实现(HotSpot虚拟机)
  • 加载Java核心类到内存。包括<JAVA_HOME>\lib目录或-Xbootclasspath指定的路径中的类库
2.其他类加载器
  • 由Java语言实现,继承自抽象类ClassLoader
  • 分为扩展类加载器(Extension ClassLoader)和应用程序加载器(Application ClassLoader)
    1)扩展类加载器负责加载Java扩展的核心类之外的类,包括<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库
    2)应用程序类加载器负责加载用户类路径(classpath)上指定的类库,是默认的类加载器
3.双亲委派模型
  • 双亲委派要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器
  • 双亲委派使任意类都通过加载它的类加载器和该类本身一同确立其在虚拟机的唯一性
  • 同时,类随它的类加载器一起具备了一种带有优先级的层次关系
  • 双亲委派工作过程
    1)类加载器收到类的加载请求时,首先会把请求为派给父类加载器,而非自己去加载
    2)每个层级的类加载器都是这样,所有的加载请求最终都将传送到顶层的启动类加载器
    3)仅当父加载器反馈自己无法完成该加载请求时,子加载器才会尝试自行加载
双亲委派加载机制

三.常见面试题

1.可不可以自己写一个String类?
  • 不可以,根据双亲委派机制,会去加载父类加载器,父类发现存在冲突的String类时,将停止加载
2.能否在加载类时,对类的字节码进行修改?
  • 可以,使用Java探针技术

常恐秋风早,飘零君不知

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容