运用《深入理解Java虚拟机》书中知识解决实际问题
前言
以前看别人博客说看完《深入理解Java虚拟机》这本书并没有让自己的编程水平提高多少,不过却大大提高了自己的装逼水平。其实,我倒不这么认为,至少在我看完一遍这本书后,有一种醍醐灌顶的感觉,很多模糊的知识和概念也变得清晰起来。今天,也是偶然的机会能够运用书中所学的知识解决实际问题,在这里,与大家分享一下,如有不正确的地方,还请指正。
问题描述
预生产环境突然出现了一个运行时异常,异常信息如下(Error异常):
java.lang.NoClassDefFoundError: javax/servlet/ServletOutputStream
at com.soa.xxx.ProductTransForm.transProduct(ProductTransForm.java:10)
......
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletOutputStream
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
报异常的代码如下(根据真实项目场景模拟代码):
public class ProductTransForm { public ProductRespVo transProduct(ProductVo productVo) { ProductRespVo productRespVo = new ProductRespVo();
productRespVo.setProId(productVo.getProId());
productRespVo.setName(productVo.getName()); // TODO:注意下面这行代码,出问题的代码
productRespVo.setImage(FtpUtil.getFtpPath() + File.separator + productVo.getImage()); return productRespVo;
}
}
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class FtpUtil { public static String getFtpPath() {
return "The path of Ftp";
} public static void downloadFile(HttpServletRequest req, HttpServletResponse resp) { // 下载代码逻辑
}
}
问题出在静态方法调用:FtpUtil.getFtpPath(),初看之下,并没有什么问题,静态方法getFtpPath()只是简单地返回一个地址字符串。
原因分析
经过各种尝试、调试以及重新打包等都没有能解决问题。这时候,突然想到《深入理解Java虚拟机》中有关Java类的初始化机制中讲到过类的初始化时机,因为FtpUtil类的getFtpPath()方法为静态方法,而调用一个类的静态方法会触发其初始化,带着这个设想,我写下了以下一行代码:
FtpUtil ftpUtil = new FtpUtil();
启动运行,果然重现了错误。既然原因是出在FtpUtil类的初始化上,那么从FtpUtil这个类着手分析,异常信息显示找不到ServletOutputStream类的定义,而在引入的包"javax.servlet.http.HttpServletResponse"的父接口也确实找到了对ServletOutputStream类的引用,但奇怪的是该类所在的包:servlet-api.jar是有引入的,否则也不能正常导入"javax.servlet.http.HttpServletResponse"包,于是猜测可能是jar包冲突,查看工程,发现工程中确实存在多个不同版本的servlet-api.jar(历史原因):
因此猜测是servlet jar包冲突导致的。
问题解决
定位了原因之后,首先想到的就是《深入理解Java虚拟机》书中讲到过的类的加载机制和双亲委派模型:
“如果一个类加载器收到类收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。”。从上图可以看到,由于启动类加载器和扩展类加载器的搜索范围内都没有servlet-api.jar包,所以无法加载ServletOutputStream类,因此,应用程序类加载器会尝试自己加载类ServletOutputStream,而ClassPath范围内存在多个不同版本的servlet-api.jar包,所以出现包冲突。
基于以上分析,我将一个servlet-api.jar包拷贝到JRE/lib/ext路径下,这样,扩展类加载器能够加载拷贝jar包中的ServletOutputStream类,应用程序加载器就不会再去加载ServletOutputStream类,也就不会冲突了。经过重启程序验证,果然没有再抛异常了。
从上图也可以看出,为什么我们不能够自己定义一些与JDK类名、路径完全一样的类来覆盖JDK的类(如String),因为这些类在rt.jar中,由启动类加载器加载,我们自己定义的同名同路径类根本没有加载的机会,也就不可能覆盖JDK的类了。记得有一场面试,面试官问道:我们有一个项目需要在不同的JDK版本运行,如果保证jar的兼容不冲突?想来也是想考这方面的知识吧。
补充:
一、类的初始化时机
虚拟机规范严格规定了有且只有5种情况必须立即对类进行初始化:
- 遇到new、getstatic、putstatic或invokestatic这4个字节码指令时,如果类没有经过初始化,则需要触发其初始化;
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,则需要触发其初始化;
- 当初始化一个类时,如果发现它的父类没有进行过初始化,则需要先触发其父类的初始化;
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类;
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invokke.MethodHandle实例最后解析的结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
二、类加载器
1、启动类加载器(Bootstrap ClassLoader)
负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放到lib目录中也不会被加载)类库加载到虚拟机内存中。
2、扩展类加载器(Extension ClassLoader)
负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
3、应用程序类加载器(Application ClassLoader)
负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
运用《深入理解Java虚拟机》书中知识解决实际问题的更多相关文章
- 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略
第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收. 3.2 对象已死吗 ...
- 深入理解Java虚拟机(类文件结构)
深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_P ...
- Java内存区域与内存溢出异常——深入理解Java虚拟机 笔记一
Java内存区域 对比与C和C++,Java程序员不需要时时刻刻在意对象的创建和删除过程造成的内存溢出.内存泄露等问题,Java虚拟机很好地帮助我们解决了内存管理的问题,但深入理解Java内存区域,有 ...
- 《深入理解Java虚拟机》第2版挖的坑终于在第3版中被R大填平了
这是why技术的第34篇原创文章 本周还是在家办公的一周,上面的图就是我在家的工位,和上周<Dubbo Cluster集群那点你不知道的事>这篇文章里面的第一张图片比起来,升级了显示器支撑 ...
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
- 《深入理解Java虚拟机:JVM高级属性与最佳实践》读书笔记(更新中)
第一章:走进Java 概述 Java技术体系 Java发展史 Java虚拟机发展史 1996年 JDK1.0,出现Sun Classic VM HotSpot VM, 它是 Sun JDK 和 Ope ...
- 深入理解Java内存模型中的虚拟机栈
深入理解Java内存模型中的虚拟机栈 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都会有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启 ...
- 《深入理解JAVA虚拟机》笔记1
java程序运行时的内存空间,按照虚拟机规范有下面几项: )程序计数器 指示下条命令执行地址.当然是线程私有,不然线程怎么能并行的起来. 不重要,占内存很小,忽略不计. )方法区 这个名字很让我迷惑. ...
- 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的
概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...
随机推荐
- 转载 Java设计模式
java常用设计模式 1.strategy(策略模式)2.static Factory Method(静态工厂)3.singelton(单例模式)4.观察者模式(Observer)5.迭代器模式( ...
- spring+spring mvc+mybatis 实现主从数据库配置
一.配置文件 1.jdbc.properties master_driverUrl=jdbc:mysql://localhost:3306/shiro?useUnicode=true&char ...
- JavaScript高级程序设计(一)
一.三种常见的著名的命名规则: 1.Camel(驼峰式命名):首字母是小写的,接下来的单词都以大写字母开头.例如:var myTestValue=0; 2.Pascal(帕斯卡命名):首字母是大写的 ...
- python访问mysql
1,下载mysql-connector-python-2.0.4 pythoin访问mysql需要有客户端,这个就是连接mysql的库 解压后如下图: 双击lib 以windows为例 把mysql ...
- Maven项目打包为jar的几种方式
这里收集整理下以往打包MAVEN项目为JAR包的各种方式 直接打包,不打包依赖包 直接打包,不打包依赖包,仅打包出项目中的代码到JAR包中.在POM中添加如下plugin即可,随后执行maven in ...
- Mysql中外键的 Cascade ,NO ACTION ,Restrict ,SET NULL
外键约束对子表的含义: 如果在父表中找不到候选键,则不允许在子表上进行insert/update 外键约束对父表的含义: 在父表上进行update/delete以更新或删除在子表中有一条或多条对应匹配 ...
- ActiveMQ的使用
ActiveMQ使用分为两大块:生产者和消费者 一.准备 项目导入jar包:activemq-all-5.15.3.jar 并buildpath 二.生产者 创建连接工厂 ActiveMQCon ...
- ActiveX、OLE和COM的关系(转自百度文档)
比较流行的组件模型有COM(Component Object Model,对象组件模型)/DCOM(Distributed COM,分布式对象组件模型)和CORBA(Common Object Req ...
- 你不知道的JavaScript--Item13 理解 prototype, getPrototypeOf 和__proto__
1.深入理解prototype, getPrototypeOf和_ proto _ prototype,getPropertyOf和 _ proto _ 是三个用来访问prototype的方法.它们的 ...
- RabbitMQ (二)工作队列
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37620057 本系列教程主要来自于官网入门教程的翻译,然后自己进行了部分的修改与 ...