他是干什么的?
从点java文件到点class文件,通用类加载器加载到内存,并加入内部库,例如string.class
通过解释编译,或者频率搞的代码会变成本地代码可以直接执行,然后到引擎执行
他有什么巨大的优点?
- 跨平台是最大的优点
- 相当于一个操作系统
首先jvm和java没用关系,只要实现了jvm规范的机器上面可以跑各种语言,据说已经有100多种了, 每个平台上面都有具体的实现,所以强啊。
他在哪里?
里面详细的写出了他的规范。 只要你强你就可以实现他。
怎么用?
看到这里你就应该差不多知道了,下载好了jdk后就自然有了。
使用工具查看十六进制码 和下面工具一起看比较好
就长这样子
简单介绍一下: 咖啡babe 是class文件是标准符,别搞错了, 这边4位算两个字节,后两位是jdk的minor-version, 再后面的00 34 是jdk 1.8 十进制是52 转化成16进制就是 34 然后后面的0a啥的就是class meoth ref 03 就是引用常量池中的第几个。01 开头的都是utf 啥的 后面跟0几 就有几个白色 就是一些名字常量
使用工具查看class码
这个工具能很好的体现class文件内的分布, 显示常量池,然后接口 属性 方法 附加属性 再里面也可以看到互相的引用
classload
首先要明确的一点是bootclassload 和其他两个不是继承关系 只是我这找不到了 就去你那里找 ,你那里 也找不到 回来 我这边直接给你安排掉,如果一开就在缓存中的就不需要找了
classload
其实非常的简单,就是你自定义的classload想要去加载一个类,首先去自己的内存里看一下,有没有加载过,有就直接返回,没有就去父加载器app里面找同样的会去找内存和父加载器,ext 和boot 加载器,如果在他们指定的范围里面没找到的话,就依次委托给子加载器, 直到最后都没找到的话,就直接报出classnotfound 异常。
其实就是说:我的父类加载器是你,但我不一定是由你加载的
下面就体现出来了,getparnet()能获得他的父加载器是谁 , 但这个不一定是加载他的
加载器加载的目录打印
什么时候要用到类加载器
1.比如 动态代理的时候,如你所看到的 他会生成一个类文件,实际上你调用的时候是在这个类的方法 所以需要你把他加入到内存里面去。 2.热加载 显然要自动加入内存里面去,所以自己动把
这里面的初始化只是把静态变量给赋值了 别以为是把属性变量也赋值了。
怎么自己实现类加载器 真的不要太简单了
首先看源码 loadclass --- 里面首先就是先看自己的缓存里面有没有加载过这个傻逼类, 没有递归调用去看我的父亲有没有调用过这个类,都没有的话就自己上了,直接实现findclass方法 父类里面的findclass是找不到这个文件的
加密自己的文件 别人是不能反编译的
java是通过解释+编译的jit
平时启动项目时要build 其实是在 本地编译中,执行是时候是在解释
自定义classload
继承classload后要去重新他的findclass方法,里面必须用到defineclass 有因为要用到这个类 ,他的参数是要字节数组,所以在io的时候要用到bytearrayoutputstream ,最后在用tobytearray 转化成字节数组就好了,但是记得要关闭流,调用的时候直接new 然后调用对应的loadclass方法。
jit即时编译
java会给循环遍历计数 用的多的代码会编译成本地代码 -Xmix 是jvm混合编译 -Xint 是解释 -Xcomp 是纯编译 ,但是如果文件类比较多的话就会很慢 默认是混合编译
加密
先把class文件疑惑一下 就算别人有这个文件哪有咋样,没有seed 照样解不了
先加密
public static void encFile(String name) throws IOException {
File file=new File("C:/Users/yangwu/IdeaProjects/jmh/target/classes/",name.replace(".","/").concat(".class"));
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream=new FileOutputStream(new File("F:/",name.replace(".","/").concat(".class")));
int i;
while ((i=fileInputStream.read())!=-1){
fileOutputStream.write(i^seed);
}
fileInputStream.close();
fileOutputStream.close();
}
再解密
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file=new File("F:",name.replace(".","/").concat(".class"));
try {
FileInputStream fileInputStream = new FileInputStream(file);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
FileOutputStream fileOutputStream = new FileOutputStream(file);
int b=0;
while ((b=fileInputStream.read())!=-1){
byteArrayOutputStream.write(b^seed);
}
fileInputStream.close();
byteArrayOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
懒加载
就是只有你用到的时候,才会去初始化这个类,这边的初始化指的是,静态变量的初始化,
什么叫用到,就是new 或者调用 啥的父类,如果只是P P 这样的 不调用的 是不会初始化静态变量的
可以打破双亲委派机制吗 当然是可以的
那个机制主要在于classload里面,所以你要干掉他的话,只能重写loadclass方法
tomcat里面每个webapp 都有自己的classload 不然不能加载版本不同的依赖了,因为是同名的 以前加载过所以不会去加载。 还有热部署里面,要重写加载这个类,不能用他原来的loadclass 否则不管你加载多少次都是一个同样的,因为他已经存在于内存中,
我们只要把那部分的在内存里面找的部分给删除掉就好了,只要你给我名字我就去找,然后把原先的东西给干掉
prepare 准备阶段
根据顺序不一样 答案也是不一样的,也是附默认值 然后再试初始值 一个好的编程习惯 就是先附默认值 在构造方法里面去搞初始值
dcl单例模式 不加volitail的话 指令重排
可能会读到 初始化一般的东西 非常好理解 正常来说 先是附默认值 然后就给初始值, 但是指令如果发生重排序的话 有可能是先给了默认指 好家伙如果被一个线程读到了, 因为当时已经new了所以不为空 那么他就非常幸运的都到0
cpu的存储结构
越接近cpu 越快 cpu内部的寄存器是最屌的,然后是附近的缓存l1和l2是在cpu的内部的
现代cpu的数据一致性是通过缓存锁和总线锁实现
cpu 中是怎么保持的不同核之间的数据一致性的,一是通过数据总线,二是通过,mesi协议,
其实就是标记,数据的属性,是否被修改过,如果被修改过就重写去拿一份新的数据过来。
modify的是我自己改的 invalid是我拿了别人改的
缓存行的概念
就是比如我要读一个int字节的话,cpu不会去一行里面 去找那个int值,哪有的话太慢了,之间会把一行全部加进去
缓存行的伪共享
就是cpu的两个核 一个要的是x 一个要的是Y 但是tm他们在一行上 ,同时被读进去两个核里面去,当一个核改了x 整个行都被改为invalid 然后通知所有的cpu,第二核 啥都没干 他要的只是Y 你妈我还要重新去拿一遍 我烦不烦啊
骚套路啊 填充缓存行 加快执行
如果两个线程要的数据在同一个数组或者啥里面 ,可能会出现伪共享的问题,我们可以填充缓存行 64字节 使他们强制不在一个缓存行上。一个long是8个字节 所以先填了7个
cpu的读乱序
为什么 因为cpu执行的速度太快了,当要去内存里面取东西的期间完全是可以执行别的指令的 但前提是指令不能有依赖关系
cpu的写合并
因为写回去还是太慢了,把几个写的合并在一起最后写回去。因为cpu有一个合并写的缓存 也可以利用这个机制 但是他只有4个字节,一次凑成4个过去比较快,所以我想到了一个方法 把一个东西变成字节数组 然后分成4个字节一份 ,这让比原来所有的一块过去要快很多。
java jvm的实现volitail
就是加了点屏障
sync的底层实现
字节码层面就是 mnitorenter 和exit 就是加了监视器 jvm就是 c和c++ 调用操作系统 机器层面就是 lock
对象的创建过程
- loading
- linking 中有三个 第一个验证 第二个就是静态变量赋默认值 第三个就是解析替换符号
- 初始化 静态变量 初始值 执行静态代码块
- new 声请空间
- 变量赋默认值
- 调用构造方法
对象的大小
object是16字节 因为没啥东西 对象头是8字节 classpoint是4字节 压缩过的 对齐4字节 数组的话24字节 一个O对象有属性的话,int之类的都是4字节 引用属性4字节 压缩过的 然后对齐。
内存的分配
每一个线程都有自己的栈和本地栈 pc 堆和方法区的共享的
栈里面放的是栈帧
每一个方法就是一个栈帧
stroe就是给本地栈里面的东西赋值 load就是压栈 invoke就是调用 i sub 就是减法 icount 就是数字1
递归就是栈帧向上叠加,算完后把结果retern给下面的栈帧。
垃圾回收
- 第一种引用计数 但是不能解决循环引用 第二个找根 有四种根 main栈 静态变量 常量池,C方法
对象回收
堆回收
垃圾主要回收就是在堆内存里面,堆里面是分代的 ,
对象的产生
首先new 看看栈上能不能放的下 ,放栈上好啊 ,pop就是直接没了, 然后本地内存放,如果太大了,本地放不下 ,直接old
四句话
- 垃圾怎么产生的 对象---分代
- 怎么找到垃圾 垃圾定位
- 垃圾怎么处理 算法
- 谁来处理 cms 等