JAVA字节码.Class解析
不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java虚拟机的规范,那么它就能够执行该字节码文件


字节码组成
每个16进制符代表0.5个字节-4位
MagicNumber
前4个字节:0xCAFE BABE 用来区分文件类型的一种标志 -咖啡宝贝为java特征
VersionNumber
次版本号+主版本号 2字节+2字节 0x0000 0033 次版本号为0 主版本号 3*16+3=51->对应jdk1.7 次版本为0->jdk1.7.0
Constant Pool
占用2+n个字节
主要存储2大类常量:字面量和符号引用
字面量如字符串,java中声明为final的常量值等等
符号引用如 类和接口的全局限定名,字段的名称和修饰符,方法的名称和修饰符。
全局限定名:
java虚拟机首先需要将类加载到虚拟机中,那么这个过程设计对类的定位(加载A包而不是B包)
所以需要通过全局限定名来判别唯一性
demo解析
0x0015:代表常量池数量 16+5=21-1=20项常量 0留出来备用
常量 索引项#1
0x0a -tag类型 定位表中信息类型 0a=10 对应表中的MethodRef_info
0x0004 Class_Info索引项#4
0x0011 NameAndType索引项#17
常量 索引项#2
0x-09: FieldRef_info
0x0003 : Class_Info索引项#3
0x0012:NameAndType索引项#18
常量 索引项#3
0x07-: Class_info
0x0013: 全局限定名常量索引为#19
常量 索引项#4
0x07 :Class_info
0x0014:全局限定名常量索引为#20
等

访问标志位
大小2个字节
包括该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final
0x 0021:是0x0020和0x0001的并集

类索引
大小2个字节
类索引用于确定类的全限定名
0x0003 表示引用第3个常量,同时第3个常量引用第19个常量 (com/demo/Demo)
父类索引
大小2个字节
0x00 04 同理:#4.#20(java/lang/Object)
接口索引
大小2+n个字节
前两个字节表示的是接口数量,后面跟着就是接口的表
类没有任何接口 0000
字段表集合
大小2+n个字节
用于描述类和接口中声明的变量
这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
前两个字节表示的是数量

方法
大小2+n个字节

demo:
0x00 02 只有一个方法testMethod 但隐藏存在init方法
0x00 01:访问标志 ACC_PUBLIC,表明该方法是public
0x00 07:方法名索引为#7,对应的是"<init>"
0x00 08:方法描述符索引为#8,对应的是"()V"
0x00 01:属性表数量为1(一个属性表)
属性表中又对应Code表 每个成员又对应自己的结构表
Attribute
大小2+n个字节

0x0001 :同样的,表示有1个Attributes了。
0x000f : #15("SourceFile")
0x0000 0002 attribute_length=2
0x0010 : sourcefile_index = #16("Demo.java")
ClassLoader类加载机制
类加载机制分为3个步骤 加载-连接-初始化
判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。
类加载成员
先介绍下类加载涉及到的类
1.BootStrap 根类加载器 负责加载rt.jar 使用c++编写
2.ExtClassLoader 扩展类加载器 extends URLClassLoader
3.AppClassLoader 应用类加载器 extends URLClassLoader,我们自定义的类加载器通常属于这个
而Android使用的是DVM加载的是.dex格式字节码涉及到其他的类加载成员
1.DexClassLoader 可加载jar、apk和dex,可以从SD卡中加载
2.PathClassLoader:只能加载已经安装搭配Android系统中的apk文件
类加载机制-双亲委任机制
介绍完类之后必须理解类加载中的重要机制
AppClassLoader不会自己去加载类,而会委ExtClassLoader进行加载,那么到了ExtClassLoader类加载器的时候,它也不会自己去加载,而是委托BootStrap类加载器进行加载,就这样一层一层往上委托,如果Bootstrap类加载器无法进行加载的话,再一层层往下走。
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
加载
加载即选择加载器的过程
连接
类的连接有三步,分别是验证,准备,解析
验证
-将已经读入到内存类的二进制数据合并到虚拟机运行时环境中去。
-类文件结构检查:格式符合jvm规范-语义检查:符合java语言规范,final类没有子类,final类型方法没有被覆盖
-字节码验证:确保字节码可以安全的被java虚拟机执行.
-二进制兼容性检查
准备
java虚拟机为类的静态变量分配内存并赋予默认的初始值.
如int分配4个字节并赋值为0,long分配8字节并赋值为0;
解析
解析阶段主要是将符号引用转化为直接引用的过程
A{
void a(){
B.b();
}
}
B{
void b();
}
将B.b()的符号引用改为直接引用(内存地址)
初始化
主动初始化的6种方式:
(1)创建对象的实例:我们new对象的时候,会引发类的初始化,前提是这个类没有被初始化。
(2)调用类的静态属性或者为静态属性赋值
(3)调用类的静态方法
(4)通过class文件反射创建对象
(5)初始化一个类的子类:使用子类的时候先初始化父类
(6)java虚拟机启动时被标记为启动类的类:就是我们的main方法所在的类
Tips:
1)在同一个类加载器下面只能初始化类一次,如果已经初始化了就不必要初始化了.
2)在编译的时候能确定下来的静态变量(编译常量),不会对类进行初始化; final
3)在编译时无法确定下来的静态变量(运行时常量),会对类进行初始化;
4)如果这个类没有被加载和连接的话,那就需要进行加载和连接
5)如果这个类有父类并且这个父类没有被初始化,则先初始化父类.
6)如果类中存在初始化语句,依次执行初始化语句.
public class Test1 {
public static void main(String args[]){
System.out.println(FinalTest.x);
FinalTest.x =3;//为静态属性赋值 因为类加载初始化过了不会重复
}
}
class FinalTest{
//public static final int x =6/3;//只输出2 编译常量,能够在编译时期确定的
public static int x =6/3;// FinalTest static block 2 运行时才能确定,运行时常量
//对类的首次主动使用,引用类的静态变量 会造成类的初始化
static {
System.out.println("FinalTest static block");
}
}
初始化步骤顺序
没有父类的情况:
1)类的静态属性
2)类的静态代码块
3)类的非静态属性
4)类的非静态代码块
5)构造方法
有父类的情况:
1)父类的静态属性
2)父类的静态代码块
3)子类的静态属性
4)子类的静态代码块
5)父类的非静态属性
6)父类的非静态代码块
7)父类构造方法
8)子类非静态属性
9)子类非静态代码块
10)子类构造方法
Android程序运行原理
Android的程序,同样,安卓也有自己的启动方式,也是一个main方法
ActivityThread的main 运行ui线程
当抛出异常后结束运行
操作系统异常,导致DVM退出,其实DVM就是一个软件,如果我们的操作系统都出现了错误,那么运行在他上面的软件必然会被kill,android为每个app配备独立的dvm空间 dvm退出这个app也会被强退
经典案例
public class Singleton {
private static Singleton singleton = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getSingleton() {
return singleton;
}
}
public class TestSingleton {
public static void main(String args[]){
Singleton singleton = Singleton.getSingleton();
System.out.println("counter1="+singleton.counter1);
System.out.println("counter2="+singleton.counter2);
}
}
加载 连接 初始化
加载连接Singleton类
singleton=null
counter=0
counter2=0
初始化类
new Singleton() counter++ counter2++
counter没有初始化 counter2初始化为0
初始化后
singleton=instance;
counter=1
counter=0
执行代码
Singleton.getSingleton()得到初始化后的instance
输出分别为1,0
反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能
1、在运行时判断任意一个对象所属的类;
2、在运行时构造任意一个类的对象;
3、在运行时判断任意一个类所具有的成员变量和方法;
4、在运行时调用任意一个对象的方法;生成动态代理;
在java和android中的运用
1.生成数据库驱动对象实例:Class.forName("com.mysql.jdbc.Driver.class").newInstance();
2.通过类加载器通过类名加载Class对象:
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.izzy.Test");
3.AndroidFramework的ActivityThread中 通过类名和类加载器生构造类的对象activity:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
为什么要用
反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性
缺点就是运行速度比较慢
我们如何使用呢
Class c=Class.forName("className");
Object obj=c.newInstance();//创建对象的实例
Constructor getConstructor(Class[] params)//根据指定参数获得public构造器
Constructor[] getConstructors()//获得public的所有构造器
Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器
Constructor[] getDeclaredConstructors()//获得public的所有构造器
Method getMethod(String name, Class[] params),根据方法名,参数类型获得方法
Method[] getMethods()//获得所有的public方法
Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法
Method[] getDeclaredMethods()//获得所以的public和非public方法
Field getField(String name)//根据变量名得到相应的public变量
Field[] getFields()//获得类中所以public的方法
Field getDeclaredField(String name)//根据方法名获得public和非public变量
Field[] getDeclaredFields()//获得类中所有的public和非public方法
IPC-Binder-AIDL-Intent (进程间)共享数据机制
IPC:Inter-Process Communication(进程间通信) 操作系统层面的概念
Binder:Binder进程间通信机制 IPC的android具体实现
AIDL:android接口定义语言 是Binder机制向外提供的接口
Intent: 最高层级的封装 封装了对Binder的使用 也包含进程内的调用

上图中最底层的Ashmen 为匿名共享内存
IPC
IPC是操作系统层面的概念,因为进程是操作系统里最基本的单位,进程的一大特征就是有自己独立的内存地址空间,所以同一进程里的代码能直接访问其地址空间的内存,而其它进程是不能直接访问的。操作系统是多进程的,多个进程配合工作肯定会涉及到互相的通信,故出现了IPC,而对IPC的实现也不是一种,有很多,且有各自的长处,比如Socket就是一种IPC,其长处就是能跨机器进行进程间通信。
Binder
Binder本质上是通过共享内存来实现的IPC( 直白点就是一段物理内存在不同进程中都映射了虚拟地址(虚拟地址可能不同),一个对它写,另一个对它读,然后解析、处理),但是Binder机制不是简简单单共享内存,而是搭了一套巧妙的架构,来达到最终共享内存进行IPC的目的。
基本思想
采用C/S通信结构,通信由Binder驱动程序和Service Manager组件协助
这两个组件Android已经实现好了并由系统运行,而开发者只需要按照框架规范实现Client与Server接口即可
服务端创建Binder对象 其中涵盖一些服务内容
客户端得到Binder对象 并调用其中内容
具体实现
[图片上传失败...(image-d7c5bc-1522580909920)]
AIDL
是为了方便使用Binder框架封装的库
简化了进程间通信操作
只要按照规范写一个.aidl文件,插件会帮助自动创建一个与interface同名的.java文件,里面已经帮我们自动写好一些操作,这些标准化的操作,用模板自动生成即可,让开发者尽量关注功能实现上
Intent
Intent的架构包括三方面:
Client,发送这个Intent的activity;
Server,ActivityManagerService.java,它主要是负责分发这些Intent给适当的对象;
Target,也就是那些需要处理这个Intent的Activity,称为Receiver;
进程间发送消息或者Broadcast,并不是直接把Intent发过去,而是把Intent打包到Parcel中,通过Binder机制传递消息。
Binder机制
类似 我们日常生活中联系同学
1.电话机 Binder驱动
2.打电话 需要信号传到基站 SM
电话机互相交流需要通过基站
电话机B要联系电话机A
设电话机A为服务端
电话机B为客户端
客户发起请求
首先需要在基站中注册服务端电话机A的号码(Binder对象)
客户发起请求电话机B 向基站查询服务端的号码(Binder对象)
拿到号码之后建立通信 (调用 Binder对象的服务 ) 拿到的只是代理对象
实例: AIDL
成员介绍-使用到了静态代理模式
1.Binder 进程间通信的媒介
2.IBinder 是一个接口,它代表了一种跨进程传输的能力 其中有许多Binder跨进程所必备的函数功能
3.Proxy 是导入对IBinder的依赖 故具备跨进程传输的能力 并实现AIDL接口进行扩展满足我们提供功能的代理
即:使我们的接口定义的功能可以通过Binder机制来传输
实际上,在跨越进程的时候,Binder驱动会自动完成代理对象和本地对象的转换。
4.Stub 这个类继承了Binder, 说明它是一个Binder本地对象 且实现了接口包含了提供的服务
服务端: 提供服务函数
步骤1.创建AIDL接口文件并写入待实现函数
步骤2 编译生成java文件 运用到代理模式
步骤3 创建对应的AidlService并在配置清单中注册
步骤4 在service中接口的代理中写出具体实现 并在onBind返回
客户端 调用服务
步骤1 客户端创建相同的AIDL接口文件 编译生成java文件 ps:包名文件名必须相同
步骤2 创建connection将Ibinder转为已经实现的接口
步骤3 绑定service 并调用接口函数
此篇文章借鉴https://www.jianshu.com/u/b997cd1b6a9d作者的经验
所有实例都经过本人实际操作证实
特写此文感悟,谢谢支持
网友评论