精华内容
下载资源
问答
  • 2020-11-04 22:51:12

    一、概述

    在Android诞生至今,也已经有十年了。整个开发社区也更加的活跃。在高速的信息发展时代,社区的各个成员也将自身的知识分享出去,对于一些功能的开发,网上也充斥着各种解决方案。我们往往能在网上找到适合自身功能的解决方案,在完成功能之后,却往往对功能模块的稳定性和相关性能进行测试。在如今的时代中,面对大量“年久失修”的历史代码,又引入不同跨平台方案的应用,我们又该如何打造一款高质量的应用呢。
    及时Android发展了10余年,内存带来的各种问题从史贯穿至今,即使现在高性能的手机已经推出了8GB,4GB的内存,但内存问题依然影响着我们。在日常开发过程中,大家肯定或多或少都接触过内存优化,以及内存所带来的问题。我们将从理解内存、内存产生的问题到实战如何解决内存来一起探索Android中的内存优化。
    本篇文章主要来理解java中对象内存的分配和使用。

    二、理解内存

    Android 运行时 (ART) 和 Dalvik 虚拟机使用分页内存映射来管理内存。这意味着应用修改的任何内存,无论修改的方式是分配新对象还是轻触内存映射的页面,都会一直驻留在 RAM 中,并且无法换出。要从应用中释放内存,只能释放应用保留的对象引用,使内存可供垃圾回收器回收

    2.1 手机RAM:

    手机运行内存(RAM)其实相当于我们的 PC 中的内存,是手机中作为 App 运行过程中临时性数据暂时存储的内存介质。不过考虑到体积和功耗,手机不使用 PC 的 DDR 内存,采用的是 LPDDR RAM,全称是“低功耗双倍数据速率内存”,其中 LP 就是“Lower Power”低功耗的意思。手机系统内存是指手机运行程序时使用的内存(即运行内存),只能临时存储数据,用于与CPU交换高速缓存数据,但是随机存储器(RAM)本身不能用于长期存储数据。

    LPDDR系列的带宽 = 时钟频率 ✖️内存总线位数 / 8
    LPDDR4 = 1600MHZ ✖️64 / 8 ✖️双倍速率 = 25.6GB/s。
    

    内存类型 分页 误区

    2.2 内存占用越少越好?

    当系统内存充足的时候,我们可以多用一些获得更好的性能。当系统内存不足的时候,希望可以做到”用时分配,及时释放“。

    三、Android内存管理机制

    大家肯能都或多或少对Android内存管理机制有所了解,JVM拥有垃圾内存回收机制,自身会在虚拟机层面自动分配和释放内存,因此不需要使用C/C++一样在代码中可以直接控制分配和释放某一块内存。即便有了内存自动管理机制,但是在我们日常的业务开发中稍有不慎,不合理的使用内存,也会造成一系列的性能问题,比如内存泄漏,内存抖动,短时间内分配大量的内存对象等。
    Android中我们使用的有java对象,所以需要了解下JVM相关的知识。

    3.1 java内存区域

    Java 中的运行时数据可以划分为两部分,一部分是线程私有的,包括虚拟机栈、本地方法栈、程序计数器,另一部分是线程共享的,包括方法区和堆。

    区域名称作用
    程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。计算当前线程的当前方法执行到多少行
    虚拟机栈虚拟机栈描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接地址、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈桢在虚拟机中入栈和出栈的过程。
    本地方法栈native变量引用,Native 方法执行的内存模型
    java堆Java 堆是所有线程共享的一块数据区域,主要用来存放对象实例。它也是垃圾收集器管理的主要区域。
    方法区存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据

    3.2 java内存模型

    Java 内存模型规定了所有的共享变量都是存储在主内存,每个线程还有自己的工作内存,线程的工作内存保存了该线程使用到的共享变量的主内存副本拷贝,线程对变量的操作都必须在工作内存中进行,而不能直接读写主内存中的变量,不同的线程之间也无法直接访问对方工作内存中的数据,线程间变量值的传递均需要主内存来完成。

    3.3 java对象的创建

    3.3.1 java对象创建过程

    对象的创建是我们经常要做的事,通常是通过new 指令来完成一个对象的创建的。

    1. 当虚拟机接收到一个new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过了,如果没有就走类加载流程。

    2. 在类加载检查通过之后,虚拟机就会为新生对象分配内存,对象所需内存在类加载完成之后就确定了。内存分配根据Java堆是否规整决定,指针碰撞空闲列表

    3. 对象创建在虚拟机是非常频繁的行为,所以需要解决并发问题,解决方案有两种,一种是采用CAS算法并配上失败尝试的方式保证更新操作的原子性,另一种是使用线程私有的分配缓冲区TLAB

    4. 初始化分配的内存,除了对象头外斗初始化位零值

    5. 设置对象的对象头,将对象的所属类、对象的Hashcode和对象的GC分代年龄等数据存储在对象的对象头中。

    3.3.2 对象的堆内存布局

    对象创建完毕,并且已经在Java堆中分配了内存,对象在对内存布局分为三个区域 ,分别是对象头实例数据对齐填充。可以使用 OpenJDK 开源的 JOL 工具查看对象的内存布局,直接 new Object 所占用的大小为 16 字节,即 12 个字节的对象头 + 4 个字节的对其填充。JOL 对分析集合源码扩容、HashMap 的 hash 冲突等非常有用。

    3.3.3 对象的访问定位

    Java 程序需要通过栈上的 reference 数据来操作堆上的具体对象,由于 reference 类型在 Java 虚拟机规范中只规定了一个指向对象的引用,并没有规定这个引用应该通过什么方式去定位和访问堆中的对象,所以对象访问方式也是取决于虚拟机实现而定。
    目前主流的方式有使用句柄和直接指针两种。
    使用句柄,就是相当于加了一个中间层,在对象移动时只会改变句柄中的实例数据的指针,reference 本身不需要改变。HotSpot 使用的是第二种,使用直接指针的方式访问的最大好处就是速度很快。

    3.4 Android内存回收 --GC

    3.4.1 垃圾标记算法

    在垃圾收集器回收对象时,先要判断对象是否已经不再使用了,有引用计数法和可达性分析两种

    • 引用计数法
      引用计数法就是给对象添加一个引用计数器,每当有一个地方引用时就加一,引用失效时就减一。
      引用计数实现简单,判断效率也很高,但是他很难解决对象之间的相互循环引用问题。所以JVM并未采用

    • 可达性分析
      可达性分析的思路是通过一系列称为 GC Roots 的对象作为起始点,从这些起始点出发向下搜索,当有一个对象到 GC Roots 没有任何引用链时,即不可达,则说明此对象是不可用的。在 Java 中,可作为 GC Roots 的对象有虚拟机栈和本地方法栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象等。

    Java 中的引用可以分为四类,强引用、软引用、弱引用和虚引用。
    强引用在程序中普遍存在,类似 new 的这种操作,只要有强引用存在,即使 OOM JVM 也不会回收该对象。软引用是在内存不够用时,才会去回收,JDK 提供了 SoftReference 类来实现软引用。弱引用是在 GC 时不管内存够不够用都会去回收的,可以使用 WeakReference 类来实现弱引用。虚引用对对象的生命周期没有影响,只是为了能在对象回收时收到一个系统通知,可以使用 PhantomReference 类来实现虚引用。

    3.4.2 垃圾回收算法

    垃圾回收算法主要有标记清除、复制算法、标记整理、分代收集算法

    标记清除是先通过GC Roots 标记所存活的对象,然后再统一清除未标记的对象,它的主要问题是会产生内存碎片。老年代使用的CMS收集器就是基于标记清除算法

    复制算法是把内存空间划分为两块,每次分配对象只在一块内存上进行分配,在这一块内存使用完时,就直接把存活的对象复制到另外一块上,然后把已使用的那块空间一次清理掉,但是这种算法的代价就是内存的使用量缩小了一半。

    标记整理算法即是在标记清除之后,把所有存活的对象都向一端移动,然后清理掉边界以外的内存区域

    分代收集算法把Java堆区分为新生代和老年代,其中新生代划分为了一个 Eden 区和两个 Survivor 区,比例是 8:1:1,每次使用 Eden 和其中一块 Survivor 区,也就是只有 10% 的内存会浪费掉。如果 Survivor 空间不够用,需要依赖其他内存比如老年代进行分配担保。复制算法在对象存活率比较高时效率是比较低下的,所以老年代一般不使用复制算法。

    四、Android进程间内存的分配

    Android 运行时 (ART) 和 Dalvik 虚拟机使用分页内存映射来管理内存。这意味着应用修改的任何内存,无论修改的方式是分配新对象还是轻触内存映射的页面,都会一直驻留在 RAM 中,并且无法换出。要从应用中释放内存,只能释放应用保留的对象引用,使内存可供垃圾回收器回收。

    Android 平台在运行时不会浪费可用的内存。它会一直尝试利用所有可用内存。例如,系统会在应用关闭后将其保留在内存中,以便用户快速切回到这些应用。因此,通常情况下,Android 设备在运行时几乎没有可用的内存。要在重要系统进程和许多用户应用之间正确分配内存,内存管理至关重要。

    4.1 内存不足管理

    Android 有两种处理内存不足情况的主要机制:内核交换守护进程和低内存终止守护进程。

    • 内核交换守护进程

    内核交换守护进程 (kswapd) 是 Linux 内核的一部分,用于将已使用内存转换为可用内存。当设备上的可用内存不足时,该守护进程将变为活动状态。Linux 内核设有可用内存上下限阈值。当可用内存降至下限阈值以下时,kswapd 开始回收内存。当可用内存达到上限阈值时,kswapd 停止回收内存。

    kswapd 可以删除干净页来回收它们,因为这些页受到存储器的支持且未经修改。如果某个进程尝试处理已删除的干净页,则系统会将该页面从存储器复制到 RAM。此操作称为“请求分页”。

    • 低内存终止守护进程

    很多时候,kswapd 不能为系统释放足够的内存。在这种情况下,系统会使用 onTrimMemory() 通知应用内存不足,应该减少其分配量。如果这还不够,内核会开始终止进程以释放内存。它会使用低内存终止守护进程 (LMK) 来执行此操作。

    LMK 使用一个名为 oom_adj_score 的“内存不足”分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。下表列出了从高到低的 LMK 评分类别。评分最高的类别,即第一行中的项目将最先被终止

    以下是上表中各种类别的说明:

    后台应用:之前运行过且当前不处于活动状态的应用。LMK 将首先从具有最高 oom_adj_score 的应用开始终止后台应用。

    上一个应用:最近用过的后台应用。上一个应用比后台应用具有更高的优先级(得分更低),因为相比某个后台应用,用户更有可能切换到上一个应用。

    主屏幕应用:这是启动器应用。终止该应用会使壁纸消失。

    服务:服务由应用启动,可能包括同步或上传到云端。

    可觉察的应用:用户可通过某种方式察觉到的非前台应用,例如运行一个显示小界面的搜索进程或听音乐。

    前台应用:当前正在使用的应用。终止前台应用看起来就像是应用崩溃了,可能会向用户提示设备出了问题。

    持久性(服务):这些是设备的核心服务,例如电话和 WLAN。

    系统:系统进程。这些进程被终止后,手机可能看起来即将重新启动。

    原生:系统使用的极低级别的进程(例如,kswapd)。

    设备制造商可以更改 LMK 的行为。

    4.2 监听可用内存和内存使用量

    Android 可以通过多种方式从应用中回收内存,或在必要时完全终止应用,从而释放内存以执行关键任务。为了进一步帮助平衡系统内存并避免系统需要终止您的应用进程,您可以在 Activity 类中实现 ComponentCallbacks2 接口。借助所提供的 onTrimMemory() 回调方法

      import android.content.ComponentCallbacks2;
        // Other import statements ...
    
        public class MainActivity extends AppCompatActivity
            implements ComponentCallbacks2 {
            //当UI隐藏或系统资源不足时释放内存。
            public void onTrimMemory(int level) {
                switch (level) {
    
                    case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                    //释放当前持有内存的所有UI对象。用户界面已经移动到后台。
                        break;
    
                    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
                    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
                    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
                    //释放应用程序不需要运行的内存。应用程序运行时,设备内存不足。引发的事件表示与内存相关的事件的严重程度。如果事件是TRIM_MEMORY_RUNNING_CRITICAL,那么系统将开始杀死后台进程。
                        break;
    
                    case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
                    case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                    case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                 //   释放进程能释放的尽可能多的内存。该应用程序在LRU列表中,系统内存不足。引发的事件表明该应用程序在LRU列表中的位置。如果事件是TRIM_MEMORY_COMPLETE,则该进程将是第一个被终止的。
                        break;
    
                    default:
                    //应用程序收到一个无法识别的内存级别值从系统。将此消息视为一般的低内存消息。
                        break;
                }
            }
        }
    
    更多相关内容
  • labview性能优化内存管理,了解labview软件运行的原理,及如何优化labview代码
  • 预制池系统 首先需要知道预制池系统需要做什么: 如果已经存在已回收的版本,...如果对象由对象池系统管理,他应该禁用,并在附加到GameObject上的所有IPoolableComponent接口类上调用Despawned方法 如果对象没有由

    预制池系统
    首先需要知道预制池系统需要做什么:

    1. 如果已经存在已回收的版本,应该重新生成第一个可用的对象
    2. 如果不存在已回收的版本,应该从预制体中实例化新的GameObject
    3. 在上述两种情况下, 都应该在附加到GameObject上的所有IPoolableComponent接口上调用Spawned方法
    4. 必须接受请求,以回收固定GameObject
    5. 如果对象由对象池系统管理,他应该禁用,并在附加到GameObject上的所有IPoolableComponent接口类上调用Despawned方法
    6. 如果对象没有由对象池系统管理,应该报错
      以上的需求简单明了,但是如果希望使解决方案的性能更好,则需要进行一些改进
      首先,对于主要的入口来说,典型的单例是一种非常好的选择,因为单例可以做到在任何地方,全局访问;
      生成的对象的主要任务包括接受一个预制引用,指出是否有回收的GameObject从相同的引用中实例化,为此,对象池系统需要为任何给定的预制体跟踪两种不同类型的列表,一个是已经激活的(实例化的),一个使未激活的(回收的)共两个对象列表,这种数据信息,最好抽象到一个类去管理,将其命名为PrefabPool;
      另一方面,由于生成的Gameobject需要给定一个预制体,我们将通过一个数据结构能够快速的将预制体映射到管理他们的PrefabPool,同时,由于回收对象需要给定一个Game Object,我们将通过另一个数据结构把已经生成的Gameobject映射到最初生成他们的PrefabPool,这样看来我们需要一对字典,分别用来管理生成与回收

    在这里插入图片描述
    接下来需要定义在生成对象发生了什么
    在这里插入图片描述
    接下来需要定义回收对象的时候需要做什么
    在这里插入图片描述
    现在已经有了可以自动处理多个预制池的系统,剩下的工作就是如何定义这个池子的行为,如前所述,Prefab类应该维护两个数据结构,一个用于在给定的Prefab中实例化活动(派生)对象,另一个用于非活动(回收的)对象;
    从技术上来讲PrefabPoolingSystem1类已经维护了一个由PrefabPool管理的prefab的映射,所以实际上可以节省一点内存,因此这两个数据结构是PrefabPool需要跟踪的成员变量,但是由于每个GameObejct还必须要维护所有IPoolableComponent引用的列表,以便对他们调用Sapwned和Despawned方法,获取这些引用可能是在运行时执行的一个昂贵操作,所以最好将数据缓存在一个简单的数据结构中;如下
    在这里插入图片描述
    现在可以定义PrefabPool类的成员数据了
    在这里插入图片描述
    生成对象
    接下来定义在池系统容器中生成GameObject意味着什么,在某个时刻,PrefabPool会收到一个请求,从给定的预制体中利用给定位置旋转,生成GameObject,首先应该要检查是否有该预制体的非激活实例,如果有,就可以将下一个可用对象移出队列,并重新生成它,如果没有,就需要使用GameObject.Instantiate从预制体中实例化新的GameObject,此时应该创建PoolablePrefabData对象,来保存GameObject引用,并获取附加在它上面,所有实现了IPoolableComponent的MonoBehaviour列表
    下面的Spawn定义了这样的行为;
    在这里插入图片描述
    对象的回收
    在这里插入图片描述
    预制池测试
    在这里插入图片描述
    预先生成实例
    在这里插入图片描述
    预制池和场景加载
    该系统还有一个重要的警告,由于PrefabPoolingSystem是静态类,它比场景的生命周期更长,这意味着当加载新场景时,池系统的字典尝试维护之前场景中已经池化的实例的引用,但unity在切换场景时强制销毁这些对象,而不管我们依然保有对他们的引用,因此字典将充满空引用,这将为下一个场景带来很多严重的问题,因此,应该在PrefabPoolingSystem中创建一个方法,类似于重置系统 ,并且在新的场景加载前调用,为下一个场景做好准备;
    在这里插入图片描述

    全部代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using UnityEngine;
    
    public static class PrefabPoolingSystem1
    {
        static Dictionary<GameObject, PrefabPool> _prefabTopoolMap = new Dictionary<GameObject, PrefabPool>();
        static Dictionary<GameObject, PrefabPool> _goToPoolMap = new Dictionary<GameObject, PrefabPool>();
    
    
        public static GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
        {
            if(!_prefabTopoolMap.ContainsKey(prefab)) //如果缓存中没有包含要生成的对象
            {
                _prefabTopoolMap.Add(prefab, new PrefabPool());
            }
            PrefabPool pool = _prefabTopoolMap[prefab];//获得对象所在的预制池
            GameObject go = pool.Spawn(prefab, position, rotation);//使用预制池生成预制
            _goToPoolMap.Add(go, pool);//将生成的预制体与预制池保存
            return go;
        }
    
        public  static GameObject Spawn(GameObject prefab) 
        {
            return Spawn(prefab, Vector3.zero, Quaternion.identity);
        }
    
        public static bool Despawn(GameObject prefab)
        {
            if(!_goToPoolMap.ContainsKey(prefab))
            {
                Debug.LogError(string.Format("Object {0} not managed by pool system!", prefab.name));
                return false;
            }
            PrefabPool pool = _goToPoolMap[prefab];
            if(pool.Despawn(prefab))
            {
                _goToPoolMap.Remove(prefab);
                return true;
            }
            return false;
        }
        public static void Prespawn(GameObject prefab, int numToSpawn)
        {
            List<GameObject> spawnedObjects = new List<GameObject>();
    
            for (int i = 0; i < numToSpawn; i++)
            {
                spawnedObjects.Add(Spawn(prefab));
            }
    
            for (int i = 0; i < numToSpawn; i++)
            {
                Despawn(spawnedObjects[i]);
            }
    
            spawnedObjects.Clear();
        }
    
    
        public static void Reset()
        {
            _prefabTopoolMap.Clear();
            _goToPoolMap.Clear();
        }
    }
    
    
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using UnityEngine;
    
    public struct PoolablePrefabData1
    {
        public GameObject go;
        public IPoolableComponent[] poolableComponents;
    }
    class PrefabPool1
    {
        Dictionary<GameObject, PoolablePrefabData1> _activeList = new Dictionary<GameObject, PoolablePrefabData1>();
        Queue<PoolablePrefabData1> _inactiveList = new Queue<PoolablePrefabData1>();
    
        public GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
        {
            PoolablePrefabData1 data;
            if(_inactiveList.Count > 0)//如果存在已经回收的对象
            {
                data = _inactiveList.Dequeue();
            }
            else //如果不存在已经回收的对象
            {
                //那么就需要新实例化一个对象
                GameObject newGo = GameObject.Instantiate(prefab, position, rotation);//实例化
                data = new PoolablePrefabData1();
                data.go = newGo;
                data.poolableComponents = newGo.GetComponents<IPoolableComponent>();//需要获取所有IPoolableComponent引用的列表,以便调用Spawned和Despawned
            }
            //设置实例化物体的位置
            data.go.SetActive(true);
            data.go.transform.position = position;
            data.go.transform.rotation = rotation;
    
            for(int i = 0;i<data.poolableComponents.Length; i++)
            {
                data.poolableComponents[i].Spawned();//所有的实现了IPoolableComponent接口的都需要调用Spawned
            }
            _activeList.Add(prefab, data);
    
            return data.go;
        }
    
        public bool Despawn(GameObject objToDespawn)
        {
            if(!_activeList.ContainsKey(objToDespawn))//如果将要回收的对象没有在显示列表中,代表出错啦
            {
                Debug.LogError("This Object is not managed by this object pool!");
                return false;
            }
    
            PoolablePrefabData1 data = _activeList[objToDespawn];
            for(int i = 0;i <data.poolableComponents.Length;i++)//所有的实现了IPoolableComponent接口的都需要调用DesSpawned
            {
                data.poolableComponents[i].Despawned();
            }
    
            data.go.SetActive(false);
            _activeList.Remove(objToDespawn); //移除
            _inactiveList.Enqueue(data);//添加
            return true;
        }
    }
    
    
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PrefabPoolingTestInput : MonoBehaviour {
        [SerializeField] GameObject _orcPrefab;
        [SerializeField] GameObject _trollPrefab;
        [SerializeField] GameObject _ogrePrefab;
        [SerializeField] GameObject _dragonPrefab;
    
        List<GameObject> _orcs = new List<GameObject>();
        List<GameObject> _trolls = new List<GameObject>();
        List<GameObject> _ogres = new List<GameObject>();
        List<GameObject> _dragons = new List<GameObject>();
    
        void Start() {
            PrefabPoolingSystem1.Prespawn(_orcPrefab, 11);
            PrefabPoolingSystem1.Prespawn(_trollPrefab, 8);
            PrefabPoolingSystem1.Prespawn(_ogrePrefab, 5);
            PrefabPoolingSystem1.Prespawn(_dragonPrefab, 1);
        }
    
        void Update() {
            if (Input.GetKeyDown(KeyCode.Alpha1)) { SpawnObject(_orcPrefab, _orcs); }
            if (Input.GetKeyDown(KeyCode.Alpha2)) { SpawnObject(_trollPrefab, _trolls); }
            if (Input.GetKeyDown(KeyCode.Alpha3)) { SpawnObject(_ogrePrefab, _ogres); }
            if (Input.GetKeyDown(KeyCode.Alpha4)) { SpawnObject(_dragonPrefab, _dragons); }
            if (Input.GetKeyDown(KeyCode.Q)) { DespawnRandomObject(_orcs); }
            if (Input.GetKeyDown(KeyCode.W)) { DespawnRandomObject(_trolls); }
            if (Input.GetKeyDown(KeyCode.E)) { DespawnRandomObject(_ogres); }
            if (Input.GetKeyDown(KeyCode.R)) { DespawnRandomObject(_dragons); }
        }
    
        void SpawnObject(GameObject prefab, List<GameObject> list) {
            GameObject obj = PrefabPoolingSystem1.Spawn(prefab,5.0f * Random.insideUnitSphere,Quaternion.identity);
            list.Add(obj);
        }
    
        void DespawnRandomObject(List<GameObject> list) {
            if (list.Count == 0) {
                // Nothing to despawn
                return;
            }
    
            int i = Random.Range(0, list.Count);
            PrefabPoolingSystem1.Despawn(list[i]);
            list.RemoveAt(i);
        }
    }
    
    public interface IPoolableComponent {
        void Spawned();
        void Despawned();
    }
    

    预制池系统总结:
    这个池系统为GameObject和预制的运行时内存分配问题提供了一个很好的解决方案,但是需要注意以下事项:

    1. 需要小心再重新生成的物体中正确的重置重要数据(刚体速度等)
    2. 必须确保不会预先生成太少或者太多的实例
    3. 应该小心IPoolableComponent中的Spawned和Despawned方法的执行顺序,不能假定他们以特定的顺序执行
    4. 在加载新场景的时候,必须要调用PrefabPoolingSystem1的Reset,以清除可能不再存爱的空引用对象

    如果希望扩展这个系统,可以从下面的几个方面来做:

    1. 在GameObject初始化之后添加到GameObject上的IPoolableComponent不会触发他们的Spawned()或Despawned方法,因为值在GameObject首次初始化收集该列表,为了修复此问题,可以PrefabPool,在每次调用Spawned和Despawned时,获取IPoolableComponent引用,但是代价时生成和回收期间有额外的开销;
    2. 添加到预制根下的子节点的任何IPoolableComponent不会被同继,为了修复此问题,可以修改prefabPool来使用GetComponentsInchildren,代价是有额外的开销;
    3. 可以实现一种方法,让IPoolableComponent在获取期间设置优先级,并直接控制他们的Spawn和Despawned的方法的执行顺序;
    4. 这个系统不会将自己设置为DontDestroyOnLoad的预制实例友好的交互,明智的做法可能时在每个Spawn调用中添加一个布尔值,以判断加载新场景的时候,是否应该持久化;
    展开全文
  • Android性能优化之内存优化浅析

    千次阅读 2022-02-03 15:28:04
    Android由于是以Java语言为主要开发语言,所以它的内存管理并不像C语言那样由开发者去管理内存的分配以及回收等,而是交由JVM虚拟机的内存回收机制去处理。这就导致我们在开发过程中难免会遇到内存相关的各种问题...

    一、背景

            Android由于是以Java语言为主要开发语言,所以它的内存管理并不像C语言那样由开发者去管理内存的分配以及回收等,而是交由JVM虚拟机的内存回收机制去处理。这就导致我们在开发过程中难免会遇到内存相关的各种问题带来的困扰。而这些问题的根源就在于我们对Android的内存管理机制的不够了解,以及开发过程中缺少对内存的关注导致。所以这里借这篇文章我们再回顾一下Android的内存管理机制,以及如何在开发过程中去尽量避免这些内存问题。

    二、内存管理—内存划分

    Android中的内存是由谁来管理?答案是JVM虚拟机——内存大管家。

    通常我们Android都得基于Java语言做的开发。(这里要排除新的Flutter开发,它是基于Dart语言开发。)而Java语言从API的角度来讲,就是由我们的JDK组成的。同时JRE又是JDK的一部分,而我们这里要说的JVM呢又是JRE的组成部分,简称Java虚拟机。它的主要作用就是实现Java语言的跨平台。它能够将我们的Java语言编译成二进制机器码,然后运行在各平台上。除了帮助跨平台之外,它还承担着内存管理的任务。Android开发中常说的内存基本都是指运行时的内存。

    从线程的角度来看,JVM在执行Java程序的过程中会将所管理的内存划分为线程私有和线程共有两大类。

    2.1、线程私有

    (1)程序计数器:Java是多线程的,既然是多线程就需要线程间切换、通信等操作。如何保证线程间操作时每个线程的执行顺序能按代码的步骤正常执行呢?这时候程序计数器就是关键了,它会帮我们记录当前线程执行到的位置(在字节码中记录的这些位置统称为指令地址)。这个内存区不会出现OOM

    2)虚拟机栈:我们常提在嘴上的堆和栈,其中栈指的就是Java虚拟机栈。每个线程在创建时都是创建一个对应的虚拟机栈,而虚拟机栈又有一个个的栈帧组成。每一个栈帧对应着一次方法调用。当前正在执行的方法对应的是当前栈帧。栈只会执行两种操作:压栈和出栈。栈帧中存储着对应方法的局部变量表,操作数栈,动态链接和方法的返回地址。即虚拟机栈中存储着栈帧,而每个栈帧里面存储着当前方法所需的数据、指令和返回等信息。这个内存区会出现栈溢出、OOM异常

    (3)本地方法栈:这个和虚拟机栈作用类似,区别就是虚拟机栈为执行的Java服务,本地方法栈为Native服务

    2.2、线程共有

    (1)堆内存:Java虚拟机中最大的一块内存,主要目的就是存放对象实例和数组。也是Java虚拟机进行垃圾回收的主要工作区域。

    (2)方法区:我们都知道Java中每个类都对应一个Class对象保存这个类的信息,方法区就是用来保存这个类信息的区域。同时它还会保存常量(包括运行时常量池)、静态变量以及编译器编译后的代码等。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来

    三、内存管理—内存分配

    Java中的内存时如何分配的?

    3.1、堆内存区划分

    我们这里所说的内存分配主要说的是堆内存块的分配。而要了解堆内存块的分配规则,我们要先了解JVM对堆内存块的区域划分。

    JVM将堆内存进行了进一步的划分,如下:

    新生代(PSYoungGen):MinorGC。

    新生代又细分成如下三个空间

      1)、Eden空间  

      2)、FromSurvivor空间

      3)、ToSurvivor空间

    Eden空间用来优先分配对象、数组。From和To空间是个交换区。默认情况下这三个空间的内存占比是8:1:1。当然这个比例也是可以通过虚拟机调的。

    老年代(ParOldGen):Full GC

    3.2、堆内存区的分配方式

    我们创建一个对象时,JVM会去判断当前堆内存是否是规整状态。通过当前堆内存的规整状态来选择指针碰撞还是空闲列表方式进行分配。

    指针碰撞:如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”

    空闲列表:如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

    四、内存分配—内存回收

    前面我们也讲了,Java中的内存回收不由开发者来控制,而是由JVM虚拟机的垃圾回收器(GC)来进行回收管理。Java中GC是如何回收一个对象的?

    4.1、GC如何判断一个对象可回收

    1、引用计数法

      顾名思义当一个对象被另一个对象引用时,将该对象的引用计数+1。相反当该对象被引用对象释放时则-1。当这个对象的引用计数为0时,则认为这个对象是垃圾对象可回收。但是这种方法是不靠谱的,因为这种方法是无法判断一个对象是否真实无用可回收的。比如对象A引用了对象B,同时对象B也引用了A。虽然这两个对象在各自的引用者中都没有被用到,但是这个时候引用计数法就无法判断这个对象是否是可回收对象了。

    2、可达性分析算法

      Java中可达性分析首先要确定Gc Roots对象。然后将所有与这个GCRoot对象有直接或间接引用立链的对象都统计为不可回收对象。

    这个Gc Roots对象是个啥对象?所谓Gc Roots对象就是如下这些对象实列:

    方法区中类静态属性引用的对象,以及方法区中常量引用的对象

    虚拟机栈(本地变量表)中引用的对象

    本地方法栈(JNI开发Native方法)中引用的对象

     

    4.2、GC如何回收内存

    在GC经过上述算法确定完那些对象可回收之后就是进行对象的回收操作了。但是回收操作是要根据不同的GC回收器的能力来完成的。而这个能力就是回收器的回收算法能力。

    复制回收算法:它的主要工作区域是年轻代

      它会将年代年轻的内存分出两块同样大小的区域(其实就是前面说的From、To两块内存区)。一块用来正常使用,一块空闲备用等待复制。判断完对象存活状态后,进行回收时,将存活对象复制到备用空间,然后清空之前的那块内存空间等待备用。复制回收算法的缺点就是内存只有50%利用率。这里就可以解释一下为什么前面的年轻代内存区占比是8:1:1了,在Java中我们通常认为有90%的对象是不需要回收的,只有10%的对象需要被回收,而复制回收算法需要预留一份与这个10%一样大小的内存区来备用。所以就得出了8:1:1的空间占比。复制回收算法就是在From和To之间进行内存Copy

     

    标记-清除算法:主要作用域是老年代

      判断完内存区对象的存活状态后,会对垃圾对象做一个可回收的标记。等垃圾回收器扫描完所有内存后,一次性清除被标记为可回收的对象。标记算法的缺点是垃圾回收后内存空间是不连续的,存在内存碎片(也就是前面提到的内存不规整状态)。

    标记-整理算法:主要作用域是老年代

      标记整理算法就是在标记清除算法的基础上解决了垃圾回收后内存碎片的问题,即清除垃圾对象后,会对内存区进行整理,使其成为规整状态。可是既然要整理内存区,就必然要进行内存移动,就会降低效率。所以它的效率比标记清除算法要低。

     

    五、内存管理—内存优化

    首先为啥会出现内存优化一说?这是因为我们在开发中经常忽视内存相关的问题,导致功能开发完成后经过测试或者上线后发现经常由于内存的问题带来一连串的恶性循环的后果。比如:内存抖动、内存泄漏等等容易在开发中被忽视的问题最终都有可能引发严重的线上问题。

    5.1、内存抖动优化

    首先要知道什么是内存抖动:短时间内有大量对象创建销毁,它伴随着频繁的GC。比较直观的现象就是如下图:

     

    内存抖动的危害

    (1)频繁的GC会导致程序出现卡顿的现象,简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,挂起(stop the word),等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿

    (2)频繁GC会导致我们的内存不连续(内存碎片),从而导致OOM。

    内存抖动的预防

    (1)避免在循环中创建对象

    (2)避免在频繁调用的方法中创建对象,如View的onDraw方法

    (3)允许复用的情况下,使用对象池进行缓存。

    5.2、内存泄漏优化

    为什么会出现内存泄漏?该释放的资源未能够及时释放,导致系统内存的浪费,可用内存逐渐减少。长生命周期对象持有短生命周期对象的强引用,从而导致短生命周期的对象无法被回收。这就是内存泄漏的主要原因。

    内存泄露的危害

    (1)频繁的GC会导致程序出现卡顿的现象,简而言之, 就是执行GC操作的时候,任何线程的任何操作都会需要暂停,挂起(stop the word),等待GC操作完成之后,其他操作才能够继续运行, 故而如果程序频繁GC, 自然会导致界面卡顿

    (2)频繁GC会导致我们的内存不连续(内存碎片),从而导致OOM。

    内存泄漏优化

    (1)合理使用如下四种引用关系

    强引用:new出来的对象都是强引用。被强引用的对象无法被垃圾回收。即使内存不足时也无法回收,就有可能造成OOM

    软引用SoftReference:被软引用的对象,当内存不足时(即将OOM时),则被回收。

    弱引用WeakReference:垃圾回收线程扫描内存区时,发现被弱引用的对象立即回收。

    虚引用PhantomReference:任何时候都有可能被回收。

    (2)合理使用内存泄漏检测工具

    (1)LeakCanary是一个Android和Java的内存泄漏检测库,可以大幅可以大幅度减少了开发中遇到的OOM问题。

    (2)Profile + MAT

    Android Studio自带的Profile工具大部分时候都能够帮助我们定位内存的使用情况,但也会有出现泄漏时无法通过Profile工具来直观定位到泄漏点的情况,这时候就需要Profile工具配合MAT工具一起来排查泄漏点了。

    这里简单列一下两个工具配合使用的步骤:

    导出profiler 的Heap Dump 文件

    找到Android SDK中platfrom-tools目录下的hprof-conv.exe工具(可以直接配置到环境变量里面)

    转换导出的Heap Dump文件: hprof-conv.exe -z 导出的HeapDump文件.hprof  转换后的目标文件.hprof

      如果不转换,格式不兼容,MAT无法查看。

    使用MAT工具打开转换后的hprof文件。

     

    六、总结

    Android开发过程中内存一直是个容易引发各类问题的根源,而这个问题根源又常因为开发过程中的各类因素没有被及时的发现,所以在开发过程中重视内存的检测和优化是我们要时刻关注的一个点。

     

    展开全文
  • 要讨论内存优化,首先要知道项目中最消耗内存的是什么? 就像creator工程中占用空间最多的一样,是资源,资源包括纹理,声音,数据等等 这里我们先了解下creator 的资源在内存中的管理方式,之后再介绍其他的...

    更多教程请关注微信公众号:
     

     

    设备对每个程序都有最大的内存分配限制,如果超过了这个阈值,会被系统强制关闭,造成 crash

     

    因此在开发的过程中,我们要在保证程序运行效率的前提下,尽量压缩程序运行时所占用的内存

     

    要讨论内存优化,首先要知道项目中最消耗内存的是什么?

    就像 creator 工程中占用空间最多的一样,是资源,资源包括纹理,声音,数据等等

     

    这里我们先了解下 creator 的资源在内存中的管理方式,之后再介绍其他的优化内容

     

    01

    存储形式

     

    资源在加载完成后,会以 { uuid : cc.Asset } 的形式被缓存到 cc.assetManager.assets 中,以避免重复加载

     

    但是这也会造成内存和显存的持续增长,所以有些资源如果不再需要用到,可以通过 自动释放 或者 手动释放 的方式进行释放

    释放资源将会销毁资源的所有内部属性,比如渲染层的相关数据,并移出缓存,从而释放内存和显存(对纹理而言)

     

    cc.assetManager:管理资源的行为和信息,包括加载,释放等

    cc.assetManager.assets :已加载资源的集合

     

    02

    引用计数

     

    引用计数 是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程

     

    资源在加载完成后,会返回 cc.Asset 实例, 所有 cc.Asset 实例都拥有成员函数 addRef 和 decRef,分别用于增加和减少引用计数

     

    初始化引用计数

    •  
    this._ref = 0;

     

    资源的引用计数 +1

    •  
    •  
    •  
    •  
    addRef () {    this._ref++;    return this;}

     

    资源的引用计数 -1,并尝试进行自动释放

    •  
    •  
    •  
    •  
    •  
    •  
    decRef (autoRelease) {    this._ref--;    //接下来会对代码进行详细的解读    autoRelease !== false && cc.assetManager._releaseManager.tryRelease(this);    return this;}

     

    Asset Manager 只会 自动统计 资源之间的 静态引用,并不能真实地反应资源在游戏中被动态引用的情况,动态引用 还需要 开发者进行控制 以保证资源能够被正确释放

     

    1静态引用

    当开发者在编辑器中编辑资源时(例如场景、预制体、材质等),需要在这些资源的属性中配置一些其他的资源,例如在材质中设置贴图,在场景的 Sprite 组件上设置 SpriteFrame。那么这些引用关系会被记录在资源的序列化数据中,引擎可以通过这些数据分析出依赖资源列表,像这样的引用关系就是 静态引用

    引擎对资源的 静态引用 的统计方式为:

    1. 在 动态加载 某个资源时,引擎会在底层加载管线中记录该资源所有 直接依赖资源 的信息,并将所有 直接依赖资源 的引用计数加 1,然后将该资源的引用计数初始化为 0

    2. 在释放资源时,取得该资源之前记录的所有 直接依赖资源 信息,并将所有依赖资源的引用计数减 1

    因为在释放检查时,如果资源的引用计数为 0,才可以被自动释放。所以上述步骤可以保证资源的依赖资源无法先于资源本身被释放,因为依赖资源的引用计数肯定不为 0。也就是说,只要一个资源本身不被释放,其依赖资源就不会被释放,从而保证在复用资源时不会错误地进行释放

    下面我们来看一个例子:

    1. 假设现在有一个 A 预制体,其依赖的资源包括 a 材质和 b 材质。a 材质引用了 α 贴图,b 材质引用了 β 贴图。那么在加载 A 预制体之后,a、b 材质的引用计数都为 1,α、β 贴图的引用计数也都为 1

    2. 假设现在又有一个 B 预制体,其依赖的资源包括 b 材质和 c 材质。则在加载 B 预制体之后,b 材质的引用计数为 2,因为它同时被 A 和 B 预制体所引用。而 c 材质的引用计数为 1,α、β 贴图的引用计数也仍为 1

    3. 此时释放 A 预制体,则 a,b 材质的引用计数会各减 1

      • a 材质的引用计数变为 0,被释放,所以贴图 α 的引用计数减 1 变为了 0,也被释放

      • b 材质的引用计数变为 1,被保留,所以贴图 β 的引用计数仍为 1,也被保留

      • 因为 B 预制体没有被释放,所以 c 材质的引用计数仍为 1,被保留

         

     

    我们通过 creator 来了解下 assets

     

    新建一个场景,不放入任何资源

     

    打印 assets

    •  
    console.log(cc.assetManager.assets)

     

    可以看到内存中的资源均为 cocos 的内置资源

     

    在场景中放入 HelloWorld

     

    启动游戏后,引擎在底层加载管线中调用 assets 的成员方法 addRef

     

    再次打印 assets 及资源的引用计数

    •  
    •  
    console.log(cc.assetManager.assets);console.log("spriteFrame.refCount : " + this.sprite.spriteFrame.refCount);

     

     

    会发现 assets 多了两项,uuid 分别是

    6aa0aa6a-ebee-4155-a088-a687a6aadec4 

    31bc895a-c003-4566-a9f3-2e54ae1c17dc

     

    在编辑器中显示 HelloWorld 的 Texture2D 和 SpriteFrame 的 uuid,和上述的两个 uuid 完全匹配

     

    图片的引用计数也增加为 1

     

    如果存在两份 HelloWorld,但他们的 spriteFrame 是同一份

     

    那么 cc.assetManager.assets 依然保持原样,但 spriteFrame 的 refCount 会变成 2

     

    对于更复杂的资源引用情况,可以自己测试下 assets 及引用计数

     

     

    补充知识点:Texture 和 SpriteFrame 资源类型

     

    在 资源管理器 中,图像资源的左边会显示一个和文件夹类似的三角图标,点击就可以展开看到它的子资源(sub asset),每个图像资源导入后编辑器会自动在它下面创建同名的 SpriteFrame 资源。

     

    SpriteFrame 是核心渲染组件 Sprite 所使用的资源,设置或替换 Sprite 组件中的 spriteFrame 属性,就可以切换显示的图像

     

    为什么会有 SpriteFrame 这种资源?Texture 是保存在 GPU 缓冲中的一张纹理,是原始的图像资源。而 SpriteFrame 包含两部分内容:记录了 Texture 及其相关属性的 Texture2D 对象和纹理的矩形区域,对于相同的 Texture 可以进行不同的纹理矩形区域设置,然后根据 Sprite 的填充类型,如 SIMPLE、SLICED、TILED 等进行不同的顶点数据填充,从而满足 Texture 填充图像精灵的多样化需求。而 SpriteFrame 记录的纹理矩形区域数据又可以在资源的属性检查器中根据需求自由定义,这样的设置让资源的开发更为高效和便利。除了每个文件会产生一个 SpriteFrame 的图像资源(Texture)之外,我们还有包含多个 SpriteFrame 的图集资源(Atlas)类型

     

    2动态引用

    当开发者在编辑器中没有对资源做任何设置,而是通过代码动态加载资源并设置到场景的组件上,则资源的引用关系不会记录在序列化数据中,引擎无法统计到这部分的引用关系,这些引用关系就是 动态引用

     

    使用 动态加载 资源来进行动态引用

     

    • 动态加载 resources 目录中的资源

    •  
    •  
    •  
    •  
    •  
    cc.resources.load("HelloWorld", cc.SpriteFrame, (err, assets: cc.SpriteFrame) => {    this.sprite.spriteFrame = assets;    console.log(cc.assetManager.assets);    console.log("spriteFrame.refCount : " + this.sprite.spriteFrame.refCount);});

     

    • 动态加载 bundle 目录中的资源

    •  
    •  
    •  
    •  
    •  
    •  
    •  
    cc.assetManager.loadBundle("bundle", (err: Error, bundle: cc.AssetManager.Bundle) => {    bundle.load("HelloWorld", cc.SpriteFrame, (err, assets: cc.SpriteFrame) => {        this.sprite.spriteFrame = assets;        console.log(cc.assetManager.assets);        console.log("spriteFrame.refCount : " + this.sprite.spriteFrame.refCount);    });});

     

    在资源加载完成后打印下 assets 及资源的引用计数

     

    可以看到,资源加载完成后会将 SpriteFrame 资源设置到 Sprite 组件上,但引擎不会做特殊处理,SpriteFrame 的引用计数仍保持 0,此时需要我们手动来管理引用计数

     

    增加引用计数

    •  
    •  
    •  
    •  
    •  
    •  
    cc.resources.load("HelloWorld", cc.SpriteFrame, (err, assets: cc.SpriteFrame) => {    this.sprite.spriteFrame = assets;    this.sprite.spriteFrame.addRef();    console.log(cc.assetManager.assets);    console.log("spriteFrame.refCount : " + this.sprite.spriteFrame.refCount);});

     

    减少引用计数(为了避免过多的资源干扰视线,我们在触摸结束时减少引用计数)

    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    •  
    onTouchEnd(event: cc.Event.EventTouch) {    console.log("###");
        this.sprite.node.destroy();
        this.sprite.spriteFrame.decRef();    console.log("spriteFrame.refCount : " + this.sprite.spriteFrame.refCount);    this.sprite.spriteFrame = null;    //在下一帧打印 assets    this.scheduleOnce(()=>{        console.log(cc.assetManager.assets);    });}

     

    运行后的 log

     

    从 log 中可以看到,addRef 后,资源的引用计数变为 1,decRef 之后资源的引用计数在当前帧为 0,在下一帧,资源也从 assets 中被清除了

     

    注意:

    动态加载 的资源必须手动卸载,卸载方式

    ① 通过引用计数:addRef  和 decRef

    ② 直接释放:releaseAsset

    展开全文
  • 提升LabVIEW程序性能优化内存管理;内容提要;为什么要优化LabVIEW程序内存管理;监测VI内存占用和运行时间的工具;显示VI内存分配情况的工具;优化 LabVIEW程序内存管理基础篇;1 数据类型转换;推荐的类型转换方式;2 防止...
  • App性能优化:内存优化

    千次阅读 2020-01-03 14:22:41
    App性能优化:内存优化 本篇主要探讨app性能优化相关的知识点,预计20年2月底前完成 内存优化工具 内存管理机制 内存都懂解决 内存泄漏解决 MAT详解 小结 ...
  • Cocos Creator性能优化-4-内存优化

    千次阅读 2022-01-15 19:33:25
    1.什么是内存优化
  • 庄延军《iOS内存管理优化》,一本不错的关于ios内存管理的书。
  • lua内存管理优化方式

    千次阅读 2020-03-04 18:57:06
    lua内存管理:由于lua中有内存回收机制,所以本质上说lua与C/C++中的内存泄漏是不一样的,lua中有GC,理论上说是不会有内存泄漏的。当它进行GC的时候,会从根部开始扫描所有的对象,如果某个地方对这个对象还有引用...
  • Android内存优化之图片优化

    万次阅读 多人点赞 2019-04-08 11:52:23
    关于图片优化,大概如下 ...:Android系统的进程(APP级别)有最大的内存限制,超过这个限制系统就会抛出)OOM错误 图片OOM问题产生的几种情况 1.一个页面一次加载过多的图片 2.加载大图片没有进行压缩 3....
  • MySQL优化系列14-优化MySQL内存

    千次阅读 2021-07-15 14:09:35
    MySQL如何使用内存二.监控MySQL内存使用三.开始large page支持参考: 一. MySQL如何使用内存 MySQL分配缓冲区和缓存来提高数据库操作的性能。默认配置被设计为允许MySQL服务器在拥有大约512MB内存的虚拟机上启动。您...
  • 首先是简要的讲一下为什么要优化LabVIEW程序的内存管理。LabVIEW是一个自动管理内存的开发语言。既然已经是自动管理内存了,那我们还能做什么提高和优化的呢? 第二部分,介绍一些在LabVIEW中非常有用的工具,可以...
  • LabVIEW编程-内存优化

    2013-10-08 21:00:07
    LabVIEW编程-内存优化,LabVIEW程序内存分配机制 LabVIEW内存管理工具 LabVIEW内存优化方法案例分析
  • Redis内存管理优化

    千人学习 2016-11-21 14:05:28
    Redis是一个高性能的开源NOSQL内存数据库。本次分享主要介绍Redis内存管理机制,Redis内存使用情况分析定位,阿里云Redis相关优化,以及相关的佳实践。
  • 在IOS程序中,内存通常被分成如下5个区域 栈区:存储局部变量,在作用域结束后内存会被回收 ...在上面的五个内存区域中,除了堆区需要开发者手动进行内存管理外,其它区都由系统自动进行回收。 一 MRC下的内存...
  • Android手机内存管理与性能优化和JNI、NDK高级编程(JNI、Dalvik、内存监测)
  • C内存管理优化高级培训讲义 深入解析内存从new,malloc到操作系统内部,一步步讲内存管理 2009年11月新出~
  • Android-APP内存优化

    千次阅读 2018-04-09 14:27:10
    为什么要进行内存优化 APP运行内存限制,OOM导致APP崩溃 APP性能:流畅性、响应速度和用户体验
  • 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录 1. 定义 优化处理 应用程序的内存使用、空间占用 2. 作用 避免因不正确使用内存 &amp; 缺乏管理,从而出现 内存泄露(ML)、内存...
  • JavaScript性能优化-内存管理(10)

    千次阅读 2020-11-14 10:57:39
    主要内容为JavaScript性能优化内存管理、GC算法以及V8引擎垃圾回收策略
  • 今天来聊一聊Android中内存优化的一些手段。 首先问问自己为什么要内存优化呢? (1):App消耗内存过大,导致手机内存低于内存警戒线的时候,Low Memory Killer机制就会触发,App占用内存越多,被处理掉的机会就越...
  • Android内存优化

    千次阅读 2018-05-23 14:42:38
    参考Android性能优化(四)之内存优化实战 内存优化的套路: 解决所有的内存泄漏 集成LeakCanary,可以方便的定位出90%的内存泄漏问题; 通过反复进出可疑界面,观察内存增减的情况,Dump Java Heap获取当前堆栈...
  • c++的内存优化

    千次阅读 2019-01-18 17:43:33
    2.内存优化有哪些方式 3.怎样做内存优化 概述: 我们常常在开发场景下提及的内存是指程序内存. 程序内存可以分为以下五种: 1、 栈区(stack):栈的空间是连续的, 先进后出能保证不会产生内存碎片, 由高地址向低...
  • Spark优化篇:动态内存管理

    千次阅读 2022-04-15 11:01:07
    Spark内存管理分为静态内存管理和统一内存管理,Spark1.6之前使用的是静态内存管理,Spark1.6之后的版本默认使用的是统一内存管理。 动态内存机制图: 内存估算: Other Memory = 自定义数据结构*每个 ...
  • Redis的内存管理优化

    千次阅读 2019-09-05 20:38:14
    Redis内存管理1.查询redis内存相关的信息2.redis内存是如何消耗的3.如何优化redis的内存 1.查询redis内存相关的信息 登录redis的客户端,查看当前redis服务器的内存使用情况: 使用info memory 命令: 关键词解释 ...
  • Windows 操作系统在没有用户干预的情况下可以相当有效地管理计算机内存的使用情况,但不是说就没有可以优化和改进的空间了,这也就是为什么有很多「系统调优实用程序」提供类似「内存清理」选项的原因。Memory ...
  • Android内存管理优化建议

    千次阅读 2016-10-17 12:42:35
    前面我们提到过使用getMemoryClass()的方法可以...简要的获取某个应用的内存占用情况可以参考下面的示例( 关于更多内存查看的知识,可以参考这篇官方教程:Investigating Your RAM Usage )1)查看内存使用情况 通过
  • 【redis】redis内存管理、淘汰机制、内存优化

    千次阅读 多人点赞 2020-08-25 12:44:21
    redis内存管理、淘汰机制、内存优化
  • Lua性能优化—Lua内存优化

    万次阅读 2017-06-09 14:15:46
    笔者在这里和大家分享了一些在Lua性能优化方面的经验。比如说拿到原始数据后,如何处理过滤数据、信息的经验,从而更快更准确地定位问题。如果大家有更好更精准的处理数据、过滤信息的方法请不吝赐教。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 484,398
精华内容 193,759
关键字:

内存优化管理

友情链接: spectrumplotsimx.zip