java的类加载过程 就是将我们通过classloader的代码转换为二级制的字节码,然后用一个class对象指向它,让我们可以取出数据。
我们知道一个类如果需要加载,那么必然会经历类的加载器,那么类加载器有哪些:
1.最顶层的类加载器,Bootstrap加载器
2.加载扩展包的加载器 Extension
3.加载classpath的加载器,App
4.自定义的ClassLoader
我们必须知道这个类加载器是层层递进的 比如bootstrap是Extension的父类 extension又是app的父类
注意:此处的父类不是继承关系的父类,而是双亲委派过程中的递归层次的父类。
简单的示例:
public static void main(String[] args) {//有四种类加载器 Bootstrap 是核心类的加载器(lib/rt.jar charset.jar) 由c++实现
//Extension 加载扩展 jar包 jre/lib/ext/*.jar,或由-Djava.ext.dirs指定
//App 加载classPath指定内容 就是我们自己写的类
//Custom ClassLoader 自定义的classLoader
System.out.println(String.class.getClassLoader());
System.out.println(sun.awt.HKSCS.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
System.out.println(com.gsx.ClassLoader.classloader_test_01.class.getClassLoader());
//获取父类的类加载器 这里的父不是指的继承关系的父 是双亲委派加载过程中需要查找的一个层次关系
//所有加载器都是由Bootstrap加载器加载的
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
System.out.println(com.gsx.ClassLoader.classloader_test_01.class.getClassLoader().getClass().getClassLoader());
}
双亲委派: 所谓双亲委派 就是类加载器在寻找类的过程中的层次关系,如果说我们遇到一个类需要被加载,那么首先从它的加载器进行加载(如果它的类加载器是我们的自定义类加载器),首先竟然到CustomClassLoader中进行加载,去它对应的缓存区域找,如果没有 向它的父类 AppClassLoader中寻找,如果App缓存中没有,那么会去到Extension缓存中去寻找,没找到,然后再去BootStrap缓存中去寻找,如果都没有找到 此时会一层一层的往下走,首先看Bootstrap能否加载这个类,无法加载,然后回到Extension中加载,无法加载,然后去App中加载,无法加载,最后回到自定义的加载器中,加载成功返回class对象,不成功抛出classNotFoundExection。
为什么会出现双亲委派这一个过程,为了安全性,如果说有人写了一个String对象,妄图覆盖掉java.lang的String,里面有一些东西 比如遇到账号密码等就发送到它的电脑,那么这样就会造成极大的安全隐患,通过双亲委派,向上找,再Bootstrap加载器中,他会直接将java内置的String返回给你 不会用你自定义的。
其实类加载的过程就是将.class文件二进制读入jvm所占有的内存中,然后返回给我们一个class对象指向这个内存,这样我们的就可以同class对象获取我们的类的属性。
这里有一段代码可以查看java内置的各个类加载器的默认的加载路径:
public static void main(String[] args) {//获取我们java中各个类加载器 所定义的加载路径
String pathBoot=System.getProperty("sun.boot.class.path");//bootstrap 类加载器
System.out.println(pathBoot.replaceAll(";",System.lineSeparator()));
System.out.println("=======================================================");
String pathExt=System.getProperty("java.ext.dirs");//extension 类加载器
System.out.println(pathExt.replaceAll(";",System.lineSeparator()));
System.out.println("=======================================================");
String pathApp=System.getProperty("java.class.path");// app类加载器
System.out.println(pathApp.replaceAll(";",System.lineSeparator()));
System.out.println("=======================================================");
}
简单的源码阅读:
我们加载一个类会调用loadClass对象 我们进入该对象中查看:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {//加锁 防止同时又相同的对象来加载
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);//从缓存中查看这个class是否已经加载了
if (c == null) {//如果没有加载
long t0 = System.nanoTime();
try {
if (parent != null) {//这里的父加载器 就是我们上面提到的那个层次关系
c = parent.loadClass(name, false);;//就去父加载器中去寻找
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//没有找到调用findClass方法 我们只需要重写这个方法即可
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
简单的自定义类加载器:
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file=new File("E:/java_project/",name.replaceAll(".","/").concat(".class"));//将class文件以二进制的形式读入
try {
FileInputStream fileInputStream=new FileInputStream(file);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
int b=0;
while((b=fileInputStream.read())!=0){
byteArrayOutputStream.write(b);
}
byte[] bytes=byteArrayOutputStream.toByteArray();//获取到的二级制转换为字节数组
byteArrayOutputStream.close();
fileInputStream.close();
return defineClass("Hello.class",bytes,0,bytes.length);//将二进制的文件 读入我们的jvm所占有的的内存空间中 让他生产class对象 这个defineClass是classLoader提供的对象
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);//这里是抛异常 classNotfoundException
}
public static void main(String[] args) throws Exception{
ClassLoader c=new MyClassLoader();
Class clazz = c.loadClass("com.gsx.ClassLoader.Hello");
Hello o = (Hello)clazz.newInstance();
o.m();
System.out.println(o.getClass().getClassLoader());
}
}
java代码的编译是混合模式 意思就是对于指向次数没有那么多的时候会编译为字节码解释执行,但对于短时间内被许多次调用的方法会使用jit直接编译成为二进制码 由硬件来执行。
如果 public static int count=2;放在上面 那么得到的值是3,如果放在下面得到的值是2,因为放在下面过后,我们的上面语句先执行,此时我们的count是默认值0,没有初始化 调用构造方法后 count变为1,然后count进行初始化 赋值 为2.
public class Linking {//java再loading之后 会进行Linking 里面有 三个步骤 1.Verification 严重文件是否符合jvm规范
//2.Preparation 静态成员变量赋默认值
//3.Resolution 将类,方法,属性等符号引用解析为直接引用 常量池中的各种符号解析为指针,偏移量等内存地址的直接引用
public static void main(String[] args) {
System.out.println(T.count);
}
static class T{//load到内存中 先赋默认值 对象是null int 是1 然后才赋初值 所以顺序不同的时候另一个还没有赋初值 可能会导致结果不同
public static T t=new T();
public static int count=2;
public T(){
count++;
}
}
}
指令重排:
public class SingleTon {
private static volatile SingleTon INSTANCE;
private SingleTon(){
}
public static SingleTon getInstance(){//双重检查的单例模式 需要加volatile参数 主要是为了不让指令重排
if(INSTANCE==null){
synchronized (SingleTon.class){
if(INSTANCE==null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE=new SingleTon();//次处我们已经 申请到了内存 同时已经进行了属性的默认值的赋值 但是 线程暂停了 其他线程进入
//由于对象已经创建完成 只是没有初始化 直接返回了INSTANCE给其他地方用了 导致出错 因为使用的值并不是我的值 而是jvm赋予的默认值
}
}
}
return INSTANCE;
}
}
如下的一个代码:
public class OrderSorted {//指令重排
public static void main(String[] args) {
OrderSorted orderSorted=new OrderSorted();
}
}
它的字节码,按这样运行没有问题 可是指令重排 可能3和4会交换位置 将我们没有初始化的对象拿来使用: