2019年末总结

2019收获:

​ 虽然是外包,但是毕竟是在微软。这一年多接触到了不少大牛,跟他们也学会了挺多东西。这些大牛有些非常和善,有些就没那么低调,很强势。性格各异吧,不过他们的脑力都挺强的。有点扯远了。。。

在微创工作期间收获如下:

  1. 代码格式自我要求的提高。(之前是在创业团队时间紧任务重人手少,没有代码review这个环节,所以代码规范上比较放飞自我)
  2. 整体架构能力的提升
  3. 对“接口”有了更加深入的理解,设计代码时会考虑代码解耦
  4. 设计代码时会考虑代码扩展性
  5. 服务端有了实践经验(java的springBoot,python的Flask,C#的.netCore)用.netCore写Api的时候给我感觉跟springBoot特别像,不过用它写起来感觉比springBoot更加“简洁”。
  6. 接触了vue(MVVM)
  7. 算法提升,设计模式提升
  8. java基础提升:apt,aop,范型
  9. Android能力提升
    • gradle
    • ndk
    • 反编译
    • 源码阅读
2020年计划
  1. Jetpack学习以及博客输出

  2. kotlin学习以及博客输出

  3. springboot学习以及博客输出

  4. docker学习以及博客输出

  5. Android开发必用框架源码分析输出(视频)

    • Room
    • Retrofit
    • Okhttp
    • RxJava
    • Permission
    • Glide
    • Leakcanary
    • EventBus
  6. Android 性能优化输出(视频)

    • 内存
    • apk体积
    • 网络
    • 电量
  7. ndk输出(视频)-看情况

  8. 视频输出

    • 23个设计模式
    • 算法(按题型每一种10个左右)
  9. 整理博客,现在的太乱了

  10. 热更新学习以及博客输出

  11. 插件化学习以及博客输出

  12. 加固sdk开源以及维护-看情况

  13. flutter学习(看情况)

源码分析-glide

基础

  1. model
  2. data
  3. resource
  4. transformedResource
  5. transcodedResource
  6. target

流程

model

—modelLoader—> data

—Decoder—> resource

—Transform —>transformedReosource

—Transcode—>transcodedResource

——>target

监听声明周期

activity中添加空的fragment,那么该fragment跟activity的生命周期一致。

demo参考:https://blog.csdn.net/qwe851023/article/details/82346178

缓存

  1. 内存缓存
    • hashMap(缓存正在使用的图片)
    • lrucache(缓存不是正在使用的图片)
  2. 磁盘缓存
  3. 网络

andriod - ffmpeg (三)

视频解码(利用ffmpeg从mp4文件中抽取出视频,使用surfaceView播放)

preview

  1. AvformatContext : 拆分为视频流跟音频流 ;合并音频跟视频流
  2. AvcodecContext : 可以获取Avcodec来解压音视频流(视频解压为yuv,音频解压为pcm),可以获取视频宽高,视频码率,fps(frame per second)等音频的采样率,采样位数,通道等。
  3. AVPacket:编码(压缩)数据块
  4. AVFrame:原始数据块(yuv,pcm)
  5. SwsContext:转换视频(缩放,旋转,转换yuv为rgb等),将yuv绘制到surfaceView上
  6. ANativeWindow-> buffer

android - 性能优化(内存)

问题

  1. 内存抖动(频繁gc,锯齿型)
  2. 内存泄漏(可用内存少了,频繁gc)
  3. 内存溢出

工具

  1. memory profiler
    • 实时图展示内存分配
    • 识别内存泄漏跟抖动
    • 提供了堆转储,强制gc,跟踪内存分配的能力
  2. mat
  3. memory leakcanary

andriod - ffmpeg (二)

整个过程:录制—编码—传输—解码—播放
协议:rtmp(跟http同级)
音视频编解码
  1. 常见格式

    视频原始数据格式:yuv

    音频原始数据格式:pcm

    视频编码: h264

    音频编码:aac

    封装:mpeg4(音视频合并成avi,mp4等)

  2. 音频采集:波形数据建模

    • 采样率:单位时间内采集波形图上的点的个数(xxHz)
    • 采样位数:波峰跟波谷之间的数据用多少位来表示(8,16,32)
    • 通道:单通道跟双通道
    • 帧:对应采集的一个点(采样位数*通道数)

    采样完成之后的数据格式是pcm原始数据,相对较大,需要压缩 -> aac

  3. 视频采集

    • hvs 人类视觉系统(对亮度,对比度敏感,对色度不敏感)
    • 使用rgb不符合hvs
    • yuv(y是亮度,uv是色值对应蓝色跟红色)是根据hvs设计的,有y444,y420。。

    采样完成之后的yuv同样很大,需要压缩 -> h264

参考:https://zhuanlan.zhihu.com/p/28058109

android - ffmpeg(一)

编译ffmpeg

使用Mac:

  1. 下载ffmpeg:我这边下的是4.0

    <https://github.com/FFmpeg/FFmpeg/tree/release/4.0>
    
    1. 下载ndk:用的之前下好的r16 (r17也ok,但是不能比r17更高了,更高版本没有gcc而是clang)

      将ndk加入环境变量(vim ~/.bash_profile)

      1. 在ffmpeg目录下新建一个shell脚本:build.sh
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      #!bin/bash
      NDK_ROOT=/Users/kb_jay/Documents/ndk-home/android-ndk-r16b

      CPU=arm-linux-androideabi

      TOOLCHAIN=$NDK_ROOT/toolchains/$CPU-4.9/prebuilt/darwin-x86_64

      FLAGS="-isystem $NDK_ROOT/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=21 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -O0 -fPIC"

      INCLUDES=" -isystem $NDK_ROOT/sources/android/support/include"

      PREFIX=./android/armeabi-v7a

      ./configure \ # 执行configure
      --prefix=$PREFIX \ # 指定结果目录
      --enable-small \ # 压缩体积
      --disable-programs \ # 不编译ffmpeg程序,我们只需要静态库(.a)
      --disable-avdevice \ # 关闭avdevice(这个在android中没用)
      --disable-encoders \ # 关闭编码器 (如果要使用到音视频编码,需要打开)
      --disable-muxers \ # 关闭封装器(不需要mp4,所以关掉)
      --disable-filters \ # 关闭滤镜
      --enable-cross-compile \ # 开启交叉编译
      --cross-prefix=$TOOLCHAIN/bin/$CPU- \ # gcc的前缀
      --disable-shared \ # 非动态so
      --enable-static \ # 静态a
      --sysroot=$NDK_ROOT/platforms/android-21/arch-arm \ # 指定编译的逻辑目录
      --extra-cflags="$FLAGS $INCLUDES" \ # gcc的参数
      --extra-cflags="-isysroot $NDK_ROOT/sysroot/" \ #gcc的参数
      --arch=arm \ # arm架构
      --target-os=android # Android系统

      # 清理
      make clean
      #执行makefile
      make install
      1. 执行build.sh

      2. 在ffmpeg/android/armeabi-v7a下会有我们需要的头文件以及6个静态库

集成到开发项目中

  1. 新建as项目(c++)

  2. 将cmakelists.txt文件剪切到app目录下,跟app的build.gradle同级

  3. build.gradle如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    apply plugin: 'com.android.application'

    android {
    compileSdkVersion 28
    defaultConfig {
    applicationId "com.kbjay.ffmepegdemo"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    externalNativeBuild {
    cmake {
    cppFlags ""
    abiFilters "armeabi-v7a"
    }
    }
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    }
    externalNativeBuild {
    cmake {
    path "CMakeLists.txt"
    }
    }
    }

    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
  4. 将6个静态库copy到libs/armeabi-v7a/下

  5. 将头文件copy到cpp/include下

  6. 修改CmakeLists.txt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    cmake_minimum_required(VERSION 3.6.0)

    file(GLOB SOURCE ${CMAKE_SOURCE_DIR}/src/main/cpp/*.cpp)
    add_library(
    native-lib
    SHARED
    ${SOURCE})

    # 设置头文件
    include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)

    # 通过修改CMAKE_CXX_FLAGS设置静态库的路径
    set(lib_path ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${lib_path}")

    # 设置依赖
    target_link_libraries(
    native-lib
    avcodec
    avfilter
    avformat
    avutil
    swresample
    swscale
    log)
  7. 测试,修改native-lib.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <jni.h>
    #include <string>
    #include "android/log.h"

    extern "C" {
    #include "libavcodec/avcodec.h"
    }

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_kbjay_ffmepegdemo_MainActivity_stringFromJNI(
    JNIEnv *env,
    jobject /* this */) {
    const char *str = av_version_info();
    __android_log_print(ANDROID_LOG_ERROR, "ffmepeg", "%s", str);
    return env->NewStringUTF(str);
    }
  8. 运行app,显示->4.0.5.

  9. 以上。

前端-vue

  1. el 绑定vue对象跟控件

  2. v-if v-for

  3. v-model 处理用户输入,双向绑定

  4. 组件化:相当于自定义控件,如下声明了一个“todo-item”的控件,有“todo”的属相。

    1
    2
    3
    4
    Vue.component('todo-item',{
    props:['todo'], //相当于自定义的属相
    template:'<li>{{todo.text}}</li>' //代码模板
    })
  1. data:vue对象被创建的时候,会吧data中所有的属相放入响应式系统中,当值发生变化的时候,视图会做出“相应”

  2. $:

    1
    2
    3
    4
    vm$data  ==  vm对象中的data
    vm$el == vm对象中的el
    vm$watch
    ...
  3. hook 生命周期,注意this的使用,在箭头函数中,this不代表vue实例。

    1. created
    2. mounted
    3. updated
    4. destroyed
  4. v-html 显示html

  5. v-bind 绑定控件的属相,缩写是“:”。如下绑定div的class属相

    1
    2
    3
    4
    <div v-bind:class="color">
    </div>

    <a v-bind:href="url"></a>
  6. v-on 绑定事件,缩写是“@”。如下绑定a的点击事件

    1
    <a v-on:click="clickA"></a>
  1. xxx 支持js表达式

jvm - 类加载器

​ jvm加载字节码的第一步有个加载,加载首先就是根据类的全限定名找到类的二进制字节流。jvm将这一步暴漏了出来,我们可以通过实现自己的类加载器完成这一步。这样我们就可以动态的指定我们类所在的位置,网络,磁盘等等,或者动态生成也可以。

​ 通过这个特性,我们就有了实现热部署,代码加密等的思路。

分类

  • boot加载 ,javahome中的jar
  • extent加载 ,javahome中ext下的jar文件
  • app加载,程序运行的class文件
  • 自定义加载,自己指定

双亲委派(保证了核心类只加载一次)

  1. 双亲委派并不是继承,而是组合的方法
  2. 逻辑:
    1. 先检查是否加载过(native)
    2. 没有加载过的话,如果parent不空,让parent加载。如果parent空,让boot加载。
    3. 之后class还是为空,调用自己的findClass方法加载。
    4. 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

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;
}
}
  1. 我们知道了双亲委派的机制是在classLoader的loadClass方法中实现的,所以我们在实现自定义classlaoder的时候原则上不建议重写loadclass,而是重写findclass方法

Thinker大致原理

​ android虚拟机不是加载class文件的,而是dex文件。使用的是dexClassLoader,dexClassLoader中有一个List存放dex文件的路径,加载的时候会安顺序去加载,我们可以通过反射将修复好的dex文件放入list的首部,这样修复好的类会先被找到,那么运行的时候跑的就是修复好的类文件。

jvm — jvm加载机制

概念

​ 类的class文件中包含了类的所有信息。

​ jvm将描述类的数据从class文件中加载到内存,并对数据进行校验,解析和初始化,生成可以被jvm直接使用的java类型

类加载时机

  1. 加载

  2. 链接(跟加载并行) 包括验证,准备,解析

  3. 初始化

  4. 使用

  5. 卸载

类加载过程-加载

  • 通过类的全限定名获取此类的二进制流

    加载源(文件 eg:class文件,jar文件;网络;计算生成 eg:动态代理生成接口的实例)

  • 将字节流中的静态数据结构转化成运行时方法区中的动态数据结构

  • 在内存中生成一个这个类的Class对象,作为这个类的各种数据的访问入口

类加载过程-验证

  • 保证class文件符合规范
  • 文件格式 cafebaby,版本号等
  • 元数据 语义校验,比如是否有父类,是否继承了final修饰的类等
  • 字节码校验
  • 符号引用校验

类加载过程-准备

  • 类变量赋初始值。 Int 0;double 0.0; 应用 null ; 在方法区中
  • 类常量赋指定值。

类加载过程-解析

  • 将常量池中的符号引用转为直接引用

类加载过程-初始化

触发初始化:

  • 遇到new,getstatic,putstatic (使用类的静态变量),invokestatic(调用类的静态方法)的字节码命令的时候,会初始化
  • 反射
  • 初始化子类之前一定会先初始化父类
  • 程序入口main的类

不会初始化: ( 211页 3段demo)

  • 通过子类引用父类的静态变量,子类不会被初始化 Child.filed 不会初始化Child,其中filed是父类的静态变量
  • 使用类的常量,不会初始化类(常量在编译的时候已经放入了方法区)
  • 数组引用类 Student[] stus=new Student[10]; 不会初始化Student类

jvm - 字节码文件

​ java号称编译一次,到处运行;其实真正可以到处运行的是java编译器编译之后的字节码文件同时jvm针对不同平台都做了实现;

​ 除了平台无关性,字节码文件还有语言无关系;比如groovy,它用自己的编译器编译之后生成class文件同样可以运行在jvm之上。

​ 其实我们自己也可以定义一种属于我们自己的语言,然后再写一个编译器,只要编译之后的文件符合class文件的规范,同样可以在jvm上运行。

​ 盗图一张:

“javaè¯­è¨€æ— å…³æ€§â€çš„å›¾ç‰‡æœç´¢ç»“æžœ

class字节码规范

Class 文件是一组以 8 位字节为基础单位的二进制流,其本质就是一张表,如下图所示,u1、u2、u4 表示 1 个字节、2 个字节、4 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 utf-8 编码构成的字符串值。_info 结尾的表示,用以描述有层次关系的复合结构数据。

类型 名称 描述
u4 magic 魔数,用以标记文件类型,固定值0xCAFEBABE
u2 minor_version 次版本号,向上兼容
u2 major_version 主版本号
u2 constant_pool_count 常量的个数
cp_info constant_pool 常量池,数量为(constant_pool_count - 1)
u2 access_flags 访问标识
u2 this_class 当前类索引
u2 super_class 父类索引
u2 interfaces_count 接口的个数
u2 interfaces 具体的接口内容,数量为 interfaces_count
u2 fields_count 字段的个数
field_info fields 具体的字段内容,数量为 fields_count
u2 methods_count 方法的个数
method_info methods 具体的方法内容,数量为 methods_count
u2 attribute_count 属性的个数
attribute_info attributes 具体的属性内容,数量为 attributes_count
常量池(Constant pool)

常量池是Class文件空间最大的数据项之一,长度不固定,主要包含两类:字面量(Literal)和符号引用(Symbolic References),字面量比较接近 Java 语言层面的常量概念,如文本字符串,声明为 final 的常量值等;符号引用这属于编译原理方面的概念,主要包括下面三类常量:

1
2
3
4
5
- 类和接口全限定名(Full Qualified Name)

- 字段的名称和描述符(Descriptor)

- 方法的名称和描述符

Java 是在虚拟机加载 Class 文件的时候进行的动态链接,因此 Class 文件不会保存各个方法字段的最终内存布局信息,关于动态链接部分,将在下一篇文章Java系列(四)——类的加载与执行介绍。

常量池中的每一个数据项都有自己的类型,目前 Java 8一共有以下 14 种表结构数据支持不同的常量类型,具体结构可参见官方文档

常量池中数据项类型 类型标志 类型描述
CONSTANT_Utf8_info 1 UTF-8编码的Unicode字符串
CONSTANT_Integer_info 3 int类型字面值
CONSTANT_Float_info 4 float类型字面值
CONSTANT_Long_info 5 long类型字面值
CONSTANT_Double_info 6 double类型字面值
CONSTANT_Class_info 7 对一个类或接口的符号引用
CONSTANT_String_info 8 String类型字面值
CONSTANT_Fieldref_info 9 对一个字段的符号引用
CONSTANT_Methodref_info 10 对一个类中声明的方法的符号引用
CONSTANT_InterfaceMethodref_info 11 对一个接口中声明的方法的符号引用
CONSTANT_NameAndType_info 12 对一个字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标示方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

可以使用 JDK 提供的 javap 工具,javap -verbose ClassName的方式查看 Class 中的常量。

访问标志(Access flags)

2个字节代表,标示用于识别一些类或者接口层次的访问信息。

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为public;可以从包外部访问
ACC_FINAL 0x0010 被声明为final;不允许子类修改
ACC_SUPER 0x0020 当被invokespecial指令调用时,将特殊对待父类的方法
ACC_INTERFACE 0x0200 接口标识符
ACC_ABSTRACT 0x0400 声明为abstract;不能被实例化
ACC_SYNTHETIC 0x1000 声明为synthetic;不存在于源代码,由编译器生成
ACC_ANNOTATION 0x2000 声明为注释类型
ACC_ENUM 0x4000 声明为枚举类型
类索引、父类索引和接口索引

类索引(this_class)和父类索引(super_class)都是一个 u2 的数据类型,而接口索引(Interfaces)是一组长度为interfaces_count 的 u2 类型的数据集合,它们的值大多存放的是对常量池中 CONSTANT_Class_info 的引用, Class 文件中由这三项数据来确定这个类的继承关系。

字段表(Field info)

字段表用于描述类或接口中声明的变量,格式如下:

1
2
3
4
5
6
7
field_info {
u2 access_flags; //访问标识
u2 name_index; //名称索引
u2 descriptor_index; //描述符索引
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表的具体内容
}

字段访问标识如下:(表中加粗项是字段独有的):

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为 public; 可以从包外部访问
ACC_PRIVATE 0x0002 声明为 private; 只有定义的类可以访问
ACC_PROTECTED 0x0004 声明为 protected;只有子类和相同package的类可访问
ACC_STATIC 0x0008 声明为 static;属于类变量
ACC_FINAL 0x0010 声明为 final; 对象构造后无法直接修改值
ACC_VOLATILE 0x0040 声明为 volatile; 不会被缓存,直接刷新到主屏幕
ACC_TRANSIENT 0x0080 声明为 transient; 不能被序列化
ACC_SYNTHETIC 0x1000 声明为 synthetic; 不存在于源代码,由编译器生成
ACC_ENUM 0x4000 声明为enum

Java语法中,接口中的字段默认包含ACC_PUBLIC, ACC_STATIC, ACC_FINAL标识。ACC_FINAL,ACC_VOLATILE不能同时选择等规则。紧跟其后的name_index和descriptor_index是对常量池的引用,分别代表着字段的简单名和方法的描述符。

方法表(Method info)

方法表用于描述类或接口中声明的方法,格式如下:

1
2
3
4
5
6
7
method_info {
u2 access_flags; //访问标识
u2 name_index; //名称索引
u2 descriptor_index; //描述符索引
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表的具体内容
}

方法访问标识如下:(表中加粗项是方法独有的)

标识名 标识值 解释
ACC_PUBLIC 0x0001 声明为 public; 可以从包外部访问
ACC_PRIVATE 0x0002 声明为 private; 只有定义的类可以访问
ACC_PROTECTED 0x0004 声明为 protected;只有子类和相同package的类可访问
ACC_STATIC 0x0008 声明为 static;属于类变量
ACC_FINAL 0x0010 声明为 final; 不能被覆写
ACC_SYNCHRONIZED 0x0020 声明为 synchronized; 同步锁包裹
ACC_BRIDGE 0x0040 桥接方法, 由编译器生成
ACC_VARARGS 0x0080 声明为 接收不定长参数
ACC_NATIVE 0x0100 声明为 native; 由非Java语言来实现
ACC_ABSTRACT 0x0400 声明为 abstract; 没有提供实现
ACC_STRICT 0x0800 声明为 strictfp; 浮点模式是FP-strict
ACC_SYNTHETIC 0x1000 声明为 synthetic; 不存在于源代码,由编译器生成
  • 对于方法里的Java代码,进过编译器编译成字节码指令后,存放在方法属性表集合中“code”的属性内。
  • 当子类没有覆写父类方法,则方法集合中不会出现父类的方法信息。
  • Java语言中重载方法,必须与原方法同名,且特征签名不同。特征签名是指方法中各个参数在常量池的字段符号引用的集合,不包括返回值。当时Class文件格式中,特征签名范围更广,允许方法名和特征签名都相同,但返回值不同的方法,合法地共存子啊同一个Class文件中。
属性表(Attribute info)

属性表的基本结构,不同类型的属性表以此为基础各不相同:

1
2
3
4
5
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}

属性表的限制相对宽松,不需要各个属性表有严格的顺序,只有不与已有的属性名重复,任何自定义的编译器都可以向属性表中写入自定义的属性信息,Java虚拟机运行时会忽略掉无法识别的属性。 关于虚拟机规范中预定义的属性,这里不展开讲了,列举几个常用的。

属性名 使用位置 解释
Code 方法表 方法体的内容
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 声明为deprecated
InnerClasses 类文件 内部类的列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
Signature 类、方法表、字段表 用于支持泛型的方法签名,由于Java的泛型采用擦除法,避免类型信息被擦除后导致签名混乱,Signature记录相关信息
理解:

​ class文件其实就是一种协议或者说是规范,它有自己的格式。java编译器读取java文件,按照class文件的规范生产对应的class文件,jvm根据class文件规范读取class文件之后运行。

解读工具javap

​ 我们自己根据字节码文件的规范,也可以读懂class文件。sun公司其实已经提供了读取class文件的工具javap。

定义类如下:

1
2
3
4
5
6
7
8
public class Student{
public String name;
public int age;

public int add(int a,int b){
return a+b;
}
}

运行javac之后运行javap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
kb-jaydeMacBook-Air:Desktop kb_jay$ javac Student.java
kb-jaydeMacBook-Air:Desktop kb_jay$ javap -verbose Student
Classfile /Users/kb_jay/Desktop/Student.class
Last modified 2019-9-10; size 298 bytes
MD5 checksum fec35654474a4425fb9a2abfbcc72a07
Compiled from "Student.java"
public class Student
minor version: 0 //副版本号
major version: 52 //主版本号
flags: ACC_PUBLIC, ACC_SUPER //类访问标志
Constant pool: //常量池,这里是类文件的资源池
#1 = Methodref #3.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // Student
#3 = Class #18 // java/lang/Object
#4 = Utf8 name
#5 = Utf8 Ljava/lang/String;
#6 = Utf8 age
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Student.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = Utf8 Student
#18 = Utf8 java/lang/Object
{
//字段表,这里的描述符,如果做过ndk,应该很熟悉了
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC

public int age;
descriptor: I
flags: ACC_PUBLIC

//方法表
public Student();
//方法描述符,ndk开发常用
descriptor: ()V
flags: ACC_PUBLIC
//code是属性表中的一个,
Code:
//操作数栈,局部变量表,参数个数,这里是this
stack=1, locals=1, args_size=1
//字节码指令
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//LineNumberTable是属性表中的一个,抛出异常的时的异常代码的位置
LineNumberTable:
line 1: 0

public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 6: 0
}
SourceFile: "Student.java"

字节码指令

tip:如下表(转自网络)

字节码 助记符 指令含义
0x00 nop None
0x01 aconst_null 将null推送至栈顶
0x02 iconst_m1 将int型-1推送至栈顶
0x03 iconst_0 将int型0推送至栈顶
0x04 iconst_1 将int型1推送至栈顶
0x05 iconst_2 将int型2推送至栈顶
0x06 iconst_3 将int型3推送至栈顶
0x07 iconst_4 将int型4推送至栈顶
0x08 iconst_5 将int型5推送至栈顶
0x09 lconst_0 将long型0推送至栈顶
0x0a lconst_1 将long型1推送至栈顶
0x0b fconst_0 将float型0推送至栈顶
0x0c fconst_1 将float型1推送至栈顶
0x0d fconst_2 将float型2推送至栈顶
0x0e dconst_0 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0x10 bipush 将单字节的常量值(-128~127)推送至栈顶
0x11 sipush 将一个短整型常量(-32768~32767)推送至栈顶
0x12 ldc 将int,float或String型常量值从常量池中推送至栈顶
0x13 ldc_w 将int,float或String型常量值从常量池中推送至栈顶(宽索引)
0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15 iload 将指定的int型本地变量推送至栈顶
0x16 lload 将指定的long型本地变量推送至栈顶
0x17 fload 将指定的float型本地变量推送至栈顶
0x18 dload 将指定的double型本地变量推送至栈顶
0x19 aload 将指定的引用类型本地变量推送至栈顶
0x1a iload_0 将第一个int型本地变量推送至栈顶
0x1b iload_1 将第二个int型本地变量推送至栈顶
0x1c iload_2 将第三个int型本地变量推送至栈顶
0x1d iload_3 将第四个int型本地变量推送至栈顶
0x1e lload_0 将第一个long型本地变量推送至栈顶
0x1f lload_1 将第二个long型本地变量推送至栈顶
0x20 lload_2 将第三个long型本地变量推送至栈顶
0x21 lload_3 将第四个long型本地变量推送至栈顶
0x22 fload_0 将第一个float型本地变量推送至栈顶
0x23 fload_1 将第二个float型本地变量推送至栈顶
0x24 fload_2 将第三个float型本地变量推送至栈顶
0x25 fload_3 将第四个float型本地变量推送至栈顶
0x26 dload_0 将第一个double型本地变量推送至栈顶
0x27 dload_1 将第二个double型本地变量推送至栈顶
0x28 dload_2 将第三个double型本地变量推送至栈顶
0x29 dload_3 将第四个double型本地变量推送至栈顶
0x2a aload_0 将第一个引用类型本地变量推送至栈顶
0x2b aload_1 将第二个引用类型本地变量推送至栈顶
0x2c aload_2 将第三个引用类型本地变量推送至栈顶
0x2d aload_3 将第四个引用类型本地变量推送至栈顶
0x2e iaload 将int型数组指定索引的值推送至栈顶
0x2f laload 将long型数组指定索引的值推送至栈顶
0x30 faload 将float型数组指定索引的值推送至栈顶
0x31 daload 将double型数组指定索引的值推送至栈顶
0x32 aaload 将引用类型数组指定索引的值推送至栈顶
0x33 baload 将boolean或byte型数组指定索引的值推送至栈顶
0x34 caload 将char型数组指定索引的值推送至栈顶
0x35 saload 将short型数组指定索引的值推送至栈顶
0x36 istore 将栈顶int型数值存入指定本地变量
0x37 lstore 将栈顶long型数值存入指定本地变量
0x38 fstore 将栈顶float型数值存入指定本地变量
0x39 dstore 将栈顶double型数值存入指定本地变量
0x3a astore 将栈顶引用类型数值存入指定本地变量
0x3b istore_0 将栈顶int型数值存入第一个本地变量
0x3c istore_1 将栈顶int型数值存入第二个本地变量
0x3d istore_2 将栈顶int型数值存入第三个本地变量
0x3e istore_3 将栈顶int型数值存入第四个本地变量
0x3f lstore_0 将栈顶long型数值存入第一个本地变量
0x40 lstore_1 将栈顶long型数值存入第二个本地变量
0x41 lstore_2 将栈顶long型数值存入第三个本地变量
0x42 lstore_3 将栈顶long型数值存入第四个本地变量
0x43 fstore_0 将栈顶float型数值存入第一个本地变量
0x44 fstore_1 将栈顶float型数值存入第二个本地变量
0x45 fstore_2 将栈顶float型数值存入第三个本地变量
0x46 fstore_3 将栈顶float型数值存入第四个本地变量
0x47 dstore_0 将栈顶double型数值存入第一个本地变量
0x48 dstore_1 将栈顶double型数值存入第二个本地变量
0x49 dstore_2 将栈顶double型数值存入第三个本地变量
0x4a dstore_3 将栈顶double型数值存入第四个本地变量
0x4b astore_0 将栈顶引用型数值存入第一个本地变量
0x4c astore_1 将栈顶引用型数值存入第二个本地变量
0x4d astore_2 将栈顶引用型数值存入第三个本地变量
0x4e astore_3 将栈顶引用型数值存入第四个本地变量
0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置
0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置
0x52 dastore 将栈顶double型数值存入指定数组的指定索引位置
0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55 castore 将栈顶char型数值存入指定数组的指定索引位置
0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置
0x57 pop 将栈顶数值弹出(数值不能是long或double类型的)
0x58 pop2 将栈顶的一个(对于非long或double类型)或两个数值(对于非long或double的其他类型)弹出
0x59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(对于long或double类型)或两个(对于非long或double的其他类型)数值并将复制值压入栈顶
0x5d dup2_x1 dup_x1指令的双倍版本
0x5e dup2_x2 dup_x2指令的双倍版本
0x5f swap 将栈顶最顶端的两个数值互换(数值不能是long或double类型)
0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶
0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶
0x63 dadd 将栈顶两double型数值相加并将结果压入栈顶
0x64 isub 将栈顶两int型数值相减并将结果压入栈顶
0x65 lsub 将栈顶两long型数值相减并将结果压入栈顶
0x66 fsub 将栈顶两float型数值相减并将结果压入栈顶
0x67 dsub 将栈顶两double型数值相减并将结果压入栈顶
0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶
0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
0x73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶
0x74 ineg 将栈顶int型数值取负并将结果压入栈顶
0x75 lneg 将栈顶long型数值取负并将结果压入栈顶
0x76 fneg 将栈顶float型数值取负并将结果压入栈顶
0x77 dneg 将栈顶double型数值取负并将结果压入栈顶
0x78 ishl 将int型数值左移指定位数并将结果压入栈顶
0x79 lshl 将long型数值左移指定位数并将结果压入栈顶
0x7a ishr 将int型数值右(带符号)移指定位数并将结果压入栈顶
0x7b lshr 将long型数值右(带符号)移指定位数并将结果压入栈顶
0x7c iushr 将int型数值右(无符号)移指定位数并将结果压入栈顶
0x7d lushr 将long型数值右(无符号)移指定位数并将结果压入栈顶
0x7e iand 将栈顶两int型数值”按位与”并将结果压入栈顶
0x7f land 将栈顶两long型数值”按位与”并将结果压入栈顶
0x80 ior 将栈顶两int型数值”按位或”并将结果压入栈顶
0x81 lor 将栈顶两long型数值”按位或”并将结果压入栈顶
0x82 ixor 将栈顶两int型数值”按位异或”并将结果压入栈顶
0x83 lxor 将栈顶两long型数值”按位异或”并将结果压入栈顶
0x84 iinc 将指定int型变量增加指定值(如i++, i–, i+=2等)
0x85 i2l 将栈顶int型数值强制转换为long型数值并将结果压入栈顶
0x86 i2f 将栈顶int型数值强制转换为float型数值并将结果压入栈顶
0x87 i2d 将栈顶int型数值强制转换为double型数值并将结果压入栈顶
0x88 l2i 将栈顶long型数值强制转换为int型数值并将结果压入栈顶
0x89 l2f 将栈顶long型数值强制转换为float型数值并将结果压入栈顶
0x8a l2d 将栈顶long型数值强制转换为double型数值并将结果压入栈顶
0x8b f2i 将栈顶float型数值强制转换为int型数值并将结果压入栈顶
0x8c f2l 将栈顶float型数值强制转换为long型数值并将结果压入栈顶
0x8d f2d 将栈顶float型数值强制转换为double型数值并将结果压入栈顶
0x8e d2i 将栈顶double型数值强制转换为int型数值并将结果压入栈顶
0x8f d2l 将栈顶double型数值强制转换为long型数值并将结果压入栈顶
0x90 d2f 将栈顶double型数值强制转换为float型数值并将结果压入栈顶
0x91 i2b 将栈顶int型数值强制转换为byte型数值并将结果压入栈顶
0x92 i2c 将栈顶int型数值强制转换为char型数值并将结果压入栈顶
0x93 i2s 将栈顶int型数值强制转换为short型数值并将结果压入栈顶
0x94 lcmp 比较栈顶两long型数值大小, 并将结果(1, 0或-1)压入栈顶
0x95 fcmpl 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x96 fcmpg 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x97 dcmpl 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x98 dcmpg 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifne 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值大于等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于等于0时跳转
0x9f if_icmpeq 比较栈顶两int型数值大小, 当结果等于0时跳转
0xa0 if_icmpne 比较栈顶两int型数值大小, 当结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两int型数值大小, 当结果小于0时跳转
0xa2 if_icmpge 比较栈顶两int型数值大小, 当结果大于等于0时跳转
0xa3 if_icmpgt 比较栈顶两int型数值大小, 当结果大于0时跳转
0xa4 if_icmple 比较栈顶两int型数值大小, 当结果小于等于0时跳转
0xa5 if_acmpeq 比较栈顶两引用型数值, 当结果相等时跳转
0xa6 if_acmpne 比较栈顶两引用型数值, 当结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定的16位offset位置, 并将jsr的下一条指令地址压入栈顶
0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr或jsr_w联合使用)
0xaa tableswitch 用于switch条件跳转, case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转, case值不连续(可变长度指令)
0xac ireturn 从当前方法返回int
0xad lreturn 从当前方法返回long
0xae freturn 从当前方法返回float
0xaf dreturn 从当前方法返回double
0xb0 areturn 从当前方法返回对象引用
0xb1 return 从当前方法返回void
0xb2 getstatic 获取指定类的静态域, 并将其压入栈顶
0xb3 putstatic 为指定类的静态域赋值
0xb4 getfield 获取指定类的实例域, 并将其压入栈顶
0xb5 putfield 为指定类的实例域赋值
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构建方法, 实例初始化方法, 私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba invokedynamic 调用动态方法
0xbb new 创建一个对象, 并将其引用引用值压入栈顶
0xbc newarray 创建一个指定的原始类型(如int, float, char等)的数组, 并将其引用值压入栈顶
0xbd anewarray 创建一个引用型(如类, 接口, 数组)的数组, 并将其引用值压入栈顶
0xbe arraylength 获取数组的长度值并压入栈顶
0xbf athrow 将栈顶的异常抛出
0xc0 checkcast 检验类型转换, 检验未通过将抛出 ClassCastException
0xc1 instanceof 检验对象是否是指定类的实际, 如果是将1压入栈顶, 否则将0压入栈顶
0xc2 monitorenter 获得对象的锁, 用于同步方法或同步块
0xc3 monitorexit 释放对象的锁, 用于同步方法或同步块
0xc4 wide 扩展本地变量的宽度
0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时, 操作栈中必须包含各维度的长度值), 并将其引用压入栈顶
0xc6 ifnull 为null时跳转
0xc7 ifnonnull 不为null时跳转
0xc8 goto_w 无条件跳转(宽索引)
0xc9 jsr_w 跳转至指定的32位offset位置, 并将jsr_w的下一条指令地址压入栈顶

参考:深入理解java虚拟机