网站seo优化包括哪些方面,起个娱乐网站名字,李洋网络做网站,做图的ppt模板下载网站theme: channing-cyan
在Android开发中#xff0c;内存泄露发生的场景其实主要就两点#xff0c;一是数据过大的问题#xff0c;而是调用与被调用生命周期不一致问题#xff0c;对于对象生命周期不一致导致的泄漏问题占90%#xff0c;最常见的也不好分析的当属匿名内部类…
theme: channing-cyan
在Android开发中内存泄露发生的场景其实主要就两点一是数据过大的问题而是调用与被调用生命周期不一致问题对于对象生命周期不一致导致的泄漏问题占90%最常见的也不好分析的当属匿名内部类的内存泄漏在文章《# 内存泄漏大集结安卓开发者不可错过的性能优化技巧》 中我大概进行了总结最近在开发时遇到了一个问题就是LeakCannry 检测到的内存泄漏LeakCannry检测的原理大概就是GC 可达性算法实现的我们产品中最多的一个问题就是匿名内部类导致的。
案例不涉及持有外部类引用的状态下
匿名内部类如何导致内存泄漏
在Java体系中内部类有多种最常见的就是静态内部类、匿名内部类一般情况下都推荐使用静态内部类那这是为什么呢先看一个例子
public class Test {public static void main(String[] args) {new Thread(new Runnable() {Overridepublic void run() {}}).start();}
}匿名内部类的泄漏原因内部类持有外部类的引用上述场景中当外部类销毁时匿名内部类Runnable 会导致内存泄漏 验证这个结论
上述代码的class 文件通过Javap -c 查看后是这样的
Compiled from Test.java
public class Test {public Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: returnpublic static void main(java.lang.String[]);Code:0: new #2 // class java/lang/Thread3: dup4: new #3 // class Test$17: dup8: invokespecial #4 // Method Test$1.init:()V11: invokespecial #5 // Method java/lang/Thread.init:(Ljava/lang/Runnable;)V14: invokevirtual #6 // Method java/lang/Thread.start:()V17: return
}我们直接看main 方法中的指令
0: new #2 // 创建一个新的 Thread 对象
3: dup // 复制栈顶的对象引用
4: new #3 // 创建一个匿名内部类 Test$1 的实例
7: dup // 复制栈顶的对象引用
8: invokespecial #4 // 调用匿名内部类 Test$1 的构造方法
11: invokespecial #5 // 调用 Thread 类的构造方法传入匿名内部类对象
14: invokevirtual #6 // 调用 Thread 类的 start 方法启动线程
17: return // 返回我们可以看到在第4步中 使用new 指令创建了一个Test$1的实例并且在第8步中通过invokespecial 指令调用匿名内部类的构造方法这样一来生成的内部类就会持有外部类的引用从而外部类不能回收将导致内存泄漏。
Lambda为什么不泄漏
刚开始我以为Lambda只是语法糖不会有其他的作用然而哈哈 大家估计已经想到了 匿名内部类使用Lambda 时不会造成内存泄漏。 看代码
public class Test {public static void main(String[] args) {new Thread(() - {}).start();}
}将上面的代码改为Lambda 格式
class 文件
Compiled from Test.java
public class Test {public Test();Code:0: aload_01: invokespecial #1 // Method java/lang/Object.init:()V4: returnpublic static void main(java.lang.String[]);Code:0: new #2 // class java/lang/Thread3: dup4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;9: invokespecial #4 // Method java/lang/Thread.init:(Ljava/lang/Runnable;)V12: invokevirtual #5 // Method java/lang/Thread.start:()V15: return
}第一眼看上去就已经知道了答案在这份字节码中没有生成内部类
在Lambda格式中没有生成内部类而是直接使用invokedynamic 指令动态调用run方法生成一个Runnable对象。再调用调用Thread类的构造方法将生成的Runnable对象传入。从而避免了持有外部类的引用也就避免了内存泄漏的发生。
在开发中了解字节码知识还是非常有必要的在关键时刻我们查看字节码确实能帮助自己解答一些疑惑下面是常见的一些字节码指令
常见的字节码指令
Java 字节码指令是一组在 Java 虚拟机中执行的操作码用于执行特定的计算、加载、存储、控制流等操作。以下是 Java 字节码指令的一些常见指令及其功能
加载和存储指令
aload从局部变量表中加载引用类型到操作数栈。astore将引用类型存储到局部变量表中。iload从局部变量表中加载 int 类型到操作数栈。istore将 int 类型存储到局部变量表中。fload从局部变量表中加载 float 类型到操作数栈。fstore将 float 类型存储到局部变量表中。
算术和逻辑指令
iadd将栈顶两个 int 类型数值相加。isub将栈顶两个 int 类型数值相减。imul将栈顶两个 int 类型数值相乘。idiv将栈顶两个 int 类型数值相除。iand将栈顶两个 int 类型数值进行按位与操作。ior将栈顶两个 int 类型数值进行按位或操作。
类型转换指令
i2l将 int 类型转换为 long 类型。l2i将 long 类型转换为 int 类型。f2d将 float 类型转换为 double 类型。d2i将 double 类型转换为 int 类型。
控制流指令
if_icmpeq如果两个 int 类型数值相等则跳转到指定位置。goto无条件跳转到指定位置。tableswitch根据索引值跳转到不同位置的指令。
方法调用和返回指令
invokevirtual调用实例方法。invokestatic调用静态方法。invokeinterface调用接口方法。ireturn从方法中返回 int 类型值。invokedynamic: 运行时动态解析并绑定方法调用
详细的字节码指令列表和说明可参考 Java 虚拟机规范Java Virtual Machine Specification
总结
为了解决问题而储备知识是最快的学习方式。
在开发中也不要刻意去设计invokedynamic的代码但是Java开发的同学Lambda是必选项哦