首页
关于
推荐
搜索定位器
视频解码
电源计划
软件资源
博客友链
搜索
1
开源软件:m3u8.sqlite文件转视频工具
985 阅读
2
欢迎来到我的世界
753 阅读
3
iKuai+Openwrt实现虚拟WAN分流,再也不用担心旁路网关崩了影响全局网络
689 阅读
4
AI:ChatGPT,假如你是Java架构师
662 阅读
5
开源软件:Windows网络驱动器管理工具
629 阅读
资料整理
软件发布
世新家装
萌宠乐园
登录
搜索
标签搜索
Docker
CentOS7
开源软件
Jenkins
MySQL
JVM
世新家装
ZStack
Java
Spring
Shell脚本
docker-compose
iKuai
ESXi
Bean
BeanDefinition
小太阳鹦鹉
欢迎
C#
WPF
卖萌小老头
累计撰写
51
篇文章
累计收到
8
条评论
首页
栏目
资料整理
软件发布
世新家装
萌宠乐园
页面
关于
推荐
搜索定位器
视频解码
电源计划
软件资源
博客友链
搜索到
3
篇与
JVM
的结果
JVM指令手册
栈和局部变量操作将常量压入栈的指令aconst_null 将null对象引用压入栈iconst_m1 将int类型常量-1压入栈iconst_0 将int类型常量0压入栈iconst_1 将int类型常量1压入操作数栈iconst_2 将int类型常量2压入栈iconst_3 将int类型常量3压入栈iconst_4 将int类型常量4压入栈iconst_5 将int类型常量5压入栈lconst_0 将long类型常量0压入栈lconst_1 将long类型常量1压入栈fconst_0 将float类型常量0压入栈fconst_1 将float类型常量1压入栈dconst_0 将double类型常量0压入栈dconst_1 将double类型常量1压入栈bipush 将一个8位带符号整数压入栈sipush 将16位带符号整数压入栈ldc 把常量池中的项压入栈ldc_w 把常量池中的项压入栈(使用宽索引)ldc2_w 把常量池中long类型或者double类型的项压入栈(使用宽索引)从栈中的局部变量中装载值的指令iload 从局部变量中装载int类型值lload 从局部变量中装载long类型值fload 从局部变量中装载float类型值dload 从局部变量中装载double类型值aload 从局部变量中装载引用类型值(refernce)iload_0 从局部变量0中装载int类型值iload_1 从局部变量1中装载int类型值iload_2 从局部变量2中装载int类型值iload_3 从局部变量3中装载int类型值lload_0 从局部变量0中装载long类型值lload_1 从局部变量1中装载long类型值lload_2 从局部变量2中装载long类型值lload_3 从局部变量3中装载long类型值fload_0 从局部变量0中装载float类型值fload_1 从局部变量1中装载float类型值fload_2 从局部变量2中装载float类型值fload_3 从局部变量3中装载float类型值dload_0 从局部变量0中装载double类型值dload_1 从局部变量1中装载double类型值dload_2 从局部变量2中装载double类型值dload_3 从局部变量3中装载double类型值aload_0 从局部变量0中装载引用类型值aload_1 从局部变量1中装载引用类型值aload_2 从局部变量2中装载引用类型值aload_3 从局部变量3中装载引用类型值iaload 从数组中装载int类型值laload 从数组中装载long类型值faload 从数组中装载float类型值daload 从数组中装载double类型值aaload 从数组中装载引用类型值baload 从数组中装载byte类型或boolean类型值caload 从数组中装载char类型值saload 从数组中装载short类型值将栈中的值存入局部变量的指令istore 将int类型值存入局部变量lstore 将long类型值存入局部变量fstore 将float类型值存入局部变量dstore 将double类型值存入局部变量astore 将将引用类型或returnAddress类型值存入局部变量istore_0 将int类型值存入局部变量0istore_1 将int类型值存入局部变量1istore_2 将int类型值存入局部变量2istore_3 将int类型值存入局部变量3lstore_0 将long类型值存入局部变量0lstore_1 将long类型值存入局部变量1lstore_2 将long类型值存入局部变量2lstore_3 将long类型值存入局部变量3fstore_0 将float类型值存入局部变量0fstore_1 将float类型值存入局部变量1fstore_2 将float类型值存入局部变量2fstore_3 将float类型值存入局部变量3dstore_0 将double类型值存入局部变量0dstore_1 将double类型值存入局部变量1dstore_2 将double类型值存入局部变量2dstore_3 将double类型值存入局部变量3astore_0 将引用类型或returnAddress类型值存入局部变量0astore_1 将引用类型或returnAddress类型值存入局部变量1astore_2 将引用类型或returnAddress类型值存入局部变量2astore_3 将引用类型或returnAddress类型值存入局部变量3iastore 将int类型值存入数组中lastore 将long类型值存入数组中fastore 将float类型值存入数组中dastore 将double类型值存入数组中aastore 将引用类型值存入数组中bastore 将byte类型或者boolean类型值存入数组中castore 将char类型值存入数组中sastore 将short类型值存入数组中wide指令wide 使用附加字节扩展局部变量索引通用(无类型)栈操作nop 不做任何操作pop 弹出栈顶端一个字长的内容pop2 弹出栈顶端两个字长的内容dup 复制栈顶部一个字长内容dup_x1 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的两个字长的内容压入栈dup_x2 复制栈顶部一个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈dup2 复制栈顶部两个字长内容dup2_x1 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的三个字长的内容压入栈dup2_x2 复制栈顶部两个字长的内容,然后将复制内容及原来弹出的四个字长的内容压入栈swap 交换栈顶部两个字长内容类型转换i2l 把int类型的数据转化为long类型i2f 把int类型的数据转化为float类型i2d 把int类型的数据转化为double类型l2i 把long类型的数据转化为int类型l2f 把long类型的数据转化为float类型l2d 把long类型的数据转化为double类型f2i 把float类型的数据转化为int类型f2l 把float类型的数据转化为long类型f2d 把float类型的数据转化为double类型d2i 把double类型的数据转化为int类型d2l 把double类型的数据转化为long类型d2f 把double类型的数据转化为float类型i2b 把int类型的数据转化为byte类型i2c 把int类型的数据转化为char类型i2s 把int类型的数据转化为short类型整数运算iadd 执行int类型的加法ladd 执行long类型的加法isub 执行int类型的减法lsub 执行long类型的减法imul 执行int类型的乘法lmul 执行long类型的乘法idiv 执行int类型的除法ldiv 执行long类型的除法irem 计算int类型除法的余数lrem 计算long类型除法的余数ineg 对一个int类型值进行取反操作lneg 对一个long类型值进行取反操作iinc 把一个常量值加到一个int类型的局部变量上逻辑运算移位操作ishl 执行int类型的向左移位操作lshl 执行long类型的向左移位操作ishr 执行int类型的向右移位操作lshr 执行long类型的向右移位操作iushr 执行int类型的向右逻辑移位操作lushr 执行long类型的向右逻辑移位操作按位布尔运算iand 对int类型值进行“逻辑与”操作land 对long类型值进行“逻辑与”操作ior 对int类型值进行“逻辑或”操作lor 对long类型值进行“逻辑或”操作ixor 对int类型值进行“逻辑异或”操作lxor 对long类型值进行“逻辑异或”操作浮点运算fadd 执行float类型的加法dadd 执行double类型的加法fsub 执行float类型的减法dsub 执行double类型的减法fmul 执行float类型的乘法dmul 执行double类型的乘法fdiv 执行float类型的除法ddiv 执行double类型的除法frem 计算float类型除法的余数drem 计算double类型除法的余数fneg 将一个float类型的数值取反dneg 将一个double类型的数值取反对象和数组对象操作指令new 创建一个新对象checkcast 确定对象为所给定的类型getfield 从对象中获取字段putfield 设置对象中字段的值getstatic 从类中获取静态字段putstatic 设置类中静态字段的值instanceof 判断对象是否为给定的类型数组操作指令newarray 分配数据成员类型为基本上数据类型的新数组anewarray 分配数据成员类型为引用类型的新数组arraylength 获取数组长度multianewarray 分配新的多维数组控制流条件分支指令ifeq 如果等于0,则跳转ifne 如果不等于0,则跳转iflt 如果小于0,则跳转ifge 如果大于等于0,则跳转ifgt 如果大于0,则跳转ifle 如果小于等于0,则跳转if_icmpcq 如果两个int值相等,则跳转if_icmpne 如果两个int类型值不相等,则跳转if_icmplt 如果一个int类型值小于另外一个int类型值,则跳转if_icmpge 如果一个int类型值大于或者等于另外一个int类型值,则跳转if_icmpgt 如果一个int类型值大于另外一个int类型值,则跳转if_icmple 如果一个int类型值小于或者等于另外一个int类型值,则跳转ifnull 如果等于null,则跳转ifnonnull 如果不等于null,则跳转if_acmpeq 如果两个对象引用相等,则跳转if_acmpnc 如果两个对象引用不相等,则跳转比较指令lcmp 比较long类型值fcmpl 比较float类型值(当遇到NaN时,返回-1)fcmpg 比较float类型值(当遇到NaN时,返回1)dcmpl 比较double类型值(当遇到NaN时,返回-1)dcmpg 比较double类型值(当遇到NaN时,返回1)无条件转移指令goto 无条件跳转goto_w 无条件跳转(宽索引)表跳转指令tableswitch 通过索引访问跳转表,并跳转lookupswitch 通过键值匹配访问跳转表,并执行跳转操作异常athrow 抛出异常或错误finally子句jsr 跳转到子例程jsr_w 跳转到子例程(宽索引)rct 从子例程返回方法调用与返回方法调用指令invokcvirtual 运行时按照对象的类来调用实例方法invokespecial 根据编译时类型来调用实例方法invokestatic 调用类(静态)方法invokcinterface 调用接口方法方法返回指令ireturn 从方法中返回int类型的数据lreturn 从方法中返回long类型的数据freturn 从方法中返回float类型的数据dreturn 从方法中返回double类型的数据areturn 从方法中返回引用类型的数据return 从方法中返回,返回值为void线程同步montiorenter 进入并获取对象监视器monitorexit 释放并退出对象监视器JVM指令助记符变量到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_操作数栈到变量:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_常数到操作数栈:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_加:iadd,ladd,fadd,dadd减:isub,lsub,fsub,dsub乘:imul,lmul,fmul,dmul除:idiv,ldiv,fdiv,ddiv余数:irem,lrem,frem,drem取负:ineg,lneg,fneg,dneg移位:ishl,lshr,iushr,lshl,lshr,lushr按位或:ior,lor按位与:iand,land按位异或:ixor,lxor类型转换:i2l,i2f,i2d,l2f,l2d,f2d(放宽数值转换)i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(缩窄数值转换)创建类实便:new创建新数组:newarray,anewarray,multianwarray访问类的域和类实例域:getfield,putfield,getstatic,putstatic把数据装载到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload从操作数栈存存储到数组:bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore获取数组长度:arraylength检相类实例或数组属性:instanceof,checkcast操作数栈管理:pop,pop2,dup,dup2,dup_xl,dup2_xl,dup_x2,dup2_x2,swap有条件转移:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,if_icmpeq,if_icmpene,if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne,lcmp,fcmplfcmpg,dcmpl,dcmpg复合条件转移:tableswitch,lookupswitch无条件转移:goto,goto_w,jsr,jsr_w,ret调度对象的实便方法:invokevirtual调用由接口实现的方法:invokeinterface调用需要特殊处理的实例方法:invokespecial调用命名类中的静态方法:invokestatic方法返回:ireturn,lreturn,freturn,dreturn,areturn,return异常:athrowfinally关键字的实现使用:jsr,jsr_w,ret
2023年02月11日
321 阅读
0 评论
0 点赞
JVM内存模型剖析与优化
JDK体系结构Java语言的跨平台特性JVM整体结构及内存模型补充一个问题:在minor gc过程中对象挪动后,引用如何修改?对象在堆内部挪动的过程其实是复制,原有区域对象还在,一般不直接清理,JVM内部清理过程只是将对象分配指针移动到区域的头位置即可,比如扫描s0区域,扫到gcroot引用的非垃圾对象是将这些对象复制到s1或老年代,最后扫描完了将s0区域的对象分配指针移动到区域的起始位置即可,s0区域之前对象并不直接清理,当有新对象分配了,原有区域里的对象也就被清除了。minor gc在根扫描过程中会记录所有被扫描到的对象引用(在年轻代这些引用很少,因为大部分都是垃圾对象不会扫描到),如果引用的对象被复制到新地址了,最后会一并更新引用指向新地址。这里面内部算法比较复杂,感兴趣可以参考R大的这篇文章:https://www.zhihu.com/question/42181722/answer/145085437https://hllvm-group.iteye.com/group/topic/39376#post-257329JVM内存参数设置Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar-Xss:每个线程的栈大小-Xms:设置堆的初始可用大小,默认物理内存的1/64 -Xmx:设置堆的最大可用大小,默认物理内存的1/4-Xmn:新生代大小-XX:NewRatio:默认2表示新生代占年老代的1/2,占整个堆内存的1/3。-XX:SurvivorRatio:默认8表示一个survivor区占用1/8的Eden内存,即1/10的新生代内存。关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M左右,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-XX:PermSize参数意思不一样,-XX:PermSize代表永久代的初始容量。由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。StackOverflowError示例:// JVM设置 -Xss128k(默认1M) public class StackOverflowTest { static int count = 0; static void redo() { count++; redo(); } public static void main(String[] args) { try { redo(); } catch (Throwable t) { t.printStackTrace(); System.out.println(count); } } } 运行结果: java.lang.StackOverflowError at com.appom.jvm.StackOverflowTest.redo(StackOverflowTest.java:12) at com.appom.jvm.StackOverflowTest.redo(StackOverflowTest.java:13) at com.appom.jvm.StackOverflowTest.redo(StackOverflowTest.java:13) ......结论:-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多JVM内存参数大小该如何设置?JVM参数大小设置并没有固定标准,需要根据实际项目情况分析,给大家举个例子日均百万级订单交易系统如何设置JVM参数结论:通过上面这些内容介绍,大家应该对JVM优化有些概念了,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。
2023年02月11日
329 阅读
0 评论
0 点赞
深入理解JVM类加载机制
类加载运行全过程当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM。package com.appom.jvm; public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { // 一个方法对应一块栈帧内存区域 int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); } }通过Java命令执行代码的大体流程如下:其中loadClass的类加载过程有如下几步:加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口验证:校验字节码文件的正确性准备:给类的静态变量分配内存,并赋予默认值解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用,下节课会讲到动态链接初始化:对类的静态变量初始化为指定的值,执行静态代码块类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。类加载器的引用:这个类到类加载器实例的引用对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。注意,主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。public class TestDynamicLoad { static { System.out.println("*************load TestDynamicLoad************"); } public static void main(String[] args) { new A(); System.out.println("*************load test************"); B b = null; //B不会加载,除非这里执行 new B() } } class A { static { System.out.println("*************load A************"); } public A() { System.out.println("*************initial A************"); } } class B { static { System.out.println("*************load B************"); } public B() { System.out.println("*************initial B************"); } } 运行结果: *************load TestDynamicLoad************ *************load A************ *************initial A************ *************load test************类加载器和双亲委派机制上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类自定义加载器:负责加载用户自定义路径下的类包看一个类加载器示例:public class TestJDKClassLoader { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassloader = appClassLoader.getParent(); ClassLoader bootstrapLoader = extClassloader.getParent(); System.out.println("the bootstrapLoader : " + bootstrapLoader); System.out.println("the extClassloader : " + extClassloader); System.out.println("the appClassLoader : " + appClassLoader); System.out.println(); System.out.println("bootstrapLoader加载以下文件:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassloader加载以下文件:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader加载以下文件:"); System.out.println(System.getProperty("java.class.path")); } } 运行结果: null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc bootstrapLoader加载以下文件: file:/D:/home/Java/jdk1.8.0_45/jre/lib/resources.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/rt.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/jsse.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/jce.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/charsets.jar file:/D:/home/Java/jdk1.8.0_45/jre/lib/jfr.jar file:/D:/home/Java/jdk1.8.0_45/jre/classes extClassloader加载以下文件: D:\home\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext appClassLoader加载以下文件: D:\home\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\home\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:\home\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\home\Java\jdk1.8.0_45\jre\lib\javaws.jar;D:\home\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\home\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\home\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\home\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\home\Java\jdk1.8.0_45\jre\lib\management-agent.jar;D:\home\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\home\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\home\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\Workspace\appom-jvm\target\classes;C:\Users\liulei\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\liulei\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\liulei\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\liulei\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\liulei\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\liulei\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\liulei\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\liulei\.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\liulei\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\liulei\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\liulei\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\liulei\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\home\JetBrains\IntelliJ IDEA 2022.1.3\lib\idea_rt.jar类加载器初始化过程:参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用类加载器)。JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。//Launcher的构造方法 public Launcher() { Launcher.ExtClassLoader var1; try { // 构造扩展类加载器,在构造的过程中将其父加载器设置为null var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { // 构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader, // Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); // 省略一些不需关注代码 }双亲委派机制JVM类加载器是有亲子层级结构的,如下图这里类加载其实就有一个双亲委派机制,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载我们来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。//ClassLoader的loadClass方法,里面实现了双亲委派机制 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查当前类加载器是否已经加载了该类 Class<?> c = findLoadedClass(name); 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(); // 都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 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); } return c; } }为什么要设计双亲委派机制?沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性看一个类加载示例:package java.lang; public class String { public static void main(String[] args) { System.out.println("**************My String Class**************"); } } 运行结果: 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application全盘负责委托机制“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。自定义类加载器示例:自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); // defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } } public static void main(String args[]) throws Exception { // 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader MyClassLoader classLoader = new MyClassLoader("D:/Workspace/appom-jvm"); // D盘创建 appom-jvm/com/appom/jvm 几级目录,将User类的复制类User1.class丢入该目录 Class clazz = classLoader.loadClass("com.appom.jvm.User"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } 运行结果: =======自己的加载器加载类调用方法======= com.appom.jvm.MyClassLoaderTest$MyClassLoader打破双亲委派机制再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的 java.lang.String.classpublic class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 * @param name * @param resolve * @return * @throws ClassNotFoundException */ 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); if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/Workspace/appom-jvm"); //尝试用自己改写类加载机制去加载自己写的java.lang.String.class Class clazz = classLoader.loadClass("java.lang.String"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } 运行结果: java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659) at java.lang.ClassLoader.defineClass(ClassLoader.java:758)Tomcat打破双亲委派机制以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行? 答案是不行的。为什么?第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。第三个问题和第一个问题一样。我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。Tomcat自定义加载器详解Tomcat的几个主要类加载器:commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本;从图中的委派关系中可以看出:CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而CatalinaClassLoader和SharedClassLoader自己能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热加载功能。tomcat 这种类加载机制违背了java 推荐的双亲委派模型了吗?答案是:违背了。 很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。模拟实现Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 * @param name * @param resolve * @return * @throws ClassNotFoundException */ 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); if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //非自定义的类还是走双亲委派加载 if (!name.startsWith("com.appom.jvm")){ c = this.getParent().loadClass(name); }else{ c = findClass(name); } // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/Workspace/appom-jvm"); Class clazz = classLoader.loadClass("com.appom.jvm.User1"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader()); System.out.println(); MyClassLoader classLoader = new MyClassLoader("D:/Workspace/appom-jvm1"); Class clazz1 = classLoader1.loadClass("com.appom.jvm.User1"); Object obj1 = clazz1.newInstance(); Method method1= clazz1.getDeclaredMethod("sout", null); method1.invoke(obj1, null); System.out.println(clazz1.getClassLoader()); } } 运行结果: =======自己的加载器加载类调用方法======= com.appom.jvm.MyClassLoaderTest$MyClassLoader@266474c2 =======另外一个User1版本:自己的加载器加载类调用方法======= com.appom.jvm.MyClassLoaderTest$MyClassLoader@66d3c617注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。模拟实现Tomcat的JasperLoader热加载原理:后台启动线程监听jsp文件变化,如果变化了找到该jsp对应的servlet类的加载器引用(gcroot),重新生成新的JasperLoader加载器赋值给引用,然后加载新的jsp对应的servlet类,之前的那个加载器因为没有gcroot引用了,下一次gc的时候会被销毁。附下User类的代码:package com.appom.jvm; public class User { private int id; private String name; public User() { } public User(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void sout() { System.out.println("=======自己的加载器加载类调用方法======="); } }
2023年02月10日
283 阅读
0 评论
0 点赞