JVM、ASM

一、虚拟机性能监控与故障处理工具

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、u4u8分别代表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 来查看是否存在已装入的类。 
如果没有,那么采用某种方式(如读取文件或网络加载)来获取原始字节。 
如果已有原始字节,调用defineClassfinal类型,不可重写)将它们转换成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 文件。



你可能感兴趣的:(jvm,ASM)