性能优化可以在这几个方面下手, 流畅性 , 稳定性 , 包体积大小
稳定性优化
影响App稳定性常见的有两个场景 Crash 和 ANR,它会导致App异常退出。所以解决App的稳定性应该列为最高优先级。如何避免异常的发生,可以从这几个方面入手Linux的进程内存模型是由 用户空间 和 内核空间 组成。
在应用程序中,如果直接操作外围设备,访问时也不知道其他程序有没有在访问,也不知道哪一段可以用的,大家你争我抢的,都乱套了,而且也不安全。所以需要一位管理者--操作系统。操作系统将真实的物理地址隔离起来,给每个程序分配一段虚拟地址,通过mmap将真实地址和虚拟地址起来,比如虚拟地址是0x00,那么它真实的物理地址可能是0x1c。在真实物理地址它可能不是一段连续的地址,但是在虚拟地址是连续的就可以了。
虚拟空间还可以进行细分:
内核空间(进程管理,存储管理,文件管理,设备管理,网络系统等) ---------- 栈 FileMapping 堆 BSS Data text 复制代码
进程由n个线程组成,在JVM中,又对进程以线程为单位对内存进行划分。
线程的内存分配:
在操作系统看来,JVM是一个程序,而Java程序只是运行在程序上的程序,所以JVM需要模拟程序运行的环境。
(图片来源:csdn-骁兵)
public class Test {
public void test(int b, int a) {
int x = 6;
if (b > 0) {
String str = "VeCharm";
}
int y = a;
int c = y + b;
}
}
----------------
javac Test.java
javap -v Test
----------------
class信息:
Last modified 2019-3-31; size 347 bytes
MD5 checksum b0e2fc2ec7a2d576136a693c77213446
Compiled from "Test.java"
public class com.vecharm.lychee.sample.api.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
...
{
public com.vecharm.lychee.sample.api.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public void test(int, int);
descriptor: (II)V
flags: ACC_PUBLIC
Code:
stack=2, locals=6, args_size=3
0: bipush 6
2: istore_3
3: iload_1
4: ifle 11
7: ldc #2 // String VeCharm
9: astore 4
11: iload_2
12: istore 4
14: iload 4
16: iload_1
17: iadd
18: istore 5
20: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 7
line 11: 11
line 12: 14
line 13: 20
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 11
locals = [ int ]
}
SourceFile: "Test.java"
复制代码 public class Test {
public void test(int b, int a) {
int x = 6;
if (b > 0) {
String str = "VeCharm";
}
int y = a;
int c = y + b;
}
}
----------------
javac Test.java
javap -v Test
----------------
class信息:
...
Constant pool:
#1 = Methodref #4.#14 // java/lang/Object."<init>":()V
#2 = String #15 // VeCharm
#3 = Class #16 // com/vecharm/lychee/sample/api/Test
#4 = Class #17 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 test
#10 = Utf8 (II)V
#11 = Utf8 StackMapTable
#12 = Utf8 SourceFile
#13 = Utf8 Test.java
#14 = NameAndType #5:#6 // "<init>":()V
#15 = Utf8 VeCharm
#16 = Utf8 com/vecharm/lychee/sample/api/Test
#17 = Utf8 java/lang/Object
...
SourceFile: "Test.java" 复制代码 | CONSTANT_Utf8 | UTF-8编码的Unicode字符串 |
| CONSTANT_Integer | int类型字面值 |
| CONSTANT_Float | float类型字面值 |
| CONSTANT_Long | long类型字面值 |
| CONSTANT_Double | double类型字面值 |
| CONSTANT_Class | 对一个类或接口的符号引用 |
| CONSTANT_String | String类型字面值 |
| CONSTANT_Fieldref | 对一个字段的符号引用 |
| CONSTANT_Methodref | 对一个类中声明的方法的符号引用 |
| CONSTANT_InterfaceMethodref | 对一个接口中声明的方法的符号引用 |
| CONSTANT_NameAndType | 对一个字段或方法的部分符号引用 |
看到这想必就已经知道了一个从一个Java文件到内存是如何运作的了。类从加载到虚拟机内存中开始到卸载内存为止,它的整个生命周期包括:加载,验证,准备,解析,初始化,使用,和卸载7个阶段,其中验证,准备,解析3个部分被称为连接。
加载,验证,准备,初始化和卸载这5个阶段是确定的,类的加载过程是必须按照顺序来,而解析阶段这个可以在初始化之后开始,这是为了支持运行时绑定(动态绑定)。
说到底,编程就是编的只是数据和指令,来总结一下流程。
在内存模型中,我们需要重点关注的就是Heap。因为它是由我们来控制的,处理不当容易发生OOM。内存处理的步骤无非也就三个: 申请,整理,清除。管理内存打个比方就和管理卖戏票的,观众台也就几十个座位,都是宝贵的资源。vip大户,里边走,直接坐贵席。其他的买计时票看,每隔一定时间把到时的人清出去,但经常有人到时赖着不走,隔一段时间催他才走。有时候座满了,只能把到时的赖皮清出去,不想走可以交钱。有时候人家三五成群的买票,自然要调配一下,清理出一些连座的给客户对吧。如果是一大帮人来看,更是欢迎,vip里面请。
在Java的堆模型中划分为三个区。
管理对象的生命周期
生存还是毁灭,是通过这个对象到GC Root的可到达性来决定的。能作为GC Root的对象有四种。
引用类型有四种,强引用,软引用,弱引用,虚引用。
结语,限于篇幅,只是初略的整理了一下大致的流程,参考《深入Java虚拟机》等。