Android性能优化之JVMTI与内存分配

目录
  • 前言
  • JVMTI
    • JVMTI 简介:
  • native层开启jvmti
    • 前置准备
    • 复写Agent
    • 开启jvmtiCapabilities
    • 设置jvmtiEventCallbacks
    • 开启监听
  • java层开启agent
  • 验证分配数据
  • 总结

前言

内存治理一直是每个开发者最关心的问题,我们在日常开发中会遇到各种各样的内存问题,比如OOM,内存泄露,内存抖动等等,这些问题都有以下共性:

  • 难发现,内存问题一般很难发现,业务开发中关系系数更少
  • 治理困难,内存问题治理困难,比如oom,往往堆栈只是压死骆驼的最后一根稻草
  • 易复发,几乎没有一种方案,能够杜绝内存问题,比如内存泄露几乎是100%存在,只是不同项目影响的范围不同而已

内存问题目前经过业内多年沉淀以及开发,已经有很多方案了,比如检查内存泄露(LeakCanary,MIT,KOOM等)。相关文章已经有很多,所以我们从另一个角度出发,虚拟机侧有没有想过的方案检测内存呢?有的,那就是JVMTI(Java Virtual Machine Tool Interface)即指 Java 虚拟机工具接口,它是一套由虚拟机直接提供的 native 接口,我们可以从这里面获取虚拟机运行时的大部分信息。

友情提示:本文涉及native c层的代码,如果读者不熟悉也没关系,已经尽量减少相关的代码阅读成本啦!冲就对啦!JVMTI在debug模式下有很多用处,当然release环境也可以通过hook方式开启,但是不太建议,虽然jvmti有诸多限制,但是不妨碍我们多了解一个“黑科技”

JVMTI

JVMTI 简介:

JVMTI,即由java虚拟机提供的面向虚拟机接口的一套监控api,虽然虚拟机中一直存在,但是在android中是在Android 8.0(API 级别 26)或更高版本的设备上才正式支持。jvmti的功能本质就是“埋点化”,把jvm的一些事件通过“监听”的方式暴露给外部开发调试

jvmti监听的事件包包含了虚拟机中线程、内存、堆、栈、类、方法、变量,事件、定时器,锁等创建销毁相关事件,本次我们从实战的角度出发,看看如何实现一次内存分配的监听

native层开启jvmti

前置准备

使用jvmti之前,我们需要创建一个native工程,同时我们需要使用jvmti的api,在native中就是头文件了,我们需要复制一份jdk中的名叫jvmti.h的头文件(在我们安装的jdk/include目录下),到我们的项目cpp根目录即可

此时我们也自定义一个memory.cpp作为我们使用jvmti的函数载体。jvmti.h里面包含了我们所需要的一切函数定义与常量,当然,这个头文件并不需要随着native工程进行打包,因为在真正使用到jvmti相关的工具时,是由系统进行so依赖查找进行定位的,该so位于系统库中(libopenjdkjvmtid.so、libopenjdkjvmti.so),所以我们不用关心具体的实现,接下来我们按照步骤进行即可,包括native层与java层

复写Agent

作为第一步,我们需要复写jvmti.h中的

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved);

这个是jvmti中的agent初始化的时候,由native回调,在这里我们可以拿到JavaVM环境,同时可以创建jvmtiEnv对象,该对象非常重要,用于native进行接下来的各种监听处理

// 全局的jvmti环境变量
jvmtiEnv *mJvmtiEnv;
extern "C"
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
    //准备JVMTI环境,初始化mJvmtiEnv
    vm->GetEnv((void **) &mJvmtiEnv, JVMTI_VERSION_1_2);
    return JNI_OK;
}

开启jvmtiCapabilities

默认时,jvmti中是不提供任何能力给我们使用的,我们可以通过jvmtiEnv,去查询当前虚拟机实现的哪几种jvmti回调

jvmtiError GetPotentialCapabilities(jvmtiCapabilities* capabilities_ptr) {
  return functions->GetPotentialCapabilities(this, capabilities_ptr);
}
jvmtiError AddCapabilities(const jvmtiCapabilities* capabilities_ptr) {
  return functions->AddCapabilities(this, capabilities_ptr);
}

可以看到,我们只需要传入一个jvmtiCapabilities对象指针即可,之后的能力数据就会被填充到该对象,所以我们接下来在Agent_OnAttach函数中继续补充以下代码

//初始化工作
extern "C"
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
    //准备JVMTI环境,初始化mJvmtiEnv
    vm->GetEnv((void **) &mJvmtiEnv, JVMTI_VERSION_1_2);
    //开启JVMTI的能力:到这一步啦!!
    jvmtiCapabilities caps;
    mJvmtiEnv->GetPotentialCapabilities(&caps);
    mJvmtiEnv->AddCapabilities(&caps);
    __android_log_print(ANDROID_LOG_ERROR, "hello", "Agent_OnAttach");
    return JNI_OK;
}

设置jvmtiEventCallbacks

我们已经查询到了jvmti所支持的回调,这个时候就到了正式设置回调的环节,jvmti中支持以下几种回调类型

typedef struct {
                              /*   50 : VM Initialization Event */
    jvmtiEventVMInit VMInit;
                              /*   51 : VM Death Event */
    jvmtiEventVMDeath VMDeath;
                              /*   52 : Thread Start */
    jvmtiEventThreadStart ThreadStart;
                              /*   53 : Thread End */
    jvmtiEventThreadEnd ThreadEnd;
                              /*   54 : Class File Load Hook */
    jvmtiEventClassFileLoadHook ClassFileLoadHook;
                              /*   55 : Class Load */
    jvmtiEventClassLoad ClassLoad;
                              /*   56 : Class Prepare */
    jvmtiEventClassPrepare ClassPrepare;
                              /*   57 : VM Start Event */
    jvmtiEventVMStart VMStart;
                              /*   58 : Exception */
    jvmtiEventException Exception;
                              /*   59 : Exception Catch */
    jvmtiEventExceptionCatch ExceptionCatch;
                              /*   60 : Single Step */
    jvmtiEventSingleStep SingleStep;
                              /*   61 : Frame Pop */
    jvmtiEventFramePop FramePop;
                              /*   62 : Breakpoint */
    jvmtiEventBreakpoint Breakpoint;
                              /*   63 : Field Access */
    jvmtiEventFieldAccess FieldAccess;
                              /*   64 : Field Modification */
    jvmtiEventFieldModification FieldModification;
                              /*   65 : Method Entry */
    jvmtiEventMethodEntry MethodEntry;
                              /*   66 : Method Exit */
    jvmtiEventMethodExit MethodExit;
                              /*   67 : Native Method Bind */
    jvmtiEventNativeMethodBind NativeMethodBind;
                              /*   68 : Compiled Method Load */
    jvmtiEventCompiledMethodLoad CompiledMethodLoad;
                              /*   69 : Compiled Method Unload */
    jvmtiEventCompiledMethodUnload CompiledMethodUnload;
                              /*   70 : Dynamic Code Generated */
    jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
                              /*   71 : Data Dump Request */
    jvmtiEventDataDumpRequest DataDumpRequest;
                              /*   72 */
    jvmtiEventReserved reserved72;
                              /*   73 : Monitor Wait */
    jvmtiEventMonitorWait MonitorWait;
                              /*   74 : Monitor Waited */
    jvmtiEventMonitorWaited MonitorWaited;
                              /*   75 : Monitor Contended Enter */
    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
                              /*   76 : Monitor Contended Entered */
    jvmtiEventMonitorContendedEntered MonitorContendedEntered;
                              /*   77 */
    jvmtiEventReserved reserved77;
                              /*   78 */
    jvmtiEventReserved reserved78;
                              /*   79 */
    jvmtiEventReserved reserved79;
                              /*   80 : Resource Exhausted */
    jvmtiEventResourceExhausted ResourceExhausted;
                              /*   81 : Garbage Collection Start */
    jvmtiEventGarbageCollectionStart GarbageCollectionStart;
                              /*   82 : Garbage Collection Finish */
    jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
                              /*   83 : Object Free */
    jvmtiEventObjectFree ObjectFree;
                              /*   84 : VM Object Allocation */
    jvmtiEventVMObjectAlloc VMObjectAlloc;
} jvmtiEventCallbacks;

我们需要监听的是内存分配与销毁的监听即可,分别是VMObjectAlloc与ObjectFree,在jvmtiEventCallbacks设定我们想要监听的事件之后,我们可以通过jvmtiEnv->SetEventCallbacks方法设定即可,所以我们可以继续在Agent_OnAttach中补充以下代码

jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMObjectAlloc = &objectAlloc;
callbacks.ObjectFree = &objectFree;
//设置回调函数
mJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));

其中objectAlloc是我们自定义的监听处理函数,如果jvm执行内存分配事件,就会回调此函数,该函数定义是

typedef void (JNICALL *jvmtiEventVMObjectAlloc)
    (jvmtiEnv *jvmti_env,
     JNIEnv* jni_env,
     jthread thread,
     jobject object,
     jclass object_klass,
     jlong size);

所以我们自定义的回调函数也要根据此定义进行编写。因为这里会回调所有java层的对象创建事件,回调次数非常多,在实际中我们可能并不关心系统类是如何分配内存的,而是关心我们自己的项目中的类的内存情况,所以这里我们做一个过滤,只有是项目的类我们才进行记录

void JNICALL objectAlloc(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread,
                         jobject object, jclass object_klass, jlong size) {
    jvmti_env->SetTag(object, tag);
    tag+= 1;
    char *classSignature;
    // 获取类签名
    jvmti_env->GetClassSignature(object_klass, &classSignature, nullptr);
    // 过滤条件
    if(strstr(classSignature, "com/test/memory") != nullptr){
        __android_log_print(ANDROID_LOG_ERROR, "hello", "%s",classSignature);
        myVM->AttachCurrentThread( &currentEnv, nullptr);
        // 这个list我们之后解释
        list.push_back(tag);
        char str[500];
        char *format = "%s: object alloc {Tag:%lld} \r\n";
        sprintf(str, format, classSignature,
                tag);
        memoryFile->write(str, sizeof(char) * strlen(str));
    }
    jvmti_env->Deallocate((unsigned char *) classSignature);
}

我们可以看到,我们在中间做了一个jvmti_env->SetTag的操作,这个是给这个分配的对象进行了一个打标签的动作(我们需要观察该对象是否被销毁,所以需要一个唯一标识符),我们会在释放的时候用到。因为回调的操作可能会有很多,我们采用普通的io必定会导致native层的阻塞,所以这里就要靠我们的mmap登场了,通过mmap我们可以高效的处理频繁的io,mmap不熟悉的可以看这篇,memoryFile->write是一个通过mmap的写文件操作。

objectFree是我们的释放内存的监听,它的函数定义是

typedef void (JNICALL *jvmtiEventObjectFree)
    (jvmtiEnv *jvmti_env,
     jlong tag);

可以看到,我们在释放内存的时候得到的信息非常有限,只有一个tag,也就是我们在分配内存时通过SetTag操作所得到的参数,如果有设置就就会为具体的tag数值。我们在这个函数中的业务逻辑就是记录当次的释放记录即可

void JNICALL objectFree(jvmtiEnv *jvmti_env,
                        jlong tag) {
    std::list<int>::iterator it = std::find(list1.begin(), list1.end(), tag);
    if (it != list.end()) // 找到了
    {
        __android_log_print(ANDROID_LOG_ERROR, "hello", "release %lld",tag);
        char str[500];
        char *format = "release tag %lld\r\n";
        //ALOGI(format, GetCurrentSystemTime().c_str(),threadInfo.name, classSignature, size, tag);
        sprintf(str, format,tag);
        memoryFile->write(str, sizeof(char) * strlen(str));
    }
}

我们再回到上述代码留下的疑问,list是个什么?其实就是记录了我们在VMObjectAlloc阶段所分配的属于我们自定义的类的tag,因为ObjectFree提供给我们的信息非常有限,只有一个tag,如果不通过这个list保存分配内存时的tag的话,就会导致释放的时候我们引入过多的不必要的释放记录。但是这里也带来了一个问题,就是我们需要时刻同步list的状态,因为jvmti是可以在多线程环境下回调,如果只是简单操作list的话就会带来同步问题(这里我们没有处理,为了demo的简单)真实操作上我们最好加入mutex锁或者其他机制保证同步问题。

下面我们再给出memoryFile->write的代码

currentSize 记录当前大小 m_size 以页为单位的默认大小
void MemoryFile::write(char *data, int dataLen) {
    mtx.lock();
    if(currentSize + dataLen >= m_size){
        resize(currentSize+dataLen);
    }
    memcpy(ptr + currentSize, data, dataLen);
    currentSize += dataLen;
    mtx.unlock();
}
void MemoryFile::resize(int32_t needSize) {
    // 如果mmap的大小不够,就需要重新进行mmap操作,以页为单位
    int32_t oldSize = m_size;
    do{
        m_size *=2;
    } while (m_size<needSize);
    ftruncate(m_fd, m_size);
    munmap(ptr, oldSize);
    ptr = static_cast<int8_t *>(mmap(0,m_size,PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
}

开启监听

到这里,我们还没有结束,我们需要真正的开启监听,前面只是设置监听的操作,我们可以通过SetEventNotificationMode函数开启真正监听/关闭监听

jvmtiError SetEventNotificationMode(jvmtiEventMode mode,
          jvmtiEvent event_type,
          jthread event_thread,
           ...) {
  return functions->SetEventNotificationMode(this, mode, event_type, event_thread);
}

mode代表当前状态,是个枚举,event_type就是我们要开启监听的类型(这里我们指定为内存分配与释放事件即可),event_thread可以指定某个线程的内存分配事件,null就是全局监听,所以我们的业务代码如下

//开启监听
mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, nullptr);
mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_OBJECT_FREE, nullptr);

java层开启agent

通过在native层设置了jvmti的监听与实现,我们还要在java层通过Debug.attachJvmtiAgent(9.0)进行开启,这里有细微差距

import android.content.Context
import android.os.Build
import android.os.Debug
import android.util.Log
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
object MemoryMonitor {
    private const val JVMTI_LIB_NAME = "libjvmti-monitor.so"
    fun init(context: Context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //查找SO的路径
            val libDir: File = File(context.filesDir, "lib")
            if (!libDir.exists()) {
                libDir.mkdirs()
            }
            //判断So库是否存在,不存在复制过来
            val libSo: File = File(libDir, JVMTI_LIB_NAME)
            if (libSo.exists()) libSo.delete()
            val findLibrary =
                ClassLoader::class.java.getDeclaredMethod("findLibrary", String::class.java)
            val libFilePath = findLibrary.invoke(context.classLoader, "jvmti-monitor") as String
            Files.copy(
                Paths.get(File(libFilePath).absolutePath), Paths.get(
                    libSo.absolutePath
                )
            )
            //加载SO库
            val agentPath = libSo.absolutePath
            System.load(agentPath)
            //agent连接到JVMTI
            attachAgent(agentPath, context.classLoader);
            val logDir = File(context.filesDir, "log")
            val path = "${logDir.absolutePath}/test.log"
            initMemoryCallBack(path)
        } else {
            Log.e("memory", "jvmti 初始化异常")
        }
    }
    //agent连接到JVMTI
    private fun attachAgent(agentPath: String, classLoader: ClassLoader) {
        //Android 9.0+
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            Debug.attachJvmtiAgent(agentPath, null, classLoader)
        } else {
            //android 9.0以下版本使用反射方式加载
            val vmDebugClazz = Class.forName("dalvik.system.VMDebug")
            val attachAgentMethod = vmDebugClazz.getMethod("attachAgent", String::class.java)
            attachAgentMethod.isAccessible = true
            attachAgentMethod.invoke(null, agentPath)
        }
    }
    // 设置mmap的文件path
    external fun initMemoryCallBack(path: String)
}

attachJvmtiAgent方法需要实现了jvmti 的so库的绝对地址,那么我们如何查找一个so库的地址呢?其实就是通过ClassLoader的findLibrary方法,我们可以获取到so的绝对地址,不过这个绝对地址不能够直接用,我们看一下源码attachJvmtiAgent

public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
        @Nullable ClassLoader classLoader) throws IOException {
    Preconditions.checkNotNull(library);
    Preconditions.checkArgument(!library.contains("="));
    if (options == null) {
        VMDebug.attachAgent(library, classLoader);
    } else {
        VMDebug.attachAgent(library + "=" + options, classLoader);
    }
}

其中attachJvmtiAgent 会进行格式校验Preconditions.checkArgument(!library.contains("=")),恰好我们得到的so的地址是包含=的,所以才需要一个File的copy操作(拷贝到一个不包含=的目录下)

验证分配数据

通过上面的jvmti操作,我们已经可以将数据保存到本地文件了,本地文件的保存可以自己定义,这里我保存在context.filesDir目录中/log子目录下,同时我们生成一个测试数据

package com.test.memory
data class TestData(val test:Int) {
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
    binding.sampleText.text = "Hello World"
    TestData(1)
}

运行后

我们就完成了一个内存的记录,通过该记录我们就能够分析哪些类引起了内存问题(即存在分配tag不存在释放tag)

总结

到这里,我们终于完成了一个jvmti的监控操作!当然,上面的代码还有很多需要提升的地方,比如多线程引用,比如我们可以同时开启MethodEntry的callback记录一个方法的开始和结束,为内存泄漏的定位做更加详细的分析等等!因为篇幅有限,这里就当作拓展留给读者们自行实现啦,以上就是Android性能优化之JVMTI与内存分配的详细内容,更多关于Android性能JVMTI内存分配的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android性能优化系列篇UI优化

    目录 前言 一.UI优化 1.1 系统做的优化 1.1.1 硬件加速 1.2 优化方案 1.2.1 java代码布局 1.2.2 View重用 1.2.3 异步创建view 1.2.4 xml布局优化 1.2.5 异步布局框架Litho 1.2.6 屏幕适配 1.2.7 Flutter 1.2.8 Jetpack Compose 1.3 工具篇 1.3.1 Choreographer 1.3.2 LayoutInspector/Android Device Monitor 1.3.3 Systr

  • Android性能优化之plt hook与native线程监控详解

    目录 背景 native 线程创建 PLT PLT Hook xhook bhook plt hook总结 背景 我们在android超级优化-线程监控与线程统一可以知道,我们能够通过asm插桩的方式,进行了线程的监控与线程的统一,通过一系列的黑科技,我们能够将项目中的线程控制在一个非常可观的水平,但是这个只局限在java层线程的控制,如果我们项目中存在着native库,或者存在着很多其他so库,那么native层的线程我们就没办法通过ASM或者其他字节码手段去监控了,但是并不是就没有办法,还有

  • Android性能优化死锁监控知识点详解

    目录 前言 死锁检测 线程Block状态 获取当前线程所请求的锁 通过锁获取当前持有的线程 线程启动 nativePeer 与 native Thread tid 与java Thread tid dlsym与调用 系统限制 死锁检测所有代码 总结 前言 “死锁”,这个从接触程序开发的时候就会经常听到的词,它其实也可以被称为一种“艺术”,即互斥资源访问循环的艺术,在Android中,如果主线程产生死锁,那么通常会以ANR结束app的生命周期,如果是两个子线程的死锁,那么就会白白浪费cpu的调度资

  • Android性能优化之弱网优化详解

    目录 弱网优化 1.Serializable原理 1.1 分析过程 1.2 Serializable接口 1.3 ObjectOutputStream 1.4 序列化后二进制文件的一点解读 1.5 常见的集合类的序列化问题 1.5.1 HashMap 1.5.2 ArrayList 2.Parcelable 2.1 Parcel的简介 2.2 Parcelable的三大过程介绍(序列化.反序列化.描述) 2.2.1 描述 2.2.2 序列化 2.2.3 反序列化 2.3 Parcelable的实

  • Android性能优化之捕获java crash示例解析

    目录 背景 java层crash由来 为什么java层异常会导致crash 捕获crash 总结 背景 crash一直是影响app稳定性的大头,同时在随着项目逐渐迭代,复杂性越来越提高的同时,由于主观或者客观的的原因,都会造成意想不到的crash出现.同样的,在android的历史化过程中,就算是android系统本身,在迭代中也会存在着隐含的crash.我们常说的crash包括java层(虚拟机层)crash与native层crash,本期我们着重讲一下java层的crash. java层cr

  • Android性能优化之线程监控与线程统一详解

    目录 背景 常规解决方案 线程监控 当前线程统计 线程信息具体化 线程统一 Thread创建 注意 总结 背景 在我们日常开发中,多线程管理一直是非常头疼的问题之一,尤其在历史性长,结构复杂的app中,线程数会达到好几百个甚至更多,然而过多的线程不仅仅带来了内存上的消耗同时也降低了cpu调度的效率,过多的cpu调度带来的消耗的坏处甚至超过了多线程带来的好处. 在我们日常开发中,通常会遇到以下几个问题 某个场景会创造过多的线程,最终导致oom 线程池过多问题,比如三方库有一套线程池,自己项目也有一

  • Android性能优化之RecyclerView分页加载组件功能详解

    目录 引言 1 分页加载组件 1.1 功能定制 1.2 手写分页列表 1.3 生命周期管理 2 github 引言 在Android应用中,列表有着举足轻重的地位,几乎所有的应用都有列表的身影,但是对于列表的交互体验一直是一个大问题.在性能比较好的设备上,列表滑动几乎看不出任何卡顿,但是放在低端机上,卡顿会比较明显,而且列表中经常会伴随图片的加载,卡顿会更加严重,因此本章从手写分页加载组件入手,并对列表卡顿做出对应的优化 1 分页加载组件 为什么要分页加载,通常列表数据存储在服务端会超过100条

  • Android 性能优化实现全量编译提速的黑科技

    目录 一.背景描述 二.效果展示 2.1.测试项目介绍 三.思路问题分析与模块搭建: 3.1.思路问题分析 3.2.模块搭建 四.问题解决与实 编译流程启动,需要找到哪一个 module做了修改 module 依赖关系获取 module 依赖关系 project 替换成 aar 技术方案 hook 编译流程 五.一天一个小惊喜( bug 较多) 5.1 output 没有打包出 aar 5.2 发现运行起来后存在多个 jar 包重复问题 5.3 发现 aar/jar 存在多种依赖方式 5.4 发

  • Android性能优化之JVMTI与内存分配

    目录 前言 JVMTI JVMTI 简介: native层开启jvmti 前置准备 复写Agent 开启jvmtiCapabilities 设置jvmtiEventCallbacks 开启监听 java层开启agent 验证分配数据 总结 前言 内存治理一直是每个开发者最关心的问题,我们在日常开发中会遇到各种各样的内存问题,比如OOM,内存泄露,内存抖动等等,这些问题都有以下共性: 难发现,内存问题一般很难发现,业务开发中关系系数更少 治理困难,内存问题治理困难,比如oom,往往堆栈只是压死骆驼

  • Android性能优化之利用强大的LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来.所以决定抽空学习总结一下这方面的知识,以及分享一下我们是如何检测内存泄漏的.我们公司使用开源框架LeakCanary来检测内存泄漏. 什么是内存泄漏? 有些对象只有有限的生命周期.当它们的任务完成之后,它们将被垃圾回收.如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,这就会导致内存泄漏

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • Android性能优化以及数据优化方法

    Android性能优化-布局优化 今天,继续Android性能优化 一 编码细节优化. 编码细节,对于程序的运行效率也是有很多的影响的.今天这篇主题由于技术能力有限,所以也不敢在深层去和大家分享.我将这篇主题分为以下几个小节: (1)缓存 (2)数据 (3)延迟加载和优先加载 1> 缓存 在Android中缓存可以用在很多的地方:对象.IO.网络.DB等等..对象缓存能减少内存分配,IO缓存能对磁盘的读写访问,网络缓存能减少对网络的访问,DB缓存能减少对数据库的操作. 缓存针对的场景在Andro

  • Android 性能优化系列之bitmap图片优化

    背景 Android开发中,加载图片过多.过大很容易引起OutOfMemoryError异常,即我们常见的内存溢出.因为Android对单个应用施加内存限制,默认分配的内存只有几M(具体视不同系统而定).而载入的图片如果是JPG之类的压缩格式(JPG支持最高级别的压缩,不过该压缩是有损的),在内存中展开会占用大量的内存空间,也就容易形成内存溢出.那么高效的加载Bitmap是很重要的事情.Bitmap在Android中指的是一张图片,图片的格式有.jpg .jpg .webp 等常见的格式. 如何

  • Android性能优化方案详情

    目录 1.指标 2.包大小优化 3.响应时间优化 4.内存优化 5.CPU优化 6.耗电量优化 前言: 上一个季度在百度工作挺忙碌,在最后期限完成了OKR目标,因此有一段时间没有写文章.今天趁有机会想分享下在大型Android项目工程内的一些性能优化方式. 1.指标 量化性能的指标有很多,但最重要的就是以下5种: 包大小 响应时间 内存 CPU 耗电量 优化性能就是可以从以上5点入手. 2.包大小优化 顾名思义就是减少apk包体积大小,apk大小主要取决于res下的资源文件..class文件,

  • Android性能优化及性能优化工具

    目录 1.Allaction Tracking (1)追踪 (2)分类我们的内存分配 (3)查看统计图 2.LeakCanary (1)配置 (2)制造一个单例内存泄漏的点 (3)LeakCanary 发出内存泄漏通知 (4)LeakCanary 分析 3.Lint分析工具  性能优化的帮助工具: MAT, Memory Monitor(属于AndroidMonitor中一个模块), HeapTool(查看堆信息), Allaction Tracking, LeakCanary Lint工具 1

  • Android性能优化之图片大小,尺寸压缩综合解决方案

    目录 前言 常见的图片压缩方法 质量压缩 尺寸压缩 libjpeg 图片压缩流程 总结 前言 在Android中我们经常会遇到图片压缩的场景,比如给服务端上传图片,包括个人信息的用户头像,有时候人脸识别也需要捕获图片等等.这种情况下,我们都需要对图片做一定的处理,比如大小,尺寸等的压缩. 常见的图片压缩方法 质量压缩 尺寸压缩 libjpeg 质量压缩 首先我们要介绍一个api--Bitmap.compress() @WorkerThread public boolean compress(Co

  • Android性能优化方法

    GPU过度绘制 •打开开发者选型,"调试GPU过度绘制",蓝.绿.粉红.红,过度绘制依次加深  •粉红色尽量优化,界面尽量保持蓝绿颜色  •红色肯定是有问题的,不能忍受 使用HierarchyView分析布局层级 •删除多个全屏背景:应用中不可见的背景,将其删除掉  •优化ImageView:对于先绘制了一个背景,然后在其上绘制了图片的,9-patch格式的背景图中间拉伸部分设置为透明的,Android 2D渲染引擎会优化9-patch图中的透明像素.这个简单的修改可以消除头像上的过度

随机推荐