您的位置:首页 > 编程语言 > Java开发

深入理解JVM(二,JVM初识线程上下文类加载器和java字节码)

2019-04-21 23:37 253 查看

线程上下文类加载器的引入。
最好最常见的引入:对应得数据库连接包中

Class.forName("org.postgresql.Driver").newInstance();
String url ="jdbc:postgresql://localhost/myDB"
//myDB为数据库名
String user="myuser";
String password="mypassword";
Connection conn= DriverManager.getConnection(url,user,password);

通过上面可知,这些连接方式,肯定是最初已经加载了的,显然是bootstrap加载器已经将相关类加载。因此才能初始时我们可这样直接写(反问:什么都有依据,那么你凭什么就可以直接使用?原因就在早已经加载了Connection类,这些具体的定制都是由厂商制定的,我们只是将他放在了对应的位置,加载使用)
因此引入ContextClassLoader来破坏双亲委托机制,来实现上面的SPI加载。

package com.auto.demo;

/**
* 当前类加载器(current classloader):每个都会尝试用自己的类加载器去加载其他类
* 这就是下面是null的原因;
* ContextClassLoader(线程上下文加载器):Thread类中的getContextClassLoader与setContextClassLoader
* 用于获取和设置上下文加载器。
* 没有setContextClassLoader进行设置,默认线程将继承其父类的线程的上下文加载器。
* java应运行时初始线程的上下文类加载器是系统类加载器。
* 线程上下文加载器的重要性:
* ①  SPI(Service Provide Interface)
* ②  父ClassLoader可以使用当前线程Thread.currentThread.getContextoader()所指定的classloader加载的类。
* 这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的情况,
* 即是改变了双亲委托机制。
* ③  在双亲委托机制模型下,来加载器是由上至下的,即下层的类加载会委托上层进行加载。但是对于SPI来说,有些接口是java核心
* 库所提供的,而在java核心库是由启动类加载器加载的,而这些接口的实现却来自于不同的jar包(各大厂商提供),java的启动类加
* 载器是不会加载其他来源的jar包的,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器
* 来实现对于接口实现类的加载。(框架开发极其常用)
* @author zhouyi
*
*/
public class MyContextTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader()); // 系统类加载器
System.out.println(Thread.class.getClassLoader()); // 是由启动类加载加载,因此是null
}
}

运行结果:

public class MyContextTest implements Runnable {

private Thread thread;

public MyContextTest() {
thread = new Thread(this); // 因为是Runnable所以必须放入线程中
thread.start();
}

@Override
public void run() {
ClassLoader clLoader = this.thread.getContextClassLoader();
this.thread.setContextClassLoader(clLoader);
System.out.println("class: " + clLoader.getClass());
System.out.println("parent: " + clLoader.getParent().getClass());
}
public static void main(String[] args) {
new MyContextTest();
}
}

运行结果:

ServiceLoader
是服务提供者加载的集合,比如加载数据库连接,建议查看源码。

package com.auto.demo;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

/**
*线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
*获取:ClassLoader clLoader = this.thread.getContextClassLoader();
*使用:
*try {
* Thread.currentThread().setContextClassLoader(targetClassLoader);
* myMethod(); // 我们自己要做操作的方法
*} finally {
*  Thread.currentThread().getContextClassLoader(clLoader); // 还原
*}
*myMethod():调用了Thread.currentThread().getContextClassLoader(targetClassLoader);获取当前线程的上下文类加载器做某些事。
*
*当高层提供了统一的接口让低层实现,同时又要在高层加载(或实例化)低层的类时,
*就必须通过县城上下文类加载器来帮助高层的ClassLoader找到并加载。
* @author zhouyi
*
*/

public class MyContextTest {

public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
Iterator<Driver> it = loader.iterator();
while (it.hasNext()) {
Driver driver = it.next();
System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
}
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}

运行结果:

修改它的加载方式:(改为上层)

public class MyContextTest {

public static void main(String[] args) {
Thread.currentThread().setContextClassLoader(MyContextTest.class.getClassLoader().getParent());
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 拿到数据库连接驱动
Iterator<Driver> it = loader.iterator();
while (it.hasNext()) {
Driver driver = it.next();
System.out.println("driver: " + driver + ",loader: " + driver.getClass().getClassLoader());
}
System.out.println("当前线程上下文类加载器: " + Thread.currentThread().getContextClassLoader());
System.out.println("service的类加载器: " + ServiceLoader.class.getClassLoader());
}
}

运行结果:

关于线程上下文类加载器,扩展到Servlet、tomcat和框架搭载相关的知识,有时间一定要去研究研究

接下来是java字节码(前段时间这个是写完了的,可是在提交时csdn出了bug,导致内容全清空,只好重新来过。)
字节码文件结构解析
前提准备软件:(这是IDEA可运用的jclasslib,字节码分析工具)

安装完成后,如下:

另外还需要一个字节码查看以及编辑工具:(这里如果是windows系统请用winhex,如果是mac系统请用hex_friend)只需要导入相应的.class文件即可如下:

上面的是我们程序的字节码:

package com.bytecode.stu;

public class IOStudy {
int a = 1;

public int getA() {
return a;
}

public void setA(int b) {
this.a = b;
}
}

有了这些准备,我们开始分析。
1,使用java -verbose命令分析一个字节码文件,将会分析字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与类成员变量等信息,如下:

2,魔数(Magic number) : 所有的,class字节码文件的前4个字节都是魔数,魔数是固定值 : CA FE BA BE(上图查看,是这个JVM才会认可,执行)
3,版本号: 魔数之后的4个字节就是版本信息,次版本号是前两个字节:00 00 (Minor version),主版本号是后面2个字节:00 34(Major version)。由上面可知,34(16进制)对应的是52(10进制),即1.8.0于是去查看版本信息,结果如下:(_后的不是)
4,常量池(Constant Pool): 紧跟版本号之后的就是常量池入口,一个java类中定义的很多的信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池中。常量池主要存储两类常量:字面量和符号引用。字面量如文本字符串,java中声明为final的常量值等;符号引用如类和接口的全局限定名(前面反射的内容:0(default)、1(public)、2(private)、4(protected)),字段的名称和描述符,方法的名称和描述符等。

5,常量池的总体结构:java类所对应的常量池主要是由常量池数量与常量池数组(常量表)这两部分共同构成,常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后,常量池数组与一般的数组不同是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也不同的,但是每一个元素的第一个数据都是u1类型的,该字节是一个标志位,占据一个字符,JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型,值得注意的是,常量池数组中的元素个数 = 常量池 - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定的情况下需要表达【不引用任何一个常量池】的含义:其根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以,常量值的索引从1,而不是0。

这里涉及了u1,那么就讲讲class字节码结构中常量池中11种数据类型的结构总表(JVM如何识别字节码,返回为对饮的class,就是按照下面的规则)
常量池结构总表

例如:

6,在JVM规范中,每个变量/字段都有相应的描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包括数量、类型与顺序)与返回值,根据描述符规则,基本数据类型和代表无返回值的void类型都是用一个大写的字符来表示,对象类型则使用字符Lj加全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只是使用一个大写的字母来表示。如下:B - byte , C - char , D - double , F -float , I - int , J - long , S - short , Z - boolean , V - void , L - 对象全限定类型(如:Ljava/lang/String)。

7,对于数组类型来说,每个一维度使用一个前置的

[来表示,如int[] 被记录为:[I,String[][] 被记录为:[[Lj
1c13f
ava/lang/String;

8,用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法:

String getRealnamebyIdAndNickname(int id , String name);的描述符为:(I,Ljava/lang/String;)Ljava/lang/String

有了这些东西我们来都上面字节码:
紧跟在主版本号后面2个字节的常量池数量:00 19(10进制为24,有24个常量,根据这个去读),上面显示是25,也是和规则对一个的减一。
之后的是第一个常量。首先是描述符(第一个是u1类型):0A(10),去查看上面的常量池结构总表是CONSTANT_Methodref_info对应两个u2,即是00 04(声明方法的类描述符索引) 00 15(指向名称及类型描述符索引)。
接下来下一个常量,首先也是描述符:09(9),去查看上面的常量池结构总表是CONSTANT_Fieldref_info,对应两个u2,即是:00 03 0016(便不再做解释,自己去对应)
等等。。。。。。。。。。。。。。直到第24个读完为止,常量池结束。

完整的java字节码结构:

Access_Flag(访问标识符)
紧跟着是访问标识符这里的并包含全,比如上面的提到的private等,但是是可以并集的。比如上面000000F0行的 0×0021是由0×0020和0×0001的并集,表示为super和public。但是这里的归类依然是不全的额。

紧接着是This Class NameSuper Class Name这两个比较简单。分别都是占两个字节,分别是:00 03和00 04

紧接着就是接口interface,也是占两个字节,即是:00 00 ,没有接口

Fields(字段表)
再就是字段表,字段表是用于描述接口和类中声明的变量。这里的字段包含了类级别的变量以及实例变量,但是不包括方法的内部声明的局部变量。
除了下面给出的,首先要给出**fields_count,**是u2类型。即是 属性的数量:00 01
字段表结构:

对应上面的字段结构表可知: 00 02(access_flags)00 05(name_index) 00 06(decrptor_index) 00 00(attributes_count)
由于attributes_count是00 00所以最后一个不会再出现。
这里补充一点:凡是有_index的都是指向常量池的对应的项(需要转成10进制)

(方法表)Methods
接下来便是方法,其实和字段表的几乎一模一样。
field_count : 00 03,三个方法。method_info : 00 01 00 07 00 08 00 01(由于最后不为0,那么就有attribute_info)

那么我们就需要了解方法的属性结构(字段表也一样的)attribute_info

即是:attribute_name_index(00 09)和attribute_length(00 00 00 38)(这个56是code的内容),info是真正的code内容。

那么我们就得先看看Code,里面是我们方法的具体信息。我们引入:

先看看Code结构:他的作用是保存该方法的结构

attribute_length : 表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段。(这里需要注意前面已经有了这两个不会重复)
max_stack : (00 02;指向常量池2)表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。
mac_locals : (00 01)表示该方法执行期间创建的局部变量数目,包含用来表示传入的参数的局部变量。
code_length : ( 00 00 00 0A)表示该方法的所包含的字节码的字节数以及具体的指令码。具体的字节码即是该方法被调用时,虚拟机所执行的字节码。(注记:length都是往后数多少个字节,即是code的正真内容)
exception_table,这里存放的是处理异常的信息。

看看except_table的信息,他的组成如上表。
start_pc和end_pc表示在code数组中的从start_pc和end_pc处的指令抛出异常会有这个表象来处理。
handler_pc表示处理异常的代码块的开始处。catch_type表示湖北处理的异常类型,他指向常量池里面的一个异常类。当catch_type为0时,表示处理所有的异常。
接下来的对应最好借助工具,因为已经很难分析了。

首先,是方法,下面的助记符是可以点进去的,以后若有看不懂的,就这样区查看即可。这里介绍几种。






显然用这个工具很多东西都是一目了然,因此推荐使用这个工具去分析。但是如果你不懂其中的规则也是枉然。所以建议先是了解结构和基础,再去借助工具。

附加属性–LineNumberTable,简单来说就是Java代码对应的行号。用来还原代码的。结构如下:

实例图:start PC是字节码对应的行号,Line Number是Java代码中对应的行号。

LocalVariableTable是和他基本一样的。
终极图:

G:\ownStudy\out\production\ownStudy>cd com

G:\ownStudy\out\production\ownStudy\com>cd bytecode

G:\ownStudy\out\production\ownStudy\com\bytecode>cd stu

G:\ownStudy\out\production\ownStudy\com\bytecode\stu>javap -verbose IOStudy
警告: 二进制文件IOStudy包含com.bytecode.stu.IOStudy
Classfile /G:/ownStudy/out/production/ownStudy/com/bytecode/stu/IOStudy.class
Last modified 2019-4-27; size 483 bytes
MD5 checksum 621af6f4b7c7b6ae6f41f110dbcf73bd
Compiled from "IOStudy.java"
public class com.bytecode.stu.IOStudy
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref          #4.#21         // java/lang/Object."<init>":()V
#2 = Fieldref           #3.#22         // com/bytecode/stu/IOStudy.a:I
#3 = Class              #23            // com/bytecode/stu/IOStudy
#4 = Class              #24            // java/lang/Object
#5 = Utf8               a
#6 = Utf8               I
#7 = Utf8               <init>
#8 = Utf8               ()V
#9 = Utf8               Code
#10 = Utf8               LineNumberTable
#11 = Utf8               LocalVariableTable
#12 = Utf8               this
#13 = Utf8               Lcom/bytecode/stu/IOStudy;
#14 = Utf8               getA
#15 = Utf8               ()I
#16 = Utf8               setA
#17 = Utf8               (I)V
#18 = Utf8               b
#19 = Utf8               SourceFile
#20 = Utf8               IOStudy.java
#21 = NameAndType        #7:#8          // "<init>":()V
#22 = NameAndType        #5:#6          // a:I
#23 = Utf8               com/bytecode/stu/IOStudy
#24 = Utf8               java/lang/Object
{
protected int a;
descriptor: I
flags: ACC_PROTECTED

public com.bytecode.stu.IOStudy();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield      #2                  // Field a:I
9: return
LineNumberTable:
line 3: 0
line 4: 4
LocalVariableTable:
Start  Length  Slot  Name   Signature
0      10     0  this   Lcom/bytecode/stu/IOStudy;

public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield      #2                  // Field a:I
4: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start  Length  Slot  Name   Signature
0       5     0  this   Lcom/bytecode/stu/IOStudy;

public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield      #2                  // Field a:I
5: return
LineNumberTable:
line 11: 0
line 12: 5
LocalVariableTable:
Start  Length  Slot  Name   Signature
0       6     0  this   Lcom/bytecode/stu/IOStudy;
0       6     1     b   I
}
SourceFile: "IOStudy.java"
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: