java的类加载(双亲委派等)

前言:

本文主要介绍java的类加载过程和类加载器的种类、双亲委派机制、自定义类加载器和打破双亲委派的写法,以及缓存行、伪共享和cpu指令重排的一些验证(为下一篇做引子)。

1、java类加载

类加载的过程

类加载的时候把class(二进制字节码文件)加载到内存,做两件事,生成一个区域放字节码,生成一个class的对象,以后使用的的时候都是用过class对象代理去执行对应的字节码指令。

类加载器

publicstaticvoidmain(String[] args) {
//核心类库的加载bootstrap    
System.out.println(String.class.getClassLoader());    
//核心类库的加载bootstrap   
System.out.println(HKSCS.class.getClassLoader());    
//扩展库的类加载extension    
System.out.println(DNSNameService.class.getClassLoader());    
//应用本身的类加载application    
System.out.println(ClassTest.class.getClassLoader());}

双亲委派(类加载的过程)

自底向上检查该类是否已加载,子到父的方向

class加载的时候先去调用customer加载器查看缓存是否有加载,有的话返回结果,没有的话调用application的类加载器查看缓存是否有加载,有的话返回结果,没有的话继续向上需查看extension的类加载器的缓存是否有加载,有的话返回结果,没有的话继续查看bootstrap是否加载有加载,有的话返回结果

自顶向下进行实际的查找和加载,父到子的方向

从下到上都没有加载到的话,开始向下通知extension去加载,可以加载的话返回结果,不能加载的话继续向下找application,能加载的话返回结果,不能加载的话继续向下找customer,能加载的话返回结果,不能加载的话,报错classNotFound;整个加载的过程需要循环走两次

为什么使用双亲委派:

安全问题(主要) 资源浪费问题(次要)

类加载器加载的具体路径(Launcher)

bootstrap:

exsention:

application:

代码验证:

StringbootPath=System.getProperty("sun.boot.class.path");
System.out.println(bootPath.replaceAll(";",System.lineSeparator()));
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
StringextPath=System.getProperty("java.ext.dirs");
System.out.println(extPath.replaceAll(";",System.lineSeparator()));
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
StringappPath=System.getProperty("java.class.path");
System.out.println(appPath.replaceAll(";",System.lineSeparator()));

自定义类加载器

实现方式:继承classLoader,并且重写他的findClass()。

意义:自己写类库实现;为了加密使用

相关代码块

packagecom.java.czing.jvm.classfile;

importjava.io.ByteArrayOutputStream;
importjava.io.File;
importjava.io.FileInputStream;

/**
* @Description 实现自定义类加载器
* @Author wangchengzhi
* @Date 2023/2/18 21:59
*/
publicclassCustomerClassLoaderextendsClassLoader {
@Override
protectedClassfindClass(Stringname) throwsClassNotFoundException {
Filef=newFile("D:\\workspace\\jvm\\target\\classes\\com\\java\\czing\\jvm\\classfile", name.replace(".", "/").concat(".class"));
try {
FileInputStreamfis=newFileInputStream(f);
ByteArrayOutputStreambaos=newByteArrayOutputStream();
intb=0;

while ((b=fis.read()) !=0) {
baos.write(b);
            }

byte[] bytes=baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨
//二进制转换为class类对象
returndefineClass(name, bytes, 0, bytes.length);
        } catch (Exceptione) {
e.printStackTrace();
        }
returnsuper.findClass(name); //throws ClassNotFoundException
    }

publicstaticvoidmain(String[] args) throwsException {
//获取自定义的类
ClassLoaderclassLoader=newCustomerClassLoader();
//加载自定义类下面的具体对象
Classclazz=classLoader.loadClass("com.java.czing.jvm.classfile.ClassTest");
Classclazz1=classLoader.loadClass("com.java.czing.jvm.classfile.ClassTest");
//只加载一次,加载到的是同一个对象
System.out.println(clazz==clazz1);
//通过反射创建实例
ClassTesto= (ClassTest)clazz.newInstance();
//调用方法
o.test();
//自定义类加载器
System.out.println(classLoader.getClass().getClassLoader());
//类加载器的父级加载器
System.out.println(classLoader.getParent());
System.out.println(getSystemClassLoader());
    }
}

java是解释型和编译型语言

指定类型:-Xmixed默认混合模式 -Xint 纯解释模式 -Xcomp 纯编译模式

默认为混合模式,开始解释执行,启动速度较快,对热点代码实行检测和编译。

混合模式:

1.混合使用解释器+热点代码编译

2.起始阶段采用解释执行

3.热点代码检测

-Xint: 使用解释模式,启动很快,执行稍慢

-Xcomp: 使用纯编译模式,执行很快,启动很慢。

jvm是懒加载的

java不是一次性加载所有类库 按需加载

lazyLoading的5种情况:

  • new getstatic putstaticinvokestatic指令, 访问final变量除外

  • java.lang.reflect对类进行反射调用时

  • 初始化子类的时候,父类首先初始化

  • 虚拟机启动时,被执行的主类必须初始化

  • 动态语言支持java.lang.invoke.MethodHandle解析的结果为

  • REF getstatic REF putstatic REF invokestatic的方法句柄时,该类必须初始化

打破双亲委派机制

集成classloader重写Loadclass方法,打破双亲委派机制可以实现热部署。

对应的源码:

protectedClassloadClass(Stringname, booleanresolve)
throwsClassNotFoundException
    {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Classc=findLoadedClass(name);
if (c==null) {
longt0=System.nanoTime();
try {
if (parent!=null) {
c=parent.loadClass(name, false);
                    } else {
c=findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundExceptione) {
// 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.
longt1=System.nanoTime();
c=findClass(name);

// 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);
            }
returnc;
        }
    }

2、类的链接过程(Linking)

verification(验证)

验证文件是否符合JVM规定

preparation(准备)

静态成员变量赋默认值

resolution(解析)

将类、方法、属性等符号引用解析为直接引用

常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

load - 默认值 - 初始值

new object - 申请内存 - 默认值 - 初始值

4、CPU读取文件

3、缓存行 伪共享

线程a 在CPU1上读写变量X, 同时线程b上读写变量Y,但是巧合的是变量 X , Y 在同一个缓存行上,那边线程a,b就要互相竞争获取该缓存行的读写权限,才可以进行读写。假如 线程a在内核1上 获取了缓存行的读写权限,进行了操作,就会导致其他内核中的x变量和y变量同时失效,那么线程b 就必须刷新它的缓存后才能在内核2上获取缓存行的读写权限。 这就导致了这个缓存行在不同的线程之间多次通过 L3缓存进行交换最新复制的数据,极大的影响了CPU性能。

不同的cpu读取同一个缓存行操作不同数据的时候,会因为分别操作数据,导致缓存行的不断更新,重新拉取同步内存,会有数据伪共享的问题,导致效率降低。

解决方法:

1、可以采用padding缓存行填充的方式解决,每个缓存行64字节,在该变量的前后分别加7个long类型的属性,共56字节,这样就能保证让数据自己独立存在于一个缓存行。

2、disruptor框架实现缓存行填充,前后都创建7个对象。空间换取时间。

4、cpu的乱序执行问题

cpu为了提高效率,不会完全按照指令顺序去执行,会同时执行一些不相关的指令操作。

写操作也可以进行合并写的操作,合并写的技术分词合并提交,比一次提交效率要高

证明指令重排的编码:

packagecom.java.czing.jvm.jmm;

/**
* @Description 如果x,y出现0,0
* 说明出现了指令重排
* @Author wangchengzhi
* @Date 2023/2/19 21:42
*/
publicclassDisorder {

privatestaticintx=0, y=0;
privatestaticinta=0, b=0;

publicstaticvoidmain(String[] args) throwsInterruptedException {
inti=0;
for(;;) {
i++;
x=0; y=0;
a=0; b=0;
Threadone=newThread(newRunnable() {
publicvoidrun() {
//由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
//shortWait(100000);
a=1;
x=b;
                    }
                });

Threadother=newThread(newRunnable() {
publicvoidrun() {
b=1;
y=a;
                    }
                });
one.start();other.start();
one.join();other.join();
Stringresult="第"+i+"次 ("+x+","+y+")";
if(x==0&&y==0) {
System.err.println(result);
break;
                } else {
//System.out.println(result);
                }
            }
        }


publicstaticvoidshortWait(longinterval){
longstart=System.nanoTime();
longend;
do{
end=System.nanoTime();
            }while(start+interval>=end);
        }
    }

如何保证有序性:

1、volitile保证了有序

2、加锁但是效率低

  1. 内存屏障(cpu级别)

结语

本文主要内容是针对类加载的说明和分享,双亲委派机制的讲解,最后带出缓存行等硬件层面的一些技术原理,有需要代码验证的随时留言交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值