Java 反射机制:(三)类的加载
一、类的加载过程
1、类加载三步曲
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。
2、类的加载
系统可能在第一次使用某个类时加载该类,但也可能采用预先加载机制来预加载某个类,不管怎样,类的加载必须由类加载器完成,类加载器通常由 JVM 提供,由 JVM 提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承 ClassLoader 基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
(1)从本地系统直接读取 .class 文件,这是绝大部分类的加载方法;
(2)从 zip,jar 等归档文件中加载 .class 文件,这种方式也很常见;
(3)通过网络下载 .class 文件或数据;
(4)从专有数据库中提取 .class 数据;
(5)将 Java 源文件数据上传到服务器中动态编译为 .class 数据,并执行加载。
但是,不管类的字节码内存从哪里加载,加载的结果都一样,这些字节码内容加载到内存后,都会将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口(即引用地址),所有需要访问和使用类数据只能通过这个 Class 对象。这个加载的过程需要类加载器参与。
小结:类的加载过程就是为了将字节码文件读取到内存中。
3、类的链接
当类被加载之后,系统为之生成一个对应的 Class 对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到 JVM 的运行状态之中。类的连接又可以分为如下三个阶段:
(1)验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方法的问题;
(2)准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配,给非 final 的赋默认值,如果是 final的,直接赋常量值;
(3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
小结:加载和链接后,在方法区中已经有一个能够代表当前类的 Class 对象。
4、类的初始化
(1)执行 类构造器<clinit>() 方法的过程。类构造器<clinit>() 方法是由编译期自动收集类中所有类变量的显示赋值动作和静态代码块中的语句合并产生的。(①静态变量的显式赋值;②静态代码块的内容的组成)
(类构造器是构造类信息的,不是构造该类对象的构造器)
(2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发父类的初始化;
(3)虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确加锁和同步,即每一个类在内存中都只有唯一的一个 Class对象;
类的初始化主要就是对静态的类变量进行初始化
二、什么时候会发生类初始化?
1、类的主动引用(一定会发生类的初始化)
(1)当虚拟机启动, 先初始化main方法所在的类; (2)new一个类的对象; (3)调用类的静态成员(除了final常量) 和静态方法; (4)使用 java.lang.reflect 包的方法对类进行反射调用; (5)当初始化一个类, 如果其父类没有被初始化, 则先会初始化它的父类;
2、类的被动引用(不会发生类的初始化)
(1)当访问一个静态域时, 只有真正声明这个域的类才会被初始化; (2)当通过子类引用父类的静态变量, 不会导致子类初始化; (3)通过数组定义类引用, 不会触发此类的初始化; (4)引用常量不会触发此类的初始化( 常量在链接阶段就存入调用类的常量池中了);
3、案例
声明两个类:
1 class Father {2 static int b = 2;3 static {4 System.out.println("父类被加载");5 }6 }7 8 class A extends Father {9 static {
10 System.out.println("子类被加载");
11 m = 300;
12 }
13
14 static int m = 100;
15 static final int M = 1;
16 }
测试:
1 public class ClassLoadingTest {2 public static void main(String[] args) {3 // 主动引用:一定会导致A和Father的初始化4 // A a = new A();5 // System.out.println(A.m);6 // Class.forName("com.java2.A");7 8 9 // 被动引用
10 A[] array = new A[5];//不会导致A和Father的初始化
11 // System.out.println(A.b);//只会初始化Father
12 //System.out.println(A.M); 不会导致A和Father的初始化
13 }
14
15 static {
16 System.out.println("main所在的类");
17 }
18 }
三、类的初始化
1、类的初始化
1 class Base{2 private static int a = getNum();3 static{4 ++a;5 System.out.println("(2)a = " + a);6 }7 static{8 ++a;9 System.out.println("(3)a = " + a);
10 }
11 public static int getNum(){
12 System.out.println("(1)a = " + a);
13 return 1;
14 }
15 }
16
17 class TestClinit extends Base{
18 private static int b = getNum();
19 static{
20 ++b;
21 System.out.println("(5)b = " + b);
22 }
23 static{
24 ++b;
25 System.out.println("(6)b = " + b);
26 }
27 public static int getNum(){
28 System.out.println("(4)b = " + b);
29 return 1;
30 }
31 public static void main(String[] args) {
32
33 }
34 }
运行结果:
(1)a = 0
(2)a = 2
(3)a = 3
(4)b = 0
(5)b = 2
(6)b = 3
注意:虽然类的加载大多数时候和类的初始化是一气呵成的,但其实类的加载不一定就会触发类的初始化。
2、类加载时会发生类的初始化情况
Java 程序首次通过下面5中方式来使用某个类时,系统就会初始化该类:
(1)当虚拟机启动,先初始化 main 方法所在的类
Demo:
1 //当虚拟机启动,先初始化main方法所在的类
2 public class A {
3 static{
4 System.out.println("init...A");
5 }
6 public static void main(String[] args) {
7
8 }
9 }
(2)new 一个类的对象
Demo:
1 //new一个类的对象2 class B{3 static{4 System.out.println("init...B");5 }6 }7 public class TestB{8 public static void main(String[] args) {9 new B();
10 }
11 }
(3)调用该类的静态变量(final的常量除外)和静态方法
Demo:
1 //调用该类的静态变量(final的常量除外)和静态方法2 class C{3 static{4 System.out.println("init...C");5 }6 public static void test(){7 }8 }9 public class TestC {
10 public static void main(String[] args) {
11 C.test();
12 }
13 }
14
15 //调用该类的静态变量(final的常量除外)和静态方法
16 class C{
17 public static int num = 10;
18 static{
19 System.out.println("init...C");
20 }
21 }
22 public class TestC {
23 public static void main(String[] args) {
24 System.out.println(C.num);
25 }
26 }
(4)使用 java.lang.reflect 包的方法对类进行反射调用
Demo:
1 //使用java.lang.reflect包的方法对类进行反射调用2 class D{3 static{4 System.out.println("init...D");5 }6 }7 public class TestD {8 public static void main(String[] args) throws ClassNotFoundException {9 ClassLoader cl = ClassLoader.getSystemClassLoader();
10 cl.loadClass("com.ks.loader.D");//该句不会造成类初始化,只是加载类
11 System.out.println("类加载已完成...");
12 Class.forName("com.ks.loader.D");//会导致类初始化
13 }
14 }
(5)当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
Demo:
1 //当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类2 class EBase{3 static{4 System.out.println("父类初始化");5 }6 }7 public class TestE extends EBase {8 static{9 System.out.println("子类初始化");
10 }
11 public static void main(String[] args) {
12 }
13 }
(6)类的初始化
1 public class ClassLoadingTest {2 public static void main(String[] args) {3 System.out.println(A.m);4 }5 }6 class A {7 static {8 m = 300;9 }
10 static int m = 100;
11 }
第一步:类的加载
第二步:链接结束后 m = 0
第三步:初始化后,m 的值由 <clinit> 方法执行决定
这个 A的类构造器 <clinit>() 方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于:
<clinit>() {
m = 300;
m = 100;
}
3、类加载时不会发生类的初始化
(1)引用静态常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
Demo:
1 //引用静态常量不会触发此类的初始化2 class NBase{3 public static final int MAX_VALUE = 100;4 static{5 System.out.println("父类初始化");6 }7 }8 class NSub extends NBase{9 static{
10 System.out.println("子类初始化");
11 }
12 }
13 public class TestNoInitialize {
14
15 public static void main(String[] args) {
16 System.out.println(NSub.MAX_VALUE);
17 System.out.println(NBase.MAX_VALUE);
18 }
19 }
(2)当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化;
Demo:
1 //当访问一个静态域时,只有真正声明这个域的类才会被初始化2 // 当通过子类引用父类的静态变量,不会导致子类初始化3 class NBase{4 public static int num = 10;5 static{6 System.out.println("父类初始化");7 }8 }9 class NSub extends NBase{
10 static{
11 System.out.println("子类初始化");
12 }
13 }
14 public class TestNoInitialize {
15
16 public static void main(String[] args) {
17 System.out.println(NSub.num);
18 }
19 }
(3)通过数组定义类引用,不会触发此类的初始化。
Demo:
1 //通过数组定义类引用,不会触发此类的初始化
2 NSub[] arr = new NSub[5];
Java 反射机制:(三)类的加载相关推荐
- 利用Java反射机制调用类的私有方法
利用Java反射机制调用类的私有方法 引言 来吧·展示 参考链接 引言 如何调用其他类的私有方法呢? 可以利用Java的反射机制,去调用其他类的私有方法 来吧·展示 package cn.learn. ...
- java 内部类 加载_举例讲解Java的内部类与类的加载器
内部类 class A { //Inner1 要在 A 初始化后 才能使用,即要被A的对象所调用 class Inner1 { int k = 0; // static int j = 0; //A加 ...
- java类加载过程_java类的加载过程
在这本书里面,在讲到类初始化的五种情况时,提及了一个比较有趣的事情.先来看看下面的代码 public class SubClass { static{ System.err.println(" ...
- Java新手入门200例119之Java反射机制得到类的包名和类名
文章目录 作者简介 引言 导航 热门专栏推荐 创建一个类Person 方案1 方案2 方案3 小结 导航 热门专栏推荐 作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通J ...
- JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制
一 类加载器与类加载过程 类加载子系统作用 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识. ClassLoader只负责class文件的加载,至于 ...
- (转)个例子让你了解Java反射机制
个例子让你了解Java反射机制 原文地址:http://blog.csdn.net/ljphhj/article/details/12858767 JAVA反射机制: 通俗地说,反射机制就是可以把 ...
- 一个例子让你了解Java反射机制
本文来自:blog.csdn.net/ljphhj JAVA反射机制: 通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运 ...
- Java - 反射机制
Java反射机制 Java反射机制概述 理解Class类并获取Class实例 类的加载与ClassLoader的理解 创建运行时类的对象 获取运行时类的完整结构 调用运行时类的指定结构 反射的应用:动 ...
- Java反射机制笔记一
Java反射机制 15-1 Java反射机制概述 15-2 理解Class类并获取Class的实例 反射例程 15-3 类的加载与ClassLoader的理解 类的加载器例程 15-4 创建运行时类的 ...
- JVM源码阅读-Dalvik类的加载
前言 本文主要研究Android dalvik虚拟机加载类的流程和机制.目的是了解Android中DEX文件结构,虚拟机如何从DEX文件中加载一个Java Class,以及到最终如何初始化这个类直至可 ...
最新文章
- 2017年最受欢迎的10个编程挑战网站
- sap系统操作流程财务软件_金蝶财务软件的操作流程汇总
- js入门·表单详解一(修改表单属性,修改表单元素值)
- clientWidth、clientHeight、offsetWidth、offsetHeight以及scrollWidth、scrollHeight
- Mysql update 使用join更新字段
- 求解最大公因子(JAVA辗转相除法)、python的最大公因子,最小公倍数
- pyqt的listwidget 支持键盘搜索_键盘测评丨Ceke M87机械键盘:更好的双模MAC系统支持?...
- 七日Python之路--第八天(一些琐碎)
- 如何用acme.sh申请证书
- 我的北京生活,2018面向新的开始
- 解决Unable to open debugger port错误
- 【一起读源码】1. Java 中元组 Tuple
- 【ubuntu】搭建lamp架构
- layui实现表格合并单元格,设置不同背景色
- 14 Tornado - XSRF
- 中望CAD的引线标注格式怎么改_没想到啊,原来CAD命令还可以这样学习
- 模仿猫眼电影静态网页展示
- 时间字符串解析(格式问题如2020-04-25T07:00:00+00:00)
- 什么是 Web安全?
- freemark数值如何保留两位小数的同时,使用进一法