一、虚拟机性能监控与故障处理工具
1、jps,虚拟机进程状况工具。
使用: jps -l 输出主类的命名
jps -v 输出虚拟机进程启动时JVM参数
Jps -m 输出主类main()函数的参数
2、jstat ,虚拟机统计信息监视工具。
使用:jstat -gcutil VMID ,VMID为使用jps查看到的虚拟机id。
-gcutil 监视java堆状况、gc时间合计等信息。
3、jinfo,java配置信息工具
使用:jinfo VMID
4、jmap,java内存映像工具
使用:jmp -dump:form=b,file=sts.bin VMID
5、jhat 虚拟机堆存储快照分析工具
使用: jhat sts.bin
分析内存泄漏,最底部Heap Histogram 标签。
6、jstack,java堆栈跟踪工具。
使用: jstack -l VMID
7、jconsole,java监视与管理控制台。
8、jvisualvm ,多合一故障处理工具。
二、类文件结构(使用WinHex查看class文件)
1、两种数据类型:无符号数(u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节)和表。
2、每个class文件的头4个字节称为魔数,值为0xCAFEBABE。
3、第5和第6个字节是次版本号,第7和第8个字节是主版本号。Java的版本号是从45开始的,jdk1.1之后每个JDK大版本主版本号向上加1,最新的JDK版本为1.7,可生成Class文件主版本号的最大值为51.0。
4、紧接着主次版本号之后的是常量池入口。常量池中常量的数量是不固定的,因而在常量池入口需放置一项u2(两个字节)类型的数据,代表常量池容量计数值(索引计数从1开始,所以这个十进制值应为这个u2字的十进制值减去一)。
5、常量池主要存放两大类常量:字面量(如文本字符串、final常量值)、符号引用(包括类、接口的全限定名、字段或方法的名称和描述符)。
6、常量池中的每一项常量都是一个表,共有11种结构各不相同的表结构数据,其表开始的第一位是个u1类型的标志位,代表当前这个常量属于哪种常量类型。
7、javap -verbose 类名,输出字节码内容。
8、在常量池结束之后,紧接着访问标志、类索引(用于确定这个类的全限定名)、父索引与接口索引集合、字段集合、方法集合。
三、虚拟机类加载机制
1、类型的加载和连接(包括验证、准备、解析)过程都是在程序运行期间完成。
2、对于静态字段(非final),只有直接定义这个字段的类及其父类才会被初始化(包括执行静态代码块和构造函数)。在这里只执行静态代码块,只有当new时才执行静态代码块和构造函数,其顺序是:父类静态代码块—>子类静态代码块—>父类构造函数—>子类构造函数。
3、即使两个类是来源于同一个class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。
4、两种不同的类加载器:一是启动类加载器,这个类加载器使用C++实现,是虚拟机自身的一部分;二是继承自ClassLoader的类加载器,独立于虚拟机外部。
5、双亲委派模型:若一个类加载器收到类加载请求,则先把这请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去加载。
6、定制ClassLoader
# 调用 findLoadedClass 来查看是否存在已装入的类。
# 如果没有,那么采用某种方式(如读取文件或网络加载)来获取原始字节。
# 如果已有原始字节,调用defineClass(final类型,不可重写)将它们转换成Class对象。
# 如果没有原始字节,然后调用findSystemClass查看是否从本地文件系统获取类。
# 如果resolve参数是true,那么调用resolveClass解析Class对象。
# 如果还没有类,返回ClassNotFoundException。
# 否则,将类返回给调用程序。
以下为ClassLoader的loadClass方法,可见其与findClass、resovleClass的关系。
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) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } 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.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
可见一般情况下自定义的classloader只须重写findClass即可。
示例:
package xjt.loader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class XjtClassLoader extends ClassLoader { @Override protected Class<?> findClass(String fulClassName) throws ClassNotFoundException { byte[] data = loadClassData(fulClassName); return defineClass(fulClassName, data, 0, data.length); } private byte[] loadClassData(String fulClassName) { FileInputStream fis = null; byte[] data = null; try { fis = new FileInputStream(new File( "E:/start/sts/exe/bin/" + fulClassName.replace('.', File.separatorChar) + ".class")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int ch = 0; while ((ch = fis.read()) != -1) { baos.write(ch); } data = baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { } } return data; } }
四、ASM
1、其它修改类行为的方式:a、java.lang.instrument包用于监控和控制虚拟机的行为;b、Proxy代理编程,但其只面向接口。
2、ASM通过树这种数据结构来表示复杂的字节码结构,并利用Push模型来对树进行遍历,在遍历过程中对字节码进行修改。
3、在 ASM 中,提供了一个 ClassReader类,这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码。它会调用 accept方法,这个方法接受一个实现了 ClassVisitor接口的对象实例作为参数,然后依次调用 ClassVisitor接口的各个方法。ClassVisitor会产生一些子过程,比如 visitMethod会返回一个实现 MethordVisitor接口的实例,visitField会返回一个实现 FieldVisitor接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。ClassAdaptor类实现了 ClassVisitor接口所定义的所有函数。
注:新版的asm不再提供ClassAdaptor类,而ClassVisitor改为无抽象方法的抽象类。
4、ASM 的最终的目的是生成可以被正常装载的 class 文件,因此其框架结构为客户提供了一个生成字节码的工具类 —— ClassWriter。它继承自ClassVisitor,而且含有一个 toByteArray()函数,返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件。