这一节叫异常处理详解,终归是围绕异常处理来讲述知识点, defer 延迟调用语句的用处是在程序执行结束,甚至是崩溃后,仍然会被调用的语句,通常会用来执行一些告别操作,比如关闭连接,释放资源(类似于 c+ 涉及到 defer 的操作 并发时释放共享资源锁 延迟释放文件句柄 延迟关闭 tcp 连接 延迟关闭数据库连接 这些操作也是非常容易被人忘记的操作,为了保证不会忘记,建议在函数的一开始就放置 defer panic 刚刚有说到 defer 是崩溃后,仍然会被调用的语句,那程序在什么情况下会崩溃呢? Go 的类型系统会在编译时捕获很多异常,但有些异常只能在运行时检查,如数组访问越界、空指针引用等。 报错示例 panic recover 出现 panic 以后程序会终止运行,所以我们应该在测试阶段发现这些问题,然后进行规避,但是如果在程序中产生不可预料的异常(比如在线的web或者rpc服务一般框架层 ),即使出现问题(一般是遇到不可预料的异常数据)也不应该直接崩溃,应该打印异常日志,关闭资源,跳过异常数据部分,然后继续运行下去,不然线上容易出现大面积血崩。
objc_msgSend函数是runtime方法执行的核心引擎而且调用如此的频繁,函数内部是不可能有BUG的。 那么为什么会崩溃在这呢? 但是对象的Class对象这部分定义数据是存储在进程内存的数据区段中,并且伴随着整个应用的生命周期而存在,是不可能被释放和销毁的,因此正常情况下是不可能存在非法内存地址访问异常的。 也许你会好奇既然obj对象已经被释放了,为什么崩溃会出现在objc_msgSend函数的第5条指令,其中的第3条指令是访问对象的isa数据的,为什么不崩溃在这呢? 其实答案很简单,因为几乎所有的OC对象都是从堆内存区域中分配内存的,所以当某个OC对象被销毁后,其所占用的内存仍然会放回堆内存区域中进行管理,而堆内存区域的地址是可以进行任意的读写访问的,所以即使对象被销毁释放 而视图的操作基本都应该放在主线程进行,因此当主线程的某些子视图数组对象被释放后,这里又在辅助线程中进行读取访问,就出现了上面的异常崩溃问题了。
Vite学习指南,基于腾讯云Webify部署项目。
内存泄漏检测 主要看definitely lost:这里如果是0,说明没有会导致程序崩溃读的内存泄漏问题。 “definitely lost”:确认丢失。程序中存在内存泄露,应尽快修复。 当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存则会报这个错误。 “indirectly lost”:间接丢失。当使用了含有指针成员的类或结构时可能会报这个错误。 例子可参考我的例程。当程序结束时如果一块动态分配的内存没有被释放且通过程序内的指针变量均无法访问这块内存的起始地址,但可以访问其中的某一部分数据,则会报这个错误。 “still reachable”:可以访问,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃,但长时间运行有可能耗尽系统资源,因此笔者建议修复它。 如果程序是崩溃(如访问非法的地址而崩溃)而非正常结束的,则应当暂时忽略它,先修复导致程序崩溃的错误,然后重新检测。 “suppressed”:已被解决。出现了内存泄露但系统自动处理了。
而程序卡死一般来源于代码逻辑的缺陷,导致了死循环、死锁等问题。总的来看,常见的程序异常问题一般可以分为非法内存访问和资源访问冲突两大类。 ? 多线程共享数据访问冲突 在多线程程序中,非法指针的产生可能就没那么容易发现了。 core dumped文件放在程序执行目录下,并以core作为文件名前缀。 总结 本文从Linux上C语言编程中遇到的异常开始讨论,将异常大致分为非法内存访问和资源访问冲突两大类,并对每类典型的案例做了解释和说明,最后通过core dumped文件分析和Valgrind工具的测试 希望看到此文的读者,在以后遇到程序异常时都能泰然自若,冷静分析,顺利地找到问题的根源,便不枉费笔者撰写此文之心血。
另外因为在闪屏页中仍然有剩余展示时间,所以在这个时间段里如果用户已经下载好了图片并且图片完整,就可以显示广告页。 那 Native 崩溃一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动 Abort,这些都会产生相应的 Signal 信号,导致程序异常退出。 对不同类型的 “案件” 分别应该使用什么样的调查方式? 要相信 “真相永远只有一个”,崩溃也并不可怕。 崩溃现场 崩溃现场是我们的“第一案发现场”,它保留着很多有价值的线索。 第二步:查找共性 如果使用了上面的方法还是不能有效定位问题,我们可以尝试查找这类崩溃有没有什么共性。找到了共性,也就可以进一步找到差异,离解决问题也就更进一步。 并且Android系统在内存管理上有一个Generational Heap Memory模型,当内存达到某一个阈值时,系统会根据不同的规则自动释放可以释放的内存。
然后每次分配时,再将里面的一小段标记为已分配,释放的时候再标记成未分配。 频繁地创建销毁对象将会占用更多cpu资源,高并发时容易导致cpu长期处于高负载运行状态。 什么是对象池 对象池就是一个在程序启动的时候先创建好若干个可以重复使用的对象。 在这个期间将会继续占用连接资源,而连接资源的数量又是有限制的,所以会更快出现连接不够用的情况。 处理会影响程序的运行,同时还将可能导致全站崩溃。 mysql是一个连接创建一个线程处理。 连接池 保护mysql不崩溃 连接池是将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问。 <? 假设本来我们的服务器配置是可以保证1000个连接同时稳定运行,突然某一时刻有3000个人并发,导致连接不够用,那么是保证原有1000人都正常运行好,还是让这3000人争抢资源最终导致机器响应不了全站崩溃好呢
这样的终止或“崩溃”对程序具有很高的破坏性:当Dropbox程序终止时,程序就无法同步了。为了确保我们的用户可以不间断的同步,我们会自动检测并报告所有崩溃,同时采取措施重新启动程序。 这些“原始”的崩溃并不是什么新鲜事:例如,几十年来错误的内存操作一直困扰着开发者们。 随着我们的应用程序变得越来越复杂,我们开始使用其他编程语言来构建我们的一些功能。 当应用的崩溃报告中含有minidump(小存储器转储文件:可帮助确定计算机为什么意外停止的最小的有用信息集)时, 我们使用之前生成的符号来跟踪应用里每个堆栈内容并将其链接到源代码中。 同样需要注意的是,并非所有终止都是应用崩溃(例如用户关闭应用程序或应用自动更新就不属于应用崩溃)。尽管如此,有一些终止情况仍然表明应用可能存在问题。 由于Crashpad可以访问受监视进程的内存,因此它可以读取这个状态并将其作为报告的一部分。 由于 Dropbox提供了CPython的自定义分支,因此我们可以有效地控制它的行为。
当Xcode构建一个iOS项目时,它会将编译的程序、Info.plist文件、资源目录和任何其他资源放在一个名为bundle的目录中,然后将该bundle命名为APP名字.app。 这四个任务中的每一个都对应一行代码,但有一个问题:如果我们在应用程序包中找不到start.txt,或者如果我们可以找到但无法加载它,该怎么办? 当我们调用fatalError()时,它将无条件且始终导致我们的应用程序崩溃。它会死崩溃的。不是“可能会崩溃”或“也许会崩溃”:它总是直接终止。 最好是立即终止,并对出现的问题给出明确的解释,这样我们就可以纠正问题,而fatalError()正是这样做的。 它还没有真正的意义,因为玩家仍然可以输入他们想要的任何单词。让我们下一步解决这个问题… 译自 Running code when our app launches 赏我一个赞吧~~~
(C++ vtable pointer),这导致程序尝试执行没有执行权限的内存中的指令;◈ 其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT 译注:在要求自然边界对齐的体系结构 这个“C++ 虚表指针”是我的程序发生段错误的情况。我可能会在未来的博客中解释这个,因为我最初并不知道任何关于 C++ 的知识,并且这种虚表查找导致程序段错误的情况也是我所不了解的。 我们仍然不知道该程序为什么会出现段错误! 下一步将使用 gdb 打开核心转储文件并获取堆栈调用序列。 从 gdb 中得到堆栈调用序列 你可以像这样用 gdb 打开一个核心转储文件: 1. $ gdb -c my_core_file 接下来,我们想知道程序崩溃时的堆栈是什么样的。 在试图找出程序崩溃的原因时,堆栈跟踪中的行号非常有帮助。:) 查看每个线程的堆栈 通过以下方式在 gdb 中获取每个线程的调用栈!
资源泄漏篇 试想,如果申请的资源未进行释放,那势必会资源泄漏,尤其是对于长时间运行的程序来说,会导致系统中可用的资源越来越少,严重的,系统会因为资源耗尽而崩溃。 对于这类问题,笔者总结了如下需要注意的地方: 慧眼识珠:资源获取和资源释放函数需要成对使用 成对使用的资源获取和释放函数太多,这里就不一一列举啦,总之,看到资源获取语句,必查资源释放语句,反之,亦然。 异常处理篇 优雅编程需要在一开始就考虑异常事件的处理,不仅需要保证在正常情况下程序可以稳定运行,而且在发生错误和出现“意外事件”时仍然能继续可靠运行。因此,需要尽可能多的预见所有这些异常事件。 ,而C++中数组下标越界,编译器是不会检查出这种错误的,但后果可能会比想象中严重,甚至程序崩溃。 这类的用户反馈问题也有很多,首先列举下导致多线程问题的原因: 1) 资源的读写和更新没有加锁(此处经常会有用户反馈) 2) 资源的获取和访问之间有时间间隔 3) 加锁范围太小 4) 使用了线程不安全函数
另一方面,Kubernetes通常可以强制终止您的应用程序,作为系统正常运行的一部分。 在容器出现之前,大多数应用运行在虚拟机或者物理机上。如果应用程序崩溃,启动替换程序需要很长时间。 如果您只有一台或两台机器来运行应用程序,那么这种恢复时间是不可接受的。 相反,在崩溃时使用进程级监控来重新启动应用程序变得很常见。如果应用程序崩溃,监视进程可以捕获退出代码并立即重新启动应用程序。 随着像Kubernetes这样的系统的出现,不再需要进程监控系统,因为Kubernetes可以处理重启崩溃的应用程序。Kubernetes使用事件循环来确保容器和节点等资源是健康的。 如果节点资源不足,Kubernetes将终止pod以释放这些资源 您的应用程序要优雅地处理终止是至关重要的,可以最终用户受到的影响最小,并且恢复时间尽可能快! 因此有可能会导致该Pod仍然列在服务的Endpoints中并仍然接收流量,而它已经收到SIGTERM并且已经停止,因此负载均衡器上可能会有一些Http 504。
程序员充分地考虑了资源的释放,但在这段代码中他却没有对多个资源的释放给予足够的重视,而是以释放单资源的做法去处理多资源。 在finally语句块中,如果释放Statement资源的操作失败了,就可能抛出异常,因为在finally中并没有捕获这种异常,就会导致后面的conn.close()语句没有执行,从而导致Connection 同步方法虽然可以较好地解决并发问题,在一定程度上避免出现资源抢占、竟态条件和死锁的情况。但它的一个副作用同步锁可能导致线程阻塞。这就要求同步方法的执行时间不能太长。 由于此时的create()方法是远程调用,当服务端比较繁忙时,发出的远程调用请求可能会被阻塞。由于get()方法是同步方法,在方法体内,每次只能有一个线程访问它,直到方法执行完毕释放锁。 当然,我们可以认为这种扩展本身是不合理的。但从设计的角度来看,它并没有违背Liskove替换原则。从接口的角度看,它的行为也没有发生任何改变,仅仅是实现发生了变化。
windbg+IDA分析 由于崩溃在一个不可访问的地址,不好确定之前的指令,这时需要用到与栈回溯相关的命令,就是windbg中的k一系列命令: ? 将eax置为了不可访问的地址: ? edi是作为参数传入UpdateThunkEntryPoint的,并且在函数内没有被改变,那么还要往回追踪,经过回溯,从CheckCodeGenDone函数开头开始跟,由于中间还有跳转,所以edi的值还会改变 两次指针并不相同,说明指针被改写了一次,重新运行一样在第五次断点被触发时看一下这里的内存,此时还没有被改写: ? 在这里可以下一个内存写入断点,看看什么时候被改写了: ? 那我们要找到这段内存是如何释放的,还是回到63e06cd7断点处,这次不进入函数,步过后那段内存并没有被释放,为了弄清楚在哪里被释放,给this指针和那段uaf的内存下访问断点: ?
你甚是赞同,但是你心里却又有点怀疑,对方的开发资源是我们的好几倍而且个个都是资深老司机,我们团队里却大多都是应届生小鲜肉,这KPI能完成么? 能够自动在app运行时实时捕获导致app崩溃的破环因子,然后通过特定的技术手段去化解这些破坏因子,使app免于崩溃,照样可以继续正常运行,为app的持续运转保驾护航。 5.如果没有重写拦截调用的方法,程序报错。 3.1.3 拦截调用 在方法调用中说到了,如果没有找到方法就会转向拦截调用。 那么什么是拦截调用呢? 同时做判断是否要加入标记的条件里面,我们加入了黑名单机制,是因为一些特定的类是不适用于添加到zombie机制的,会发生崩溃(例如:NSBundle),而且所以和zombie机制相关的类也不能加入标记,否则会在释放过程中循环引用和调用 3.延时释放实例是根据相关功能代码会聚焦在某一个时间段调用的假设前提下,所以野指针的zombie保护机制只能在其实例对象仍然缓存在zombie的缓存机制时才有效,若在实例真正释放之后,再调用野指针还是会出现
然而内存资源非常宝贵,将全量数据存储在内存中显然是不切合实际的。因此目前采用内存和IO结合的方式,内存只存储热点数据,而IO设备存储全量数据。 发生缓存穿透的原因有很多,一般为如下两种: 恶意攻击,故意营造大量不存在的数据请求我们的服务,由于缓存中并不存在这些数据,因此海量请求均落在数据库中,从而可能会导致数据库崩溃。 代码逻辑错误。 当经过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。如果此时请求失败率符合预设值,则完全打开限流开关;如果请求失败率仍然很高,那么继续拒绝该服务的所有请求。 ,并将数据更新值缓存后,释放锁;此时其他被阻塞的查询请求将可以直接从缓存中查到该数据。 这需要结合实际的业务考虑是否允许这么做。 互斥锁可以避免某一个热点数据失效导致数据库崩溃的问题,而在实际业务中,往往会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?
from=main_page 缓存 为了提升系统性能,将部分数据放入缓存,加速访问,减少数据的压力 什么数据适合写入缓存? 对于一些对即时性和数据一致性要求不高的,访问量大更新频率不高的数据适合写入缓存 流程图 image-20210122174839540 最简单的可以把数据放入一个map(本地缓存),单体应用时没有什么问题 缓存穿透 image-20210122211902414 查询一个一定不存在的数据,由于缓存必定不命中,而去查询数据库,查数据什么也查不到而且我们也没有把这个空结果写入缓存,导致每次差这个数据都会访问一遍数据库 ,使缓存失去效果,容易被人利用导致数据库压力大,最终导致系统崩溃 解决:将空结果也写入缓存 问题:若以后这个数据又存在了怎么办 解决:加上一个短暂的过期时间! ,导致系统崩溃 解决:加锁,当大量请求访问时,只允许一个请求查询数据,等查完后将数据写入缓存并释放锁 加锁解决缓存击穿问题 在单体应用下 使用 synchronized(this){ } 本地锁进行加锁
经过验证发现,果然在此处发生崩溃,并且是百分百复现,调用堆栈基本一致。因此可以说明我们的猜想是正确的。 仔细想想这个问题,有经验的同学可能会感到细思极恐,因为垃圾回收机制并不受我们控制,我们在进行 JSObjectMake 无法保证一定不处于垃圾回收期间,那么理论上来说应该进行发生崩溃才对,为什么这个问题之前一直没有暴露出来呢 图6 JSCore的两种垃圾回收方式 而我们之所以发生崩溃是由于我们在对象在垃圾回收的回调中访问了堆,这个问题的伪代码如下: ? 图7 伪代码 3. 问题的根源在于我们想在 JS 变量释放的时候释放它所间接持有的 OC 对象,如果在垃圾回收期间我们无法进行释放,那么是不是意味着只要我们获取到 JavascriptCore 的垃圾回收开始和结束回调就能避免这个问题了呢 那么还有什么操作是一个延迟释放的操作呢?__autoreleasing 应该是一个比较好的选择。 当对象前被添加 __autoreleasing 修饰时,这个对象会被延迟到自动释放池释放时才被释放。
扫码关注云+社区
领取腾讯云代金券