ClassLoader, JavaAgent, Aspectj Weaving一站式扫盲帖
最近工作里复习的Class Loader基础知识集锦,写下来希望对别人有帮助,而且不止是为了撂倒面试官。
为了尽量简单明了容易背,有些部分写得比较干。
0. 参考资料:
- 书:《深入了解Java虚拟机》、《实战Java虚拟机》
- 规范: Java语言规范 第12章
- 源码: OpenJDK 7 的Java及C代码( class.c , classloader.c,jvm.cpp)
1. Class装载的三个阶段
1.1 载入 (Load)
从Class文件或别的什么地方载入一段二进制流字节流,把它解释成永久代里的运行时数据结构,生成一个Class对象。
1.2 链接 (Resolve)
将之前载入的数据结构里的符号引用表,解析成直接引用。
中间如果遇到引用的类还没被加载,就会触发该类的加载。
可能JDK会很懒惰的在运行某个函数实际使用到该引用时才发生链接,也可能在类加载时就解析全部引用。
1.3 初始化 (Initniazle)
初始化静态变量,并执行静态初始化语句。
2. Class装载的时机
- ClassLoader.loadClass()
- 前文所说的链接时触发的装载
- Class.forName() 等java.lang.reflect反射包
- new 构造对象
- 初始化子类时,会同时初始化父类
- 访问类的静态变量或静态方法(但static final的常量除外,此君在常量池里)
本质上,也是很懒惰的按需加载的,由于类装载的Lazy和前面解释引用的Lazy,所以Jar包里有时候有些类用到的了没在Class Path里的其他类,也能人品爆发的照跑不误。
除了1,其他几种方式默认都到达类装载的初始化阶段。
3. ClassLoader.loadClass() 与 Class.forName()
ClassLoader.loadClass(String name, boolean resolve),其中resolve默认为false,即只执行类装载的第一个阶段。
Class.forName(String name, boolean initialize, ClassLoader loader), 其中initialize默认为true,即执行到类装载的第三个阶段。
4. ClassNotFoundException 和 NoClassDefFoundError
ClassLoader.loadClass() 与 Class.forName() 找不到类定义的二进制流时抛出ClassNotFoundException。
链接阶段解释引用失败,找不到引用的类时抛出NoClassDefFoundError。
5. ClassLoader及双亲委派机制
ClassLoader.loadClass()的标准流程:
- findLoadedClass() 查看类是否已加载
- 如果不存在,则调用parent loader的loadClass()
- 如果不存在,调用findClass() 在本ClassLoader的ClassPath里加载该类
所谓双亲委派机制,就是先从parent loader开始查找,找不到了才用自己的findClass()函数去查找,兼顾了效率:避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次,和安全,避免子类乱加载。
而OSGI或SPI或热替换方案,则需要破坏这个双亲委托,先调用自己的findClass()。
findClass() 是各个ClassLoader各自实现,各显神通的地方,从各种奇葩地方载入Class二进制字节流。
但最后都会调用defineClass(),传入二进制字节流,返回Class对象。留意此处,呆会AspectJ的时候会回到这里。
在JDK6,loadClass()很过分的定义了方法级的synchronized ,在JDK7改成一个以Class Name作Key的 parallelLockMap,增强了并行加载不同Class的能力。
6. System ClassLoader 与 Thread Context Classloader
有时候,看到错误日志说张三不是张三,包名类名一样但instanceof 死活返回 false,唯一原因是它们由两个不同的ClassLoader加载。
默认的Bootstrap(加载jdk的lib目录),Extension(加载jdk的lib/ext目录),Application(加载启动时定义的classpath)三层ClassLoader机制不再重复。
平时用ClassLoader.getSystemClassLoader()就可以得到sun.misc.Launcher$ApplicationClassLoader 这个Application ClassLoader。
在类A里加载类B,默认使用加载了类A的Loader。但,也有特殊情况,比如JDBC加载driver时的机制,需要在父 ClassLoader(JDBC属于JDK一部分)里根据配置反射创建jdbc driver的数据实现类,Sun设计了一个特殊方案 --Thread Context Class Loader。
JAXB(比如要在Jar包里找xsd schema文件的时候)也使用了它,所以用到它们时就要注意Thread Context ClassLoader的设置,可以用代码随时设置current thread的loader,也可以用自定义的ThreadFactory在创建线程时设置,它默认是父线程的loader,如果都没设置就是 System ClassLoader。
7. Java Agent机制与AspectJ的LoadTime Weaving
在JDK5开始,在启动JVM时可增加-javaagent参数,在装载Class时对类进行动态的修改。
AspectJ的Load Time Weaving机制,需要配置 -javaagent: [path to aspectj-weaver.jar] 。
打开aspectj-weaver.jar,可以看到META-INF/MANIFEST里定义了 Premain-Class: org.aspectj.weaver.loadtime.Agent
再打开这个Agent类,简化后的代码大概这个样子:
ClassFileTransformer s_transformer = new ClassPreProcessorAgentAdapter(); public static void premain(String options, Instrumentation instrumentation) { instrumentation.addTransformer(s_transformer); }
可见它的主要作用是将自己的类转换器注册到JDK所传入的Instrumentation。
再看ClassFileTransformer的定义:ClassLoader会在前面defineClass()的过程中,在把二进制字节流转换为Class对象之前,先把二进制流和当前ClassLoader传给Transformer,由Transformer加工为另一段二进制字节流返回。
AspectJ就是利用传入的ClassLoader,找出其Class Path里的META-INF/aop.xml,然后根据aop.xml里的配置进行代码植入。
测试显示,加了LoadTime Weaving,类加载的速度明显变慢,如果是100ms就调用超时的服务,需要做类的预加载。
8. Jar包的预加载
比如有个有趣的需求是加载某个Class A所在的Jar里的全部的Class (怎么好像一点都不有趣)
URL jarUrl = ClassA.getProtectionDomain().getCodeSource().getLocation(); JarFile jarfile = new JarFile(jarUrl.getPath()); Enumeration entries = jarfile.entries();
然后遍历JarEntry,过滤出后缀为.class的文件,按类名进行装载就可以了。
9.Class的二进制兼容性
如果Class A 依赖 spring-1.0.jar编译,当spring升级到spring-2.0.jar,Class A不需要修改代码也不需要重新编译,可以直接运行的,spring-2.0.jar就满足二进制兼容性。
在 Java语言规范的第13章 有详细的描述 ,不想直接睡着最好可以找个中文版来看,感谢那些翻译的同学。
虽然规范的这章看着比较长比较吓人,但其实二进制兼容性还是很容易做到的,只要你不做把接口改为抽象类之类奇怪的事情,其他一些看起来很大的改动,比如改throws定义,其实都没有问题。
真的遇到问题,设身处地想想自己是那段Class A的字节码,现在还能不能跑就行。
感谢你看到这里,希望你只在工作里用到这些知识,祝工作愉快。
ClassLoader, JavaAgent, Aspectj Weaving一站式扫盲帖的更多相关文章
- 【腾讯bugly干货分享】HTML 5 视频直播一站式扫盲
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1277 视频直 ...
- HTTPS科普扫盲帖
为什么需要https HTTP是明文传输的,也就意味着,介于发送端.接收端中间的任意节点都可以知道你们传输的内容是什么.这些节点可能是路由器.代理等. 举个最常见的例子,用户登陆.用户输入账号,密码, ...
- HTTPS科普扫盲帖 对称加密 非对称加密
http://www.cnblogs.com/chyingp/p/https-introduction.html
- [转]最详细的 HTTPS 科普扫盲帖
转载自http://www.codeceo.com/article/https-knowledge.html 为什么需要https HTTP是明文传输的,也就意味着,介于发送端.接收端中间的任意节点都 ...
- 最详细的 HTTPS 科普扫盲帖
为什么需要https HTTP是明文传输的,也就意味着,介于发送端.接收端中间的任意节点都可以知道你们传输的内容是什么.这些节点可能是路由器.代理等. 举个最常见的例子,用户登陆.用户输入账号,密码, ...
- hadoop高速扫盲帖,从零了解hadoop
1.MapReduce理论简单介绍 1.1 MapReduce编程模型 MapReduce採用"分而治之"的思想,把对大规模数据集的操作,分发给一个主节点管理下的各个分节点共同完毕 ...
- HTTPS科普扫盲帖【转】
为什么需要https HTTP是明文传输的,也就意味着,介于发送端.接收端中间的任意节点都可以知道你们传输的内容是什么.这些节点可能是路由器.代理等. 举个最常见的例子,用户登陆.用户输入账号,密码, ...
- Android 性能测试之内存 --- 追加腾讯性能案例,安卓抓取性能扫盲帖
内存测试: 思路 目前做的是酒店APP,另下载安装几个个第三方酒店的APP以方便对比(相当于可以做竞品测试) 数据的获取来源是ADB底层命令,而且最好是不需要root权限,因为很多手机root很麻烦或 ...
- java一些对象概念扫盲帖(DO VO DTO PO)
资料来源:http://virusswb.blog.51cto.com/115214/458636 BO:Business Object,业务对象.主要是承载业务数据的实体.处理业务逻辑的时候使用,数 ...
随机推荐
- redis 性能监控和排查
最近项目中接连遇到redis出现瓶颈的问题,现在把排查的一些经验记录下来备查,本篇只是思路的整理,不涉及具体的使用. 大体的思路如下: 1.通过slow log查看 参考 http://www.cnb ...
- jquery中事件重复绑定以及解绑问题
一般的情况下,对于这种情况,我们常规的思路是,先解绑,再绑定,如下: $(selector).unbind('click').bind('click',function(){....}); 当这样会有 ...
- Hadoop常用命令汇总
启动Hadoop 进入HADOOP_HOME目录. 执行sh bin/start-all.sh 关闭Hadoop 进入HADOOP_HOME目录. 执行sh bin/stop-all.sh 1.查看指 ...
- 区分jquery中的offset和position
一次又一次地碰到需要获取元素位置的问题, 然后一次又一次地查offset和position的区别. 忍不了了, 这次一定得想办法记下来. position是元素相对于父元素的位置. 这个好记, par ...
- Git教程(11)把本地的项目传到远程
1,在远程建立仓库 得到远程仓库地址,如: https://github.com/paulboone/ticgit 2,进入到项目根目录,初始化一个本地仓库 $ git init 3,为本地仓库添加 ...
- delphi使用 第三方控件
第三方控件安装时必须把所有的pas,dcu,dpk,res等文件复制到你的Lib目录下 然后通过dpk进行安装 安装后会多出来新的控件面板,新控件就在那里了 当然也有一些控件会安装到原有的面板上 比如 ...
- javascript 小日历
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx. ...
- Codeforces 364A - Matrix
原题地址:http://codeforces.com/problemset/problem/364/A 题目大意: 给定一个数字a(0 ≤ a ≤ 109)和一个数列s(每个数都是一位,长度不超过40 ...
- storm - 基础概念整理
理论 Hadoop的出现虽然为大数据计算提供了一条捷径,但其仍然存在自身难以克服的缺点:实时性不足.Hadoop的一轮计算的启动需要较长时间,因此其满足不了对实时性有较高要求的场景. Storm由此应 ...
- windows获取窗口句柄
1.使用FindWindow函数获取窗口句柄 示例:使用FindWindow函数获取窗口句柄,然后获得窗口大小和标题,并且移动窗口到指定位置. #include <Windows.h> # ...