Day Twenty-Four

类加载

类的加载过程

当一个程序主动使用某一个类的时候,如果说这个类还没有被加载到内存中,则系统会通过“类的加载”,“类的链接”,“类的初始化”这三步来对这个类进行初始化。

  1. 类的加载:在加载的时候就会把类的一个class文件读到内存里面,读的时候就会创建一个Class对象,这个就相当于在反射那里提到的“镜子”一样。这个过程是由类加载器完成的。
  2. 类的链接:这一步会将类里面的二进制数据合并到Java的运行环境中。
  3. 类的初始化:上面把数据合并到Java运行环境里,那Java虚拟机就开始对这些数据进行初始化。

类的加载与ClassLoader的理解

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:
    • 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中的所有变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
public class Test04 {public static void main(String[] args) {A a = new A();System.out.println(A.m);/*1.加载到内存,会产生一个类对应的Class对象2.链接,链接结束后 m = 0 这个是默认值3.初始化<clinit>(){System.out.println("A类静态代码块初始化");//合并之后一样的代码就被替换了。m = 300;m = 100;}m = 100;*/}
}class A{static {System.out.println("A类静态代码块初始化");m = 300;}static int m = 100;public A() {System.out.println("A类的无参构造器初始化");}
}

结果:

通过这个结果我们发现这个m的值是100。

我们通过一张图来看看类是怎么加载的。

首先在方法区里面加载了一些类的基本数据,在加载类的数据的时候就已经在堆里面创建了每个类所对应的对象。然后栈里面的main()方法开始链接,给m赋了一个默认值0,当链接所有工作准备完,确定代码没有问题的时候,才开始执行代码。在main()方法里面第一句是A a = new A();在new A()的时候,在堆里面就产生了一个A类的对象(所有new出来的都是在堆里面的),这个对象就会去找它自己的那个Class类(无论创建几个A类对象,Class只有一个,这个Class代表A类的结构),然后再通过new出来的那个A类对象里面的东西,到类的结构里面去给它赋值。赋值完之后,通过类构造器<clinit>()方法去初始化,初始化成功后m就有了一个初始值,就打印出来了

什么时候会发生类初始化

  • 类的主动引用(一定会发生类的初始化)

    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果它的父类没有被初始化,则会先初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
//测试类什么时候会初始化
public class Test06 {static {System.out.println("main类被加载");}public static void main(String[] args) throws ClassNotFoundException {//1.主动引用 new一个类的对象/*main类被加载父类被加载子类被加载*///Son son = new Son();//2.反射调用/*main类被加载父类被加载子类被加载*///Class.forName("com.liuHuan.reflect.Son");//不会产生类的引用的方法//通过子类引用父类的静态变量/*main类被加载父类被加载2*///System.out.println(Son.b);//通过数组定义类引用,这里只是开辟了一个数组空间,sons是数组空间的名字/*main类被加载*///Son[] sons = new Son[5];//引用此类的常量/*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规范定义了如下类型的类的加载器。
public class Test07 {public static void main(String[] args) throws ClassNotFoundException {//获取系统类的加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader);//获取系统类加载器的父类加载器------>扩展类加载器ClassLoader parent = systemClassLoader.getParent();System.out.println(parent);//获取扩展类加载器的父类加载器------>根加载器//这里是获取不到,根加载器是用C/C++编写的ClassLoader parent1 = parent.getParent();System.out.println(parent1);//测试当前这个类是哪一个加载器加载的ClassLoader classLoader = Class.forName("com.liuHuan.reflect.Test07").getClassLoader();System.out.println(classLoader);//测试JDK内置的类是哪个加载器加载的classLoader = Class.forName("java.lang.String").getClassLoader();System.out.println(classLoader);//如何获得系统类加载器可以加载的路径System.out.println(System.getProperty("java.class.path"));/*** C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;* C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;* C:\Users\刘欢\IdeaProjects\Learn\out\production\Learn;* C:\Users\刘欢\IdeaProjects\Learn\src\com\lib\commons-io-2.11.0.jar;* D:\IntelliJ IDEA 2019.3.3\lib\idea_rt.jar*///双亲委派机制:一个检查机制//如果说自己写了一个包,就会在系统类里找,//没有重名的再往扩展类里面找,没有重名的就在根类下找,//如果发现有重名的,那自己写的这个就没有用了。}
}

获取运行时类的完整结构

  • 通过反射获取运行时类的完整结构:Field、Method、Constructor、Superclass、Interface、Annotation
  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field
  • 注解
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;public class Test08 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {Class c1 = Class.forName("com.liuHuan.reflect.Student");//获得类的名字System.out.println(c1.getName());//获得包名 + 类名System.out.println(c1.getSimpleName());//获得类名//获得类的属性System.out.println("====================");Field[] fields = c1.getFields();//只能找到public属性for (Field field : fields) {System.out.println(field);}fields = c1.getDeclaredFields();//可以找到全部的属性for (Field field : fields) {System.out.println(field);}//获得指定属性的值System.out.println("==========================");Field name = c1.getDeclaredField("name");System.out.println(name);//获得类的方法System.out.println("==========================");Method[] methods = c1.getMethods();//获得本类及其父类的所以public方法for (Method method : methods) {System.out.println(method);}methods = c1.getDeclaredMethods();//获得本类的所有方法for (Method method : methods) {System.out.println(method);}//获得指定方法//之所以后面要带上参数,是因为Java中有重载这个概念,//如果方法名一样,不加参数的话就无法判断是哪个方法了。//参数就是这个方法的类型。System.out.println("==========================");Method getName = c1.getMethod("getName", null);Method setName = c1.getMethod("setName", String.class);System.out.println(getName);System.out.println(setName);//获得指定的构造器Constructor[] constructors = c1.getConstructors();//获得public构造器for (Constructor constructor : constructors) {System.out.println("===" + constructor);}constructors = c1.getDeclaredConstructors();//获得本类的全部构造器for (Constructor constructor : constructors) {System.out.println("***" +  constructor);}Constructor declaredConstructor = c1.getDeclaredConstructor(int.class, String.class, int.class);System.out.println(declaredConstructor);}
}

动态创建对象执行方法

当我们有了一个Class对象的时候,获得了Class对象后,我们能干什么?当然不是亲亲抱抱举高高,此对象非彼对象。

  • 创建类的对象:调用Class对象的newInstance()方法

    • 类必须要有一个无参的构造器
    • 类的构造器的访问权限需要足够。
  • 如果没有无参构造器,我们在创建对象的时候需要明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
    1. 通过Class类的getDeclaredConstructor(Class…parameterTypes)取得本类的指定形参类型的构造器
    2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
    3. 通过Constructor实例化对象

调用指定的方法

  • 通过反射,调用类中的方法,通过Method类完成。

    1. 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    2. 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递设置的obj对象的参数信息。

    1. Object 对应原方法的返回值,若原方法无返回值,此时返回null
    2. 若原方法为静态方法,此时形参Object obj可为null
    3. 若原方法形参列表为空,则Object[] args为null
    4. 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;//通过反射动态创建对象
public class Test09 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {//创建Class对象Class c1 = Class.forName("com.liuHuan.reflect.Student");//构造一个对象Student student = (Student) c1.newInstance();//本质上用的是无参构造器System.out.println(student);//通过构造器创建对象Constructor constructor = c1.getDeclaredConstructor(int.class, String.class, int.class);Student student1 = (Student) constructor.newInstance(1, "liuhuan", 18);System.out.println(student1);//通过反射调用普通方法Student student2 = (Student) c1.newInstance();//通过反射获取一个方法Method setName = c1.getDeclaredMethod("setName", String.class);//invoke:激活的意思,用法(对象,"方法的值")setName.invoke(student2,"lh");System.out.println(student2.getName());//通过反射操作属性System.out.println("==========================");Student student3 = (Student) c1.newInstance();Field name = c1.getDeclaredField("name");//不能直接操作私有属性,我们需要关闭程序的安全检测。属性或方法的setAccessible(true);name.setAccessible(true);name.set(student3,"大威德");System.out.println(student3.getName());}
}

setAccessible

  • Method和Field、Constructor对象都有setAccessible()方法。
  • setAccessible作用是启动和禁用访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查。

性能对比分析

//分析性能问题
public class Test10 {//普通方式调用public static void test01(){Student student = new Student();long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000000; i++) {student.getName();}long endTime = System.currentTimeMillis();System.out.println("普通方式调用" + (endTime - startTime) + "ms");}//反射方式调用public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Student student = new Student();Class c1 = student.getClass();Method getName = c1.getDeclaredMethod("getName", null);long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000000; i++) {getName.invoke(student,null);}long endTime = System.currentTimeMillis();System.out.println("反射方式调用" + (endTime - startTime) + "ms");}//反射方式调用,关闭检测public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Student student = new Student();Class c1 = student.getClass();Method getName = c1.getDeclaredMethod("getName", null);getName.setAccessible(true);long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000000; i++) {getName.invoke(student,null);}long endTime = System.currentTimeMillis();System.out.println("关闭检测后的反射方式调用" + (endTime - startTime) + "ms");}public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {test01();test02();test03();}
}

结果:

Java从接触到放弃(二十四)---类加载相关推荐

  1. java从入门到精通二十四(三层架构完成增删改查)

    java从入门到精通二十四(三层架构完成增删改查) 前言 环境准备 创建web项目结构 导入依赖和配置文件 创建层次模型 实现查询 实现添加 实现修改 完成删除 做一个用户登录验证 会话技术 cook ...

  2. Java从接触到放弃(十五)--线程、多线程、静态代理模式

    Day Fifteen 线程 多任务: 可以理解为一个人同时去干几件事,就好比在上厕所的时候边大号边玩手机,这就是一个多任务的实例.再好比现在很多人中午在吃饭的时候,或者说在吃饭的时候,看一些下饭综艺 ...

  3. Java并发编程系列之二十四:Exchanger

    Exchanger是一个线程间交换数据的工具类. Exchanger从字面上可以理解为交换者,是一个可以用于线程间协作的工具类.主要用于线程间的数据交换.Exchanger提供一个同步点,在这个同步点 ...

  4. Effective Java之消除受检警告(二十四)

    前记:在我编程过程中,经常会看到代码下面有一条横线,但是自己并没有注意警告的具体内容,一来觉得自己写的程序没有问题,二来,感觉改会很麻烦,这是不好的习惯,如果有这种编程习惯的,都要改正一下啊- 1.在 ...

  5. java常见面试考点(二十五):CAS是什么

    java常见面试考点 往期文章推荐:   java常见面试考点(二十):Elasticsearch 和 solr 的区别   java常见面试考点(二十一):单点登录   java常见面试考点(二十二 ...

  6. SAP UI5 初学者教程之二十四 - 如何使用 OData 数据模型试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 初学者教程之一:Hello World SAP UI5 初学者教程之二:SAP UI5 ...

  7. Docker最全教程之MySQL容器化 (二十四)

    Docker最全教程之MySQL容器化 (二十四) 原文:Docker最全教程之MySQL容器化 (二十四) 前言 MySQL是目前最流行的开源的关系型数据库,MySQL的容器化之前有朋友投稿并且写过 ...

  8. JavaWeb开发与代码的编写(二十四)

    JavaWeb开发与代码的编写(二十四) JNDI数据源的配置 数据源的由来 在Java开发中,使用JDBC操作数据库的四个步骤如下: ①加载数据库驱动程序(Class.forName("数 ...

  9. JAVA面经复习(二十六)面试难度:☆☆☆☆

    JAVA面经复习(二十六)面试难度:☆☆☆☆ 面试难度:☆☆☆☆ 推荐指数:☆☆☆☆☆ 推荐原因:总体来说本篇面经难度不高,且基本都是基础知识,不涉及复杂的分布式应用的工具,适合新手复习. 声明:答案 ...

  10. 【Microsoft Azure 的1024种玩法】二十四.通过Azure Front Door 的 Web 应用程序防火墙来对 OWASP TOP 10 威胁进行防御

    [简介] 我们都知道像 SQL 注入.跨站点脚本攻击(XSS)之类的恶意攻击以及 OWASP 发现的十大威胁都可能会导致服务中断或数据丢失,让 Web 应用程序所有者受到巨大威胁.那么如何有效的解决O ...

最新文章

  1. buu-[RoarCTF2019]polyre(控制流平坦化,虚假控制流程)
  2. Function实现ALV Table六:页眉页脚
  3. 104.求二叉树的最大深度 Maximum Depth of Binary Tree
  4. 我从草原来:自由摄影人李伟 (内蒙古电视台“蔚蓝的故乡”20110407)
  5. ReactiveLodeBalancerClientFilter响应式负载均衡代理
  6. 做数据产品经理要学习那些东西?
  7. 讨论一个比较有意思的业务需求
  8. MySQL数据库专家分享资深DBA经验
  9. Python的第三方库pandas
  10. 什么是jquery $ jQuery对象和DOM对象 和一些选择器
  11. 24V600mA限流电路的Pspice仿真实例
  12. word中域代码与题注的结合实现自动编号和超简便交叉引用
  13. 抖音Vlog必备1000+超强Premiere转场特效字幕动画LUT预设PR模板包 V6 (包含音效)
  14. excel技巧——F9键
  15. 计算机网络拓扑有,计算机网络拓扑
  16. sandboxie游戏不能运行在虚拟环境中如何解决_Mac系统运行“exe”文件最简单的解决办法...
  17. SCAU 2018新生赛 初出茅庐 全题解
  18. 记u盘内文件变为.lnk处理方法
  19. 数组去重,对象去重,数组对象去重
  20. 特斯拉“翻脸”,拼多多“翻车”

热门文章

  1. vegas可以做动画吗_学剪辑用Vegas还是Pr好?
  2. A2B 1主多从的实验
  3. nonebot2聊天机器人插件1:基础应答print
  4. 不可或缺的BCUninstaller:全面显示软件信息、批量垃圾删除、强制卸载程序……
  5. 在线视频流播放方法利弊;ffmpeg mp4 faststart;mp4 moov作用
  6. (7,3)循环码编码译码
  7. 【入门数据分析】英国某电商的销售分析
  8. UOJ121/bzoj3243/洛谷P1224 向量内积 瞎搞+前缀和
  9. 第3章 业务连续性计划
  10. 旺财宝盒浅谈:十大搜索引擎优化提示列表