一、类加载

  1.加载

    将class字节码加载到内存中,同时在方法区形成改类运行时数据结构。

    同时在堆中产生一个Class对象,反射就是获取这个对象并对其进行操作。

  2.链接

    2.1验证:验证加载的类信息是否是否符合JVM规范。

    2.2准备:分配内存,赋默认值。(此处的赋默认值不是赋初值,例如int i = 3,这个是赋初值)。

    2.3解析:将虚拟机中的符号引用替换为直接引用。

  3.初始化

    3.1执行类构造器,由编译器自动收集类变量的赋值动作和静态语句块中的语句合并而成。

    3.2如果父类没有初始化,则先初始化父类。

    3.3类构造器会在多线程环境中被正确的加锁和同步。

    3.4访问一个java类的静态域时,只有真正声明这个域的类才会被初始化。

 这里主要看初始化部分,结合上述初始化中的几点我们来看下列代码:

  

public class Demo01 {
public static void main(String[] args){
A a = new A();
System.out.println(a.weight);
}
} class A{
public static int weight = 100;
static{
System.out.println("初始化A");
weight = 300;
} public A(){
System.out.println("构造对象A");
}
}
运行结果:
初始化A
构造对象A
300

首先我们看上述的 -->3.1执行类构造器,由编译器自动收集类变量的赋值动作和静态语句块中的语句合并而成。

一开始先由编译器收集变量的赋值动作,和静态语句块中的语句进行类构造。所以最先打印出初始化A,然后在main

方法中实例化了一个对象a,这时是调用A类的无参构造,所以打印出构造对象A,由于执行类构造器时,代码时顺序执行的

所以一开始weigth=100,后来执行静态块后weight是300。

public class Demo01 {
public static void main(String[] args){
A_Child ac = new A_Child();
System.out.println(ac.weight);
}
} class A_Child extends A{
static{
System.out.println("初始化A_Child");
}
public A_Child(){
System.out.println("构造对象A_Child");
}
}
class A{
public static int weight = 100;
static{
System.out.println("初始化A");
weight = 300;
} public A(){
System.out.println("构造对象A");
}
}
运行结果:
初始化A
初始化A_Child
构造对象A
构造对象A_Child
300

3.2如果父类没有初始化,则先初始化父类。

我们可以看到,实例化子类A_Child是初始化父类A,然后在初始化A_Child,当创建A_Child对象并且调用继承自父类中的属性时,

也是先构造父类A对象,然后构造A_Child对象,最后调用weigth。

public class Demo01 {
public static void main(String[] args){
System.out.println(A_Child.weight);
}
} class A_Child extends A{
static{
System.out.println("初始化A_Child");
}
public A_Child(){
System.out.println("构造对象A_Child");
}
}
class A{
public static int weight = 100;
static{
System.out.println("初始化A");
weight = 300;
} public A(){
System.out.println("构造对象A");
}
}
运行结果:
初始化A
300

上述3.4访问一个java类的静态域(属性)时,只有真正声明这个域(属性)的类才会被初始化。

例如这里是A_Child调用weigth属性,但代码运行后只初始化了A,只有真正声明这个域(属性)的类才会被初始化。

接下来我们具体看下什么情况会发生初始化,什么情况不会发生初始化:

  类的主动引用(会发生初始化):
  1.new一个对象。(new A())
  2.调用类的静态成员(final常量除外)和静态方法。(A.weigth)
  3.初始化一个类时,当其父类没有被初始化则会先初始化父类。(new A_Child();会先初始化父类)
  4.通过反射引用也会初始化。()

  附:类被加载后会维持一段时间的缓存,在缓存存在期间不会重复加载。

  例如执行上述1中的new A()后A类被加载了,接着执行2中的A.weigth此时由于有缓存,所以不会再次加载改类。

  类的被动引用(不会发生初始化):
  1.引用常量(final修饰的)不会触发此类的初始化。
  2.通过数组定义类引用不会发送初始化(A[] arr = new A[10];)
  3.访问一个静态域是只有真正声明这个域的类才会被初始化。(int a = A_Child.weight;)
  例如子类继承父类,调用子类中继承的属性,子类不会发生初始化,只会初始化父类

基本和上述初始化中的几点差不多,只不过这里对其更加具体化了。

二、类加载器

  2.1类加载器作用

    将class字节码加载到内存中,同时在方法区形成改类运行时数据结构。

    同时在堆中产生一个Class对象,反射就是获取这个对象并对其进行操作。

  2.2类加载器层次

    2.2.1引导类加载器(bootstrap class loader):主要用于加载java核心库,由原生代码编写(比如C/C++)并不继承java.lang.java.lang.ClassLoder。

    2.2.2扩展类加载器(extensions class loader):加载java的扩展库,由sun.misc.Launcher$ExtClassLoader实现。

    2.2.3应用类加载器(application calss loader):根据java应用路径(java.class.path),加载java应用的类。由sun.misc.Launcher$AppClassLoader实现。

    2.2.4自定义类加载器:通过继承java.lang.ClassLoder类来实现自定义类加载。

    类加载器采取的是双亲委托机制,简单来说就是当前加载器不对其直接进行加载,而是向上传递直到顶层,再来判断是否可以加载。可以加载则加载,

    不能加载则给下一级判断能否加载。所有加载器都无法加载就报错。

    例如这里有个类只能由自定义加载器加载,那么首先会将其一级级上传到引导类加载器,接着引导类加载器判断是否可以加载,假设这里是不能加载,

    则下传给扩展类加载器,仍然不嗯呢加载,继续下传到应用类加载器,还是不能加载。接着下传到自定义加载器,发现可以加载就将其加载。

    

    可能感觉这样做是不是有些费事费力,为什么不首先判断当前加载器能否加载,如果能加载则直接加载,不能再上传判断能否加载这样做呢?

    主要是为了核心库、扩展库等的安全。例如我们自己定义一个java.lang.String。如果采用双亲委托,加载的依然是核心库中的String。如果采用

    我们假设的方法,那么假如第一个类加载器可以加载,那么核心库中的String就变成了我们自定义的String,这样显然是不安全。

  2.3java.lang.ClassLoder

  java.lang.ClassLoderClassLoader:根据类名找到或生成对应的字节代码,然后从这些字节代码中定义出一个java类,即java.lang.Class的实例。

  除此之外,ClassLocal还负责加载java应用所需要的资源,如图片、配置文件等。

  主要方法:

  public final ClassLoader getParent();//获取上一级(父)构造器

  protected final Class<?> findLoadedClass(String name);//如果该类已被加载则返回对应class对象,反之返回null

  public Class<?> loadClass(String name);//加载指定类,返回对应class对象

  protected final Class<?> defineClass(String name, byte[] b, int off, int len)//将字节数组转换为指定类,返回class对象。

public class Demo01 {
public static void main(String[] args){
ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);//返回当前委派的类加载器(应用类加载器)
System.out.println(cl.getParent());//获取当前类加载器的父级(扩展类加载器)
System.out.println(cl.getParent().getParent());//父级的父级(引导类加载器)
//获取当前java.class.path即应用类加载器加载的路径
System.out.println(System.getProperty("java.class.path"));
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null(由于引导类加载器由原生代码编写,所以无法获取到)
E:\eclipse\ClassLoader\bin

下面我们就要结合java.lang.ClassLoder中的方法编写一个自定义类加载器。

  具体步骤:

    1.自定义MyLoader需要继承ClassLoader

    2.当前类是否已经被加载,如果已经被加载则返回对应Class对象。

       如果没有被加载则交给父构造器加载。

    3.父构造器尝试对其加载,如果加载成功则返回对应class对象。

     如果父加载器无法加载,则使用自定义加载器加载。

    (这里只是简单的模拟下,所以只向上寻找了一层,不同于之前说的寻找到顶层为止)

    4.读取对应.class文件并用字节数组返回,将字节数组转换为class对象

    注:加载的类要有.class文件,即是已经完成编译了的。

package TestClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; public class MyClassLoader extends ClassLoader{
private String root;
public MyClassLoader(String root){
this.root = root;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
Class c = findLoadedClass(name);//如果改类已被加载返回对应类,反之返回null;
if(c != null){//如果已加载,直接返回对应class对象
return c;
}else{
//没有加载的话先调用父加载器加载
ClassLoader parent = this.getParent();//获取父加载器
//System.out.println(parent); //当前的父加载器,即应用加载器
try{
c = parent.loadClass(name);//加载指定类并返回加载的类
}catch(Exception e){
//父类可能无法加载指定类这里会弹出异常信息,
//因为这里的父加载器是应用用加载器,而引用加载器是加载class.path路径下的类。
//而这个class.path代表当前项目地址(E:\eclipse\ClassLoader\bin)
//但是我们要加载的类的地址是F:/TestJava/com/TestSsist/TestUser.class
// e.printStackTrace();//这里出现异常是不影响的。
//因为即使出现异常,最终也会执行转换。
}finally{
if(c != null){//如果父加载器加载成功,直接返回。
return c;
}else{
//如果父加载器没有加载成功
//获取指定class文件数据,并以字节数组返回,然后转换成对应类
byte[] classDate = getClassDate(name);
if(classDate != null){
//将字节数组转换为class对象
c = defineClass(name,classDate,0,classDate.length);
}else{
throw new ClassNotFoundException();
}
}
}
}
return c;
} private byte[] getClassDate(String className){
int temp = 0;
byte[] buffer = new byte[1024];
//com.TestSsist.TestUser --> root(F:/TestJava/) + com/TestSsist/TestUser.class
String path = root + className.replace('.', '/') + ".class";
InputStream bi = null;
ByteArrayOutputStream baos = null;
try {
bi = new FileInputStream(path);
baos = new ByteArrayOutputStream();
while((temp = bi.read(buffer)) != -1){
baos.write(buffer, 0, temp);
}
} catch (IOException e) {
// TODO Auto-generated catch block //e.printStackTrace();
}
return baos.toByteArray();
}
}
public class Demo01 {
public static void main(String[] args){
//自定义类加载
MyClassLoader loader = new MyClassLoader("F:/TestJava/");
try {
Class c = loader.findClass("com.TestSsist.TestUser");
System.out.println(c.getClassLoader());//打印出该类的类加载器
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//*/
}
}
运行结果:
TestClassLoader.MyClassLoader@6d06d69c

  2.4线程上下文加载器

    java本身提供的双亲委托机制的加载有其优点,但也有其缺点。例如某些接口在java的扩展库中,通过扩展类加载器可以加载。

    但其实现是由第三方厂家实现的,也就是接口声明和实现不在一起,这时就会出现无法加载的情况。

    为了避免这一情况,java中还提供了线程上下文加载器,简单的说改变当前线程的加载器,让其可以根据我们实际需要调整。

    例如我们一般是进入main方法中,这里打开一个main线程,默认一般使用应用类加载器加载,首先加载A(这里当然是用的当前线程默认的应用类加载器)。

    然后我们修改当前main线程的加载器为其他的加载,然后通过获取的其他加载器进行加载,这样就可以解决接口实现分离问题。

自定义类加载器及加载类参照上述2.3内容。

public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException{
//获取当前线程加载器并打印(此时为应用类加载器)
System.out.println(Thread.currentThread().getContextClassLoader());
//将当前线程的加载器设置为MyClassLoader
Thread.currentThread().setContextClassLoader(new MyClassLoader("F:/TestJava/"));
//获取当前线程加载器并打印(此时已变成MyClassLoader)
System.out.println(Thread.currentThread().getContextClassLoader());
//获取当前线程类加载器,并加载指定类。
Class c = Thread.currentThread().getContextClassLoader().loadClass("com.TestSsist.TestUser");
//获取加载改类的类加载器
System.out.println(c.getClassLoader());
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@73d16e93
TestClassLoader.MyClassLoader@6d06d69c
TestClassLoader.MyClassLoader@6d06d69c  

8.6(java学习笔记)类加载过程及类加载器的更多相关文章

  1. 0032 Java学习笔记-类加载机制-初步

    JVM虚拟机 Java虚拟机有自己完善的硬件架构(处理器.堆栈.寄存器等)和指令系统 Java虚拟机是一种能运行Java bytecode的虚拟机 JVM并非专属于Java语言,只要生成的编译文件能匹 ...

  2. 《Java编程思想》学习笔记(二)——类加载及执行顺序

    <Java编程思想>学习笔记(二)--类加载及执行顺序 (这是很久之前写的,保存在印象笔记上,今天写在博客上.) 今天看Java编程思想,看到这样一道代码 //: OrderOfIniti ...

  3. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  4. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  5. 20145230《java学习笔记》第九周学习总结

    20145230 <Java程序设计>第9周学习总结 教材学习内容 JDBC JDBC简介 JDBC是用于执行SQL的解决方案,开发人员使用JDBC的标准接口,数据库厂商则对接口进行操作, ...

  6. java学习笔记之基础篇

    java选择语句之switch   //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...

  7. java学习笔记(1)java的基础介绍 、JDK下载、配置环境变量、运行java程序

    java工程师是开发软件的 什么是软件呢? 计算机包括两部分: 硬件: 鼠标.键盘.显示器.主机箱内部的cpu.内存条.硬盘等 软件: 软件包括:系统软件和应用软件 系统软件:直接和硬件交互的软件:w ...

  8. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  9. Java学习笔记(04)

    Java学习笔记(04) 如有不对或不足的地方,请给出建议,谢谢! 一.对象 面向对象的核心:找合适的对象做合适的事情 面向对象的编程思想:尽可能的用计算机语言来描述现实生活中的事物 面向对象:侧重于 ...

  10. 0030 Java学习笔记-面向对象-垃圾回收、(强、软、弱、虚)引用

    垃圾回收特点 垃圾:程序运行过程中,会为对象.数组等分配内存,运行过程中或结束后,这些对象可能就没用了,没有变量再指向它们,这时候,它们就成了垃圾,等着垃圾回收程序的回收再利用 Java的垃圾回收机制 ...

随机推荐

  1. 获取系统内RAR安装路径

    RegistryKey the_Reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVers ...

  2. Python代码规范

    一:背景 用于规范化ocp python开发,对于使用python开发的程序使用统一的风格,便于代码的维护 二:python风格规范 分号:不要在行尾加分号,也不要用分号将两条命令放在同一行 括号:宁 ...

  3. ES6学习笔记(三)—— Set 和 Map

    SetES6提供的数据结构,类似于数组,但是成员的值都是唯一的.(提供了一种数组去重的方法) Set 内部判断两个值是否相同使用的是 'Same-value equality',类似于 ===但是 N ...

  4. maven在add dependecy时搜索不出jar包的解决办法

    一:前言 其实我一直都很头疼maven的项目管理的,因为觉得用起来还是没有那么方便的啊,不过今天我自己算是小弄了下maven项目的故那里,是一个同事在配置maven的项目,我去凑了下热闹而已,现在自己 ...

  5. BigDecimal精度问题

    介绍 1.商业计算使用BigDecimal. 2.使用参数为String的构造函数. 3.BigDecimal都是不可变的,每一步的运算时,都会产生一个新的对象.所以在做加减乘除后千万要保存操作后的值 ...

  6. 【BZOJ1146】【CTSC2008】网络管理 [整体二分]

    网络管理 Time Limit: 50 Sec  Memory Limit: 162 MB[Submit][Status][Discuss] Description M公司是一个非常庞大的跨国公司,在 ...

  7. Web Application Vulnerabilities and Potential Problem Due to Bad Design

    web应用设计中不安全的设计及潜在的风险: REF: https://msdn.microsoft.com/en-us/library/ff648647.aspx

  8. python3 面向对象、类、继承、组合、派生、接口、子类重用父类方法

    对象是特征(变量)与技能(函数)的结合体而类是一系列对象共同的特征与技能的集合体 class teacher: lesson = "python" def __init__(sel ...

  9. python3 基础概念

    一.3.x新特性 1.print (),打印,3.x必须加括号 2.raw_input,3.x改为input   二.简介   Python是著名的“龟叔”Guido van Rossum在1989年 ...

  10. LeetCode the longest palindrome substring

    回文检测,参考http://blog.csdn.net/feliciafay/article/details/16984031 使用时间复杂度和空间复杂度相对较低的动态规划法来检测,具体的做法 图一 ...