精华内容
下载资源
问答
  • 那么,移动信息安全问题根源在哪里?其原因可以从以下三个方面进行分析: 一、相关政策、法律法规不健全。由于国内移动互联网法律法规不完善,信息安全防范机制相对落后,对个人或企业没有明确的标准,无法进行...

    万物互联时代已经到来,移动互联网成为了人们日常工作及生活中不可缺少的一部分,移动设备信息泄露、未知攻击等移动信息安全问题也较为普遍。那么,移动信息安全问题的根源在哪里?其原因可以从以下三个方面进行分析:

    移动信息安全问题分析

    一、相关政策、法律法规不健全。由于国内移动互联网法律法规不完善,信息安全防范机制相对落后,对个人或企业没有明确的标准,无法进行有效的协调和管理。这就导致当移动信息安全问题出现时,只能事后处理进行处理,而不是提前有效预防。

    二、用户信息安全知识相对薄弱。即使在互联网高度发达的今天,仍然有大量用户缺乏信息安全知识,如随意登录未知网站、下载应用程序未设置相应权限、使用公共免费网络、点击未知链接、填写个人信息乱用、不明来源二维码扫描、网上分享生活但不注意隐藏个人信息等,导致个人信息泄露等移动信息安全事件频发。

    三、软件授权不合理。现如今市场上的几乎全部应用软件都有不合理的手机授权要求,向用户索要位置信息、麦克风和摄像头、个人信息、通讯录、相册信息等,涉及用户隐私权,存在越界索要权限和过度收集信息现象,一旦用户授权,往往会导致应用软件收集消费记录、出行住宿、行车路线等信息。如果这些数据泄露,后果将不堪设想。

    展开全文
  • 关于Java的六大问题分析引导语:Java 编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言。 。以下是百分网小编分享给大家的关于Java的六大问题分析,欢迎阅读...

    关于Java的六大问题分析

    引导语:Java 编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言。 。以下是百分网小编分享给大家的关于Java的六大问题分析,欢迎阅读!

    5a6b0e54b272dd47213ac369be40bacd.png

    一、到底要怎么样初始化!

    本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。

    1.类的属性,或者叫值域

    2.方法里的局部变量

    3.方法的参数 对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。

    所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null.对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或 finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。

    二、instanceof是什么?

    instanceof是Java的一个二元操作符,和==,>,

    三、“==”和equals方法究竟有什么区别?

    ==操作符专门用来比较变量的值是否相等。比较好理解的一点是:根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为“foo”的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用“==”操作符,结果会是false.诚然,a和b所指的对象,它们的内容都是“foo”,应该是“相等”,但是== 操作符并不涉及到对象内容的比较。对象内容的比较,正是equals方法做的事。看一下Object对象的equals方法是如何实现的:

    boolean equals(Object o){

    return this==o;}

    Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以 Object把这个任务留给了类的创建者。所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合 (HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。[ nextpage]

    四、final关键字到底修饰了什么?

    final使得被修饰的变量“不变”,但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。

    引用本身的不变:

    final StringBuffer a=new StringBuffer(“immutable”);

    final StringBuffer b=new StringBuffer(“not immutable”);

    a=b;//编译期错误

    引用指向的对象不变:

    final StringBuffer a=new StringBuffer(“immutable”);

    a.append(“ broken!”);//编译通过

    可见,final只对引用的“值”有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final 是不负责的'。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”.其实那是徒劳的。

    五、我声明了什么!

    许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象,目前指向“Hello world!”这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,如果再运行一句:String string = s;我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。

    六、String到底变了没有?

    没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:

    String s = “Hello”;

    s = s + “ world!”;

    s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是“Hello”,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为“Hello world!”,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String.例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:

    public class Demo {

    private String s;

    public Demo {

    s = “Initial Value”;}

    }

    而非

    s = new String(“Initial Value”);

    后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个 String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。

    【关于Java的六大问题分析】相关文章:

    展开全文
  • C++ 错误根源分析

    2021-11-05 14:20:51
    由于C/C++是静态类型编译语言,这类型错误一般都在编译阶段就会发现,不会带入到运行时阶段,但是这种类型的错误客观存在,并且会增大我们的排错时间; 出现这种类型的错误一般源自两种情况,一种是从动态语言转为...

    1.变量未声明

    由于 C/C++ 是静态类型编译语言,这类型错误一般都在编译阶段就会发现,不会带入到运行时阶段,但是这种类型的错误客观存在,并且会增大我们的排错时间;

    出现这种类型的错误一般源自两种情况,一种是从动态语言转为使用 C/C++ ,由于习惯问题而直接使用未定义的对象;另一种是由于粗心而写错了变量名字,导致编译器理解为一个新的未声明的变量。

    编译就会发现,容易解决掉。

    2.变量初始化

    变量初始化看似平淡无奇,但它却是我们程序运行过程中不确定行为的幕后推手,并且常常在我们意料之外;

    在Debug下某些编译器会自动对变量自动进行初始化,但Release下则是分配内存时内存中的随机值。变量忘记初始化,可能会导致Debug和Release下运行行为的不一致。

    a.对于函数、结构实例、类实例中的变量,编译器不会自动初始化,其值是不确定的,故直接使用会导致不确定的行为,这就是实践中经常碰到的程序行为表现莫名其妙的根源所在;

    在调试代码时,如果遇到0xCCCCCCCC、0xDDDDDDDD和0xFEEEFEEE等典型的异常值时,就要第一时间做出反应,可能是异常码对应的原因导致的。在Debug下,未初始化的栈内存会被编译器初始化为0xCCCCCCCC,未初始化的堆内存会被编译器初始化为0xDDDDDDDD。

    C/C++ 提供了两种初始化的机制可以完成结构实例和类实例的初始化,他们是:

    初始化机制

    简要说明

    备注

    构造函数

    1、用户使用 new/new[] 操作时自动调用

    2、构造函数顺序:从基类到子类逐层调用

    3、成员变量可在构造函数主体执行前初始化

    编译器会自动安插基类构造函数调用代码

    用户函数

    用户自定义并显式调用完成实例对象初始化,

    例如:Initialize();

    容易忘记调用

    子类的构造函数被 new/new[] 操作时自动触发,它首先调用最底层基类的构造函数对其成员进行初始化,以此类推直到子类构造函数完成整个初始化过程;编译器会自动在子类构造函数的最前面中插装基类的默认构造函数以完成基类数据的初始化,如需要传递特别参数,则需要显示的调用基类构造函数。

    由于类存在继承关系,基类和子类的构造函数调用存在着先后顺序关系,这意味着新对象的内存空间初始化会因为构造函数的调用顺序而呈现不同的状态。

    所以:

    1.构造函数、析构函数中要避免调用虚函数;见:effective c++ 学习_baidu_16370559的博客-CSDN博客 的条款9.在构造和析构函数中,不要调用virtual 函数。

    2.析构函数中要避免抛出异常;见:effective c++ 学习_baidu_16370559的博客-CSDN博客 条款8.别让异常逃离析构函数

    类继承关系带来了两个全新的概念:多态(类透视)对象切片

    b.对于动态分配的内存(new/delete、new[]/delete[]、malloc/free),默认是不会置初值的,需要显式的初始化;对于结构和类型实例,new/new[]操作会自动调用构造函数初始化内存,详情请参见【对象初始化】;

    c.值类型相对比较安全,可以声明时即初始化,这是最安全的作法. 如int a = 1;,string = “hello”类型等.

    总之:只有全局变量(全局名字空间变量和子名字空间内变量)、静态变量会在首次执行时初始化,其它例如函数内局部变量、类成员变量、结构成员变量都不会自动初始化,每次执行时会为每一个变量分配内存,局部变量、成员变量指向未初始化的内存,于是就出现了上述案例所出现的情况;

    3.内存访问

    总结起来可以归纳为几类,他们分别是:数组访问越界、指针访问越界、字符串访问越界、迭代器访问越界、访问游移指针对象、访问空指针.

    内存访问出错类别

    出错关键点

    数组访问越界

    索引序号大于等于最大个数

    指针访问越界

    指针超出最大分配范围

    字符串访问越界

    1、字符串结束符不存在

    2、目标字符串缓冲区小于源字符串

    迭代器访问越界

    1、迭代器越过右边界

    2、用其它容器迭代位置赋值

    访问游移指针

    指针所指内存被释放并回收再分配使用

    访问野指针

    变量声明时未初始化,链接器分配地址对应的随机值

    例如:0xCDCDCDCD

    访问空指针

    指针所指地址为零(NULL)

    所以要判断是否是空指针,数组大小的上下限。

    内存越界:

    就问题排查的难度而言,堆内存越界和全局内存越界比较难查,栈内存越界排查起来要容易很多。其中有一种情形下的越界(被调用函数越界主调函数的栈内存上)。

    对于内存越界,我们可以通过分块注释代码、添加数据断点(Visual Studio中支持数据断点)等手段进行定位

    空指针

    对于Windows系统,访问空指针会之所以会产生崩溃,是因为访问了Windows系统的64KB禁止访问的空指针内存区(即0-64KB这个区间的内存区域),这是Windows故意设定的一块小地址内存区域,是为了方便程序员定位问题使用的。一旦访问到该内存区就会触发内存访问违例,系统就会强制将进程强制结束掉.

     野指针

    野指针,是指该指针指向的内存已经被释放了,但还去访问该指针指向的内存,就会导致内存访问违例,导致软件崩溃。还有一种情形是同一段堆内存被delete或free了两次,也会触发崩溃。

    4.分配和释放

    内存分配和释放在我们的程序中分分秒秒的进行着,它分为隐式分配回收和显式分配回收两种,我们详细说明一下这两种情况:

    分配回收类型

    表现特征

    案例、说明

    隐式分配回收

    1、直接声明并使用

    2、编译器生成分配、回收代码

    适用于自动变量

    CListCtrl m_listProject;

    显示分配回收

    1、间接声明并使用

    2、用户编写分配、回收代码

    new/delete

    new[]/delete[]

    malloc/realloc/free

    OS提供的分配回收API

    如果不配对使用则会导致内存泄漏,进而导致内存分配失败。我们着重讨论内存泄漏的正常和不正的原因,详细如下:

    内存泄漏类型

    原因分析

    案例、备注

    对象内存未释放

    分配、释放操作未配对使用导致:

    new/delete

    new[]/delete[]

    malloc/free

    其它 API

    对象内存局部释放

    基类指针指向子类对象,释放该指针对象

    基类析构函数未定义为虚函数

    对象数组释放错误

    未逐个调用对象的构造函数

    new[]/delete[] 配对使用

    内存碎片

    由于数据对齐、内存分块分配后出现无法使用的小内存块

    这个难以避免,可以忽略它

     内存释放的问题:

    1.可以考虑用智能指针,这样系统自动释放内存。就不需要人工去释放。

    2.使用Windbg检测内存泄露.

    5.参数传递

    函数参数传递的不像内存分配、释放那么自由,受到诸多的限制,例如类型限制、常量修饰符限制、传递类型限制等,并且编译器能检测出大部分参数传递方面的错误,然而仍然无法阻止我们犯错误。

    类、结构类型的参数以值传递方式带来的危害:堆栈溢出、无法深度复制导致数据丢失,因此这两类参数应该尽量以指针、引用方式传递,对于不需要修改的参数尽量使用常量修饰符修饰(const)。关于函数参数的介绍见:函数参数约定、传递顺序、传递方式_baidu_16370559的博客-CSDN博客

    6.堆栈溢出(即线程栈溢出)

    实践中碰到的另一类典型的崩溃是堆栈溢出,代码能编译通过,运行过程中会产生stack overflow线程栈溢出异常而崩溃。如windows x86/x64   栈默认是1024KB,最大是32768K。

    x86/x64

    1024KB

    32768K

    常见的堆栈溢出类型一一列举,工作中稍加注意就可以预防;

    实现类型

    核心表现

    备注

    递归调用

    结束条件不能满足而无法返回

    循环调用

    间接的函数调用循环(如死循环)

    消息循环

    消息处理不当导致消息构成循环

    大对象参数

    结构、对象以值传递方式使用

    应以指针、引用传递

    大对象局部变量

    函数中结构、类变量直接定义

    使用 new 操作创建

    switch语句中的case分支过多应该将部分case分支封装到新的函数中

    switch语句中的case分支过多:

    虽然代码执行到case分支中这些变量才有“生命”,但其实这些变量已经在所在函数入口处几句分配好栈内存了,可以编写C++测试代码进入调试状态查看一下汇编代码,就能看出来的 

    7.转换错误

    标量类型强制转换出错是比较隐秘,因为 C/C++ 中本身就隐藏着大量的类型转换,不易为人察觉。如从 uint64_t 到 int 类型的转换,即从无符号类型向有符号类型转换。

    8.死循环

    死循环一般会引发CPU高占用率,一般不会导致软件崩溃。一旦发现进程占用的CPU过高,可能就是死循环触发的,也有可能是线程函数中没有添加sleep导致的。

     死循环会导致所在线程的卡顿或堵塞,如果发生在UI线程中,UI界面会出现点击没反应或者反应很慢的情况。对于Windows程序,可以使用Windbg或者Process Explorer去定位死循环所在线程及函数。

    9.函数调用约定不一致导致栈不平衡

    函数调用约定参考见:https://blog.csdn.net/baidu_16370559/article/details/121904896

           函数的调用约定不仅决定着函数参数压入栈中的先后顺序,还决定了应该由谁来释放给被调函数传递的参数所占用的栈空间。

           我们常用的stdcall标准调用约定和C调用约定,如果被调用函数是标准约定,则由被调函数释放传给被调函数的参数的栈空间。如果被调函数是C调用,则由主调函数去释放栈空间。

          比如我们经常用到的C函数printf,设定的是C调用约定,由主调函数进行释放。

          这个问题在设置回调函数时比较常见,特别是跨语言设置回调函数时。因为调用约定的不一致,可能会导致参数栈空间多释放了一次。比如C++的SDK给C#程序调用,因为C++语言中默认使用C调用约定,C#默认使用标准调用,如果回调函数没有显式地指明调用约定,在实际使用时就会出问题。

    默认情况下,Visual Studio 的/RTC编译选项只在Debug下是开启的,Release下该选项是关闭的。有的模块为了方便排查问题,在Release版本中开启了该编译选项。开启该选项会向代码添加很多额外的跟踪代码,会对程序的执行效率产生一定的影响。

    出错的样子:

     10.库与库之间不匹配


           因为一些原因,导致库与库之间的版本不一致或不匹配,从而导致程序运行异常或崩溃。比如底层的库只发布了debug版本的库,忘记发布release版本,导致Debug版本库与Release库混用,因为Debug于Release下的内存管理机制的不同导致崩溃。

           再比如,底层库的API头文件发生了改动(比如结构体中新增或删减了若干字段),但是只发布了库文件,忘记发布头文件;还比如,我们修改了头文件,但是发布时有若干关联的库没有编译或者编译失败,导致这些库用的还是老版本。

     处理这类问题的办法是,查看svn/git上的库发布记录,可能要重新发布库,也可能需要将相关的模块重新编译一下。 

    11.死锁

            死锁一般发生在多线程同步的时候,比如线程1占用了锁A,在等待获取锁B,而线程2占用了锁B,在等待获取锁A,两个线程各不相让,在没有获取到自己要获取的锁之前,都不会释放另一个锁,这样就导致了死锁。我们需要做好多个线程间协调,避免死锁问题的出现。很多时候我们能够根据现象及相关的打印日志,初步估计出可能发生死锁的地方。
           如果UI线程出现堵塞,或者是底层业务模块出现拥堵,业务出现异常,可能就是死锁引起的。可以将windbg挂在到目标进程上,查看所有线程的函数调用堆栈,确定发生死锁的是哪些线程,发生死锁的线程一般都会停留在WaitForSingleObject这个函数的调用或者类似函数的调用上,比如这样的截图:

    如图所示,当前线程卡在了WaitForSingleObject的函数调用上。通过函数调用堆栈,可以确定是调用了哪个函数触发的。
           对于使用临界区的死锁,使用Windbg排查比较容易分析,因为临界区是属于用户态的,我们只需要使用为Windbg进行用户态的调试即可。如果是信号量等其他的锁,则要使用Windbg进行内核态的调试,内核态的调试则要复杂很多。

    此外,可以使用《Windows核心编程》一书中附带的、有源码的工具LockCop来检测一下,但该工具有局限性,只适用于部分内核对象的死锁检测,有些内核的死锁检测不到,后面会有专门的文章来介绍该工具的使用详情。

    下载见:链接:https://pan.baidu.com/s/1jTX4mtFklVHLl4U7rNl7_g 
    提取码:itho

    LockCop的使用检测见:使用lockcop软件检测c++死锁_baidu_16370559的博客-CSDN博客

    12.GDI对象接近或达到1万个导致异常

    当程序中有GDI对象泄露时,程序长时间拷机运行,可能就会出现GDI对象接近或达到1万个,导致GDI绘图函数调用出现异常,出现窗口绘制不出来等问题。

    在MFC框架中,每个控件都是一个真实的窗口,都会占用若干个GDI对象,而一个程序窗口中可能会包含多个控件,这样单个窗口占用的GDI对象就不少了。

    在duilib框架中,窗口中的dui控件都是绘图绘制出来的,控件本身并不是窗口,所以dui窗口相对MFC窗口,占用的GDI对象可能要少很多
    对于GDI对象泄露,可以使用GDIView工具去实时查看目标进程中的GDI对象的占用及增长情况,就能确定哪种GDI对象数比较高了,这样就能有针对性地去排查代码了

    下载见:链接:https://pan.baidu.com/s/1k2eZ8ZXGHFWryRE2plHZww 
    提取码:6y27

    13.对包含C++类成员的结构体进行memset操作       

           在使用结构体对象时,在使用结构体之前,都会习惯性地对结构体对象进行memset操作,但如果结构体中包含C++类时,是不能进行memset操作的,我们需要在构造函数中对结构体对象成员进行初始化。

    延伸到:结构体或类不是pod,或者是含有数组、vector等容器,不能使用memset函数。

    不要轻易零初始化string、vector等STL标准容器及具有动态内存管理的类。

    见:

    C++ Struct内部包含容器(list/vector/map)初始化, 结构体嵌套复杂结构初始化_zzhongcy的专栏-CSDN博客_struct包含vector

    另外:对POD的介绍见:

    什么是 POD 数据类型_baidu_16370559的博客-CSDN博客_pod类型数据

    14.程序中抛出了异常,将部分该执行的代码跳过去了

           程序中执行了非法的操作,抛出了异常,但程序并没有崩溃,直接将当前函数余下的代码跳过去了, 导致部分该执行的代码没有被执行到,导致后续的代码逻辑出现了问题,进而导致程序出现异常。、

    15.模块注入到程序中导致程序出现异常


          我们之前遇到过很多次这样的问题,输入法的库注入到我们软件的进程中,导致了我们软件的崩溃。通过分析发现,崩溃是发生在输入法的模块中,但因为这个模块是注入到我们的进程中的,所以直接导致了我们软件的崩溃。此外,输入法注入到我的进程后还出现了软件卡顿、CPU占用不间断跳高的问题。 除了输入法,还有一些安全软件也会注入到我们的进程中,导致我们的软件出现异常。

    16.格式化时格式化符与参数不一致

           在数据格式化时,如果格式化符与参数类型不一致,可能会引发内存访问违例,导致软件崩溃。

    所以

    1.主要编程的规范技巧。参考见:https://blog.csdn.net/baidu_16370559/article/details/121165371

    2.出现内存泄漏会手工处理分析。参考见:c++ 内存泄漏检查的办法(如VLD)_baidu_16370559的博客-CSDN博客

    3.出现程序崩溃会手工处理分析。参考见:vs利用DMP文件、pdb文件查找release下的异常行号的方法_baidu_16370559的博客-CSDN博客

    利用windbg分析dmp文件_baidu_16370559的博客-CSDN博客

    展开全文
  • 近期工作中遇到一个问题:API接口性能始终达不到设定要求,虽然不是什么大型电商系统,但是100用户的并发量都慢的要命就有点说不...因此就有了以下从头到尾的问题排查和分析过程。 2.原因分析 测试工具输出的结果只是

    近期工作中遇到一个问题:API接口性能始终达不到设定要求,虽然不是什么大型电商系统,但是100用户的并发量都慢的要命就有点说不过去了。


    1.问题描述

    系统的入口–登录接口功能正常,进行并发测试时会出现卡顿、延迟等现象,并且测试结果并不稳定,有时快有时慢。单次访问时长在3s以内,100现场并发访问总时长能到达1-3分钟。初期以为是硬件资源及网络的限制,在硬件资源扩容后仍没有明显改善。此外在配置了负载均衡策略后也没有显著提升。因此就有了以下从头到尾的问题排查和分析过程。
    在这里插入图片描述

    2.原因分析

    测试工具输出的结果只是表现,想要挖出问题的根源还是要耐心分析,从总体结构到代码细节逐个琢磨。

    2.1.结构分析

    系统总体上使用微服务架构,前端应用通过网关访问后端服务接口。用户登录时通过网关访问验证授权服务Admin获取token令牌,之后携带token令牌访问其他应用服务,应用服务通过比对缓存中的令牌判定身份,确认权限后返回相应的资源。
    在这里插入图片描述
    总体结构上比较常规,出现问题的可能性不大,即使架构出问题了,也没法去改,毕竟成本太高。况且登录接口是所有业务接口的前置条件,牵一发动全身,这一步要是搞不定,后面附带的问题会更多。

    2.2.流程分析

    刚开始时测试人员和开发人员只关注总时长和平均时长这两个指标,导致我自己排查问题时也只看这两个指标。在一次测试过程中发现总时长和平均时长大幅度下降,增加到万级以上竟然平均15ms的响应,有这么厉害吗?仔细检查一看,原来是接口地址写错了,所有请求都是404,当然很快了。这时我意识到总时长和平均时长并不能反映真实的压力测试结果。单次请求时长只是一次HTTP请求到响应的间隔,并不代表每次请求返回的都是正确结果,404、501、200这些状态的响应都会被放在一起计算。
    再次测试果然发现了问题的端倪,以100个线程并发测试为例。共进行了10次测试,每次总时长约2.35分钟,平均响应时间15万毫秒。每100次请求中有4次响应超时,有12次为501错误,有84次为正确结果,而且响应超时造成了测试进程的堵塞,往往能达到1分钟左右。因此只要有响应超时的问题存在,无论是并发100还是并发10000,总时长总是要两分钟以上。
    从一开始开发人员就没有“代码可能有问题”的考虑,毕竟登录接口已经实际使用这么长时间了,没发现有什么问题。但是在并发测试中出现的响应超时和501内部错误该如何解释,一切现象的矛头都指向一个地方–“代码逻辑有问题”。经过本地测试再对登录接口的逻辑进行了梳理,整个登录过程的时序图如下:
    在这里插入图片描述
    登录过程涉及三个重要节点:网关GatewayHost,授权验证服务AdminService和缓存数据库Redis。网关GatewayHost提供两个接口Login_Auth/Login(登录)、Login_Auth/GetOAuthUserInfo(获取用户信息),分别映射到授权验证服务AdminService的接口OAuth2/OAuthLogin(登录)、OAuth2/Get_UserInfo(获取用户信息)。其中网关GatewayHost的接口Login_Auth/Login内部又调用了接口Login_Auth/GetOAuthUserInfo,因此内在代码逻辑实际上是分为两步:登录、获取用户信息。

    详细流程是:

    1. 前端请求登录网关GatewayHost的接口Login_OAuth/Login

    2. 网关GatewayHost将请求重定向至授权验证服务Admin的接口OAuth2/OAuthLgoin

    3. a. 用户名及密码验证成功后,Admin创建登录令牌LoginToken并存储至Redis,过期时间为1分钟

    4. b. 网关GatewayHost接收到Admin接口OAuth2/OAuthLogin的正确响应(含LoginToken令牌)后开始执行接口Login_OAuth/GetOAuthUserInfo

    5. 网关GatewayHost将接口Login_OAuth/GetOAuthUserInfo重定向(携带LoginToken令牌)至Admin的接口OAuth2/Get_UserInfo

    6. 授权验证服务Admin在接口OAuth2/Get_UserInfo中验证LoginToken令牌

    7. 身份验证成功后创建会话令牌SessionToken并存储值Redis,过期时间为1小时

    8. 在执行完查询用户信息等逻辑后,返回用户信息

    以相同的用户名和密码再次请求与上一次请求的区别在于:

    步骤3.a处,重新创建LoginToken令牌,与原有LoginToken令牌无关,且原有LoginToken令牌过期自动销毁
    步骤6,创建SessionToken令牌,并覆盖原有SessionToken令牌,以账号AcountId为标识,重新计算过期时间

    2.3.代码分析

    经过详细代码调试发现501错误发生在步骤5,即验证LoginToken令牌的地方,错误详情为:“令牌无效或者过期,请重新登录”。也就是代码执行到Step2(Get_UserInfo)时,在Step1(OAuthLoginIn)中创建的LoginToken已经过期销毁了,这也意味着从执行OAuthLogin到执行Get_UserInfo中间间隔了1分钟以上。看起来不可思议,实际上确实如此,之前测试结果中的平均响应时间也证明了这一点。

    为什么会发生这种情况?看来还是要从并发的实际过程说起。

    举个例子,把服务看作柜台(GatewayHost是A柜台、Admin是B柜台),把接口看作窗口(Login_OAuth/Login是A1窗口、Login_OAuth/GetOAuthUserInfo是A2窗口、OAuth2/OAuthLoginIn是B1窗口、OAuth2/Get_UserInfo是B2窗口),把请求看作人。100个人同时进入办事大厅到A柜台的A1窗口排队,A1窗口告诉他们到B1窗口领取凭证,于是一堆人呼啦啦又去B1窗口排队,在B1窗口拿到凭证后,又被告知要去A2窗口排队领取结果,又是一堆人去A2窗口排队,排队结果是被告知领取结果是在B2窗口,又是一堆人去B2窗口排队领取结果,领到结果,一个人的任务才算完成。这个过程中的关键点在于无法保证每个人在不同的窗口排队时有同样的位次,同一个人在A1窗口可能排第1位,在下一个窗口可能就是排第100位。每个人的每次排队都是一次HTTP请求,B1到B2的间隔时间太长凭证就会过期(501 internal error),A1到B2的间隔时间太长就会操作超时(operation timed out)。

    所以症结就在这里了,关键就要找到问什么两次方法执行间隔这么长?
    继续通过代码进行排除法分析。
    总体架构或者请求的流程是:前端发起请求,网关服务Gateway转发请求,授权验证服务Admin处理请求后原路返回。按照这个流程设计了几个场景来排查问题。

    • 直接访问授权验证服务Admin的接口
      同样是并发100,不经过网关Gateway,直接访问授权验证服务Admin的接口。不到1秒跑完,快到飞起,不敢再说是授权验证服务Admin的问题了。
    • 授权验证服务Admin的接口不做业务处理直接返回
      注释掉授权验证服务Admin接口中的所有业务逻辑,直接返回200,应该没有比这更快了吧。并发100,跑起…卡死…大约两分钟跑完。原来叛徒在这里,网关服务Gateway的问题没错了。

    看来问题就在网关这里,网关服务引用了Ocelot组件,并配合Consul使用。都属于常规操作,跟网上的大部分示例都一致,配置文件也没找到毛病。配置文件修改后结果也没有发生很大变化,可以排除Ocelot和Consul的嫌疑了。

    这时接口重定向的代码逻辑成功引起了我的怀疑,之前也怀疑过它,但由于它还算能正常工作也没多想。现在看来这部分逻辑确实值得探讨,来看看它的逻辑。

    应用服务类Login_OAuth为开发给外部的接口,在顶部实例化Realize类,通过Realize实例来进行实际接口的跳转。同时也对传递的参数进行转换。

    在这里插入图片描述

    在Realize实例的GetOAuthOfAdmin方法中才是真正实现接口跳转的地方。核心代码就是iAdmin.GetAdminOfOAuth。iAdmin是接口IAdmin的实例,接口实例是在Realize实例的开始阶段获取。

    在这里插入图片描述

    首先是获取一个数据对象SignDto,再从SignDto中解析IAdmin的实例。

    对象SignDto的实例化过程比较复杂,从代码的执行逻辑来看,既包含了IAdmin接口的注册(将IAdmin接口注册为HttpApi),也包含了IAdmin实例的解析(解析出当前实例化的HttpApi对象)

    在这里插入图片描述

    另外就是SignDto内包含了一个线程共享的单例对象,用于读取存储应用基本配置,包含应用Id、应用key和租户id。

    从代码逻辑可以看出当时的设计思路:线程共享的单例对象SignDto包含应用配置信息、HttpApi接口的注册和解析,既能在线程间实时更新和共享配置信息,也能控制要注册的接口和要解析的实例,从而动态的实现Http请求。

    单次运行这个逻辑并没有问题,运行也是正常的。但是遇到并发请求时,问题就来了。Realize类的实例化、SignDto的实例化、HttpApi接口的注册、HttpApi实例的解析在每次请求都会执行一遍,不断的装箱拆箱势必耗费不少时间。

    另外SignDto的单例对象为了避免线程抢占锁死使用了volatile关键字,然而从查询资料的情况来看这个关键字并不能达到期望的效果。

    其实网关这一堆逻辑的核心只有一个,在Gateway接口内部创建Http请求访问授权验证服务Admin接口,也就是请求套请求。通过一系列的接口是实现了网关服务与接口约定的解耦,性能却也不可避免的降低了。

    3.解决过程

    经过上述一系列分析后,问题的根源已经被挖掘出来,解决过程也是顺利成章的事情。关键的处理方式有:

    3.1.修改SessionToken机制

    从目前的流程来看LoginToken只是在身份验证成功后获取用户信息的临时令牌,而且只使用一次,短时间内过期后自动销毁。访问其他资源使用的才是会话令牌SessionToken,在目前的登录流程中并无任何用处。既然用户身份已经验证成功了,为什么还要拿着临时令牌去换正式令牌?直接给正式令牌不行吗?所以有必要在OAuthLoginIn接口身份验证成功后就创建正式的会话令牌SessionToken,获取用户信息时验证SessionToken,时间足够长,不用担心过期。

    但随之而来的一个问题是,每次身份验证成功,会话令牌SessionToken会被刷新。如果短时间内使用相同用户名和密码并发访问登录接口,SessionToken会被不断重置刷新,在获取用户信息时仍有可能用的是旧的令牌,依然会报错。所以需要将SessionToken的机制改为若存在则不刷新,不存在则创建。

    3.2.修改HttpApi接口注册机制

    项目引用了类库WebApiClient,这里继续使用。WebApiClient是开源的第三方类库,对HttpClient进行了封装,性能接近原生的HttpClient。

    在启动类Startup中引入WebApiClient,并进行配置。

    services.AddHttpApi<RunGo.OAuth2._0_Base.IRealize>();
    services.ConfigureHttpApi<RunGo.OAuth2._0_Base.IRealize>(option =>
         {
             option.HttpHost = new System.Uri(_Configuration["Sign:AdminHost"]);
             //option.JsonSerializeOptions.Converters.Add(new IsoDateTimeConverter("yyyy-MM-dd HH:mm:ss"));
         });
    

    IRealize是定义HttpApi接口的接口类。

    public interface IRealize:IDisposable, IHttpApi
    {
        #region 管理中心提供的接口
    
        /// <summary>
        /// 登录
        /// </summary>
        /// <returns></returns>
        [HttpPost("/api/services/Admin/OAuth2/OAuthLoginIn")]
        Task<string> GetOAuthOfAdmin([JsonContent] GetOAuthOfAdminInput input);
        #endregion
    }
    

    在需要使用的地方通过构造函数实现依赖注入。同时注入配置管理器IConfiiguration和部分局部变量,用于获取配置信息。
    在这里插入图片描述

    在使用时直接调用,剩下的交给依赖注入框架。

    /// <summary>
    /// 登录接口
    /// </summary>
    /// <returns></returns>
    [Route("api/services/app/Login_OAuth/Login")]
    [HttpPost]
    [HttpOptions]
    [DontWrapResult]
    public virtual Task<dynamic> Login(LoginDto loginDto)
    {
        var info = $"网关独立请求测试";
     
        GetOAuthOfAdminInput inputDto = new GetOAuthOfAdminInput()
        {
            userName=loginDto.userName,
            passWord=loginDto.passWord,
            response_type="code",
            client_id=clientId,
            client_secret= clientSecret,
            tenantId=tenantId,
            redirect_uri=redirectUri,
            state=loginDto.state,
            source=loginDto.source
        };
     
        string resultJson = _realize.GetOAuthOfAdmin(inputDto).Result;
        info = new { result = resultJson }.ToJson();
        return Task.FromResult<dynamic>(info);
    }
    

    经过改造后的接口再进行测试,结果就能令人接受了。
    在这里插入图片描述
    并发100,总时长1s,平均249ms;并发500,总时长4s,平均3000ms;并发1000,总时长16s,平均10200ms;并发1500,总时长45s,平均12000ms;跟之前比已经有质的提升了。

    4.经验总结

    • 解决问题不能只看问题表象,还是要刨根问题找根源。
    • 测试工作要全面,功能可用并不是结束,而是开始。
    • 项目前期要多花时间在设计上,否则在项目后期需要花更多时间去填补设计缺陷。
    • 工作交接要到位,不会有人愿意去翻看别人一年前写的代码,更不要说写代码的人已离职,逻辑全凭推断。
    • 开发过程代码质量要监督到位,否则等到发现问题时已经是屎山巨大、烂不可闻。
    • 问题分析要有耐心,认真思考绝不会错。

    好的代码不是一天练成的,烂的项目也不是一日造就的。

    展开全文
  • 质量问题根源寻找,得从以下几个方面入手: 1、要想尽一切办法获取更多的问题信息 当产品在客户端发生问题问题现象和信息很多时候是通过销售转发客户邮件,信息传递到工厂,对于问题信息的获取不完整,不准确,...
  • 如果收到监控系统的告警,发现系统资源或者应用程序出现性能瓶颈,又该如何进一步分析它的根源呢?今天,我们就分别从系统资源瓶颈和应用程序瓶颈这两个角度,一起来看看,性能分析的一般步骤。 系统资源瓶颈 在系统...
  • 点击上方「蓝字」关注我们今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。系统性能问题分析流程我们首先来分析...
  • 个人工作存在的不足和改进措施篇一通过学习文件和领导讲话精神,结合工作实际对照自查,我对个人进行了认真剖析,深刻感受到自己在学习、工作、生活中还存在很多问题与不足,现对自己在思想、政治、工作等方...
  • Greenplum常见问题分析与处理

    千次阅读 2021-02-01 17:07:58
    第二节 数据库启动停止的问题分析 1、gpstop 有哪些几步操作 2、too many clients alrrady 3、database is in recovery mode / is starting up 4、gpstart 有哪些几步操作 5、postmaster.pid 不存在
  • 《国务院关于加强审计工作的意见》(国发〔2014〕48号)中强调,要狠抓审计发现问题的整改落实:健全整改责任制,加强整改督促检查,严肃整改问责。被审计单位的主要负责人作为整改第一责任人,要切实抓好审计发现问题...
  • Android程序报错程序包org.apache.http不存在问题的解决方法Android Studio 2.1中使用 Android SDK 6.0(API 23),加载融云Demo时,报错:解决办法:Android 6.0(api 23)已经不支持HttpClient了,在build.gradle中 ...
  • 我们可以从三个方面分析:堆栈的局部信息,一次堆栈的统计信息,多个堆栈的对比信息。从一次的堆栈信息中,我们可以直接获取以下信息:每一个线程的调用关系,当前线程在调用哪些函数每个线程的当前状态,持有哪些锁...
  • 性能分析的一般步骤 一、CPU 性能分析 二、内存性能分析 三、磁盘和文件系统 I/O 性能分析 四、网络性能分析 五、应用程序瓶颈 性能优化的一般方法 一、CPU 优化 二、内存优化 三、磁盘和文件系统 I/O 优化...
  • “5WHY”分析概述所谓“5WHY”分析法,又称“5问法”,就是连续反复使用5次“为什么”方式自问,以打破砂锅问到底方式...理解“5WHY分析法”的过程和含义“5WHY”分析法是根据事实分析找到问题根源,给出治本对策...
  • 2.1、可见性问题产生的原因 在很多年前,那个嫁妆只需要一个手电筒的年代你或许还不会出现可见性这样的问题,因为大家都是单核处理器,不存在并发的情况。 而对于现在“视金钱如粪土”的年代。多核处理器已经是现代...
  • 问题、探究过程和解决方法: 在接手一个新的项目构建的过程中,遇到了构建项目过程中,项目一直处于parsing java的过程 打开构建日志的位置 日志位置: 查询构建日志(build-log)发现 项目触发了很多的低...
  • 篇一:《工作中存在的不足及改进措施》通过近一段时间的工作,反省自身,还存在许多不足和缺点,现将近期的工作、学习中存在的不足和缺点简要总结如下:1、自身的专业业务水平不高,事故应急处理能力不强.虽然通过学习和...
  • 如果收到监控系统的告警,发现系统资源或者应用程序出现性能瓶颈,又该如何进一步分析它的根源呢? 接下来分别从系统资源瓶颈和应用程序瓶颈这两个角度,看看性能分析的一般步骤 一、系统资源瓶颈 首先来看系统资源...
  • 可有的网站原创度高的文章却得不到好的收录数据,这到底是哪里出现了问题呢?下面就为大家针对原创性文章收录不高的情况进行分析,看看到底是因为哪些原因引起的,希望可以帮助到有需要的大家哦。1、抓取是否存在...
  • 考试试卷分析与反思

    2021-07-28 07:53:23
    小编语:为你精心整理的试卷分析,希望对你有帮助!如果喜欢就请继续关注我们的后续更新吧!考试试卷分析与反思1第一单元:小数乘法总体来说:卷面干净,整洁,认真读题、审题,计算准确,简算熟练,大部分学生对所...
  • 查找 EXC_BAD_ACCESS 问题根源的方法

    万次阅读 2012-09-06 15:30:22
    写程序遇到 Bug 并不可怕,大部分的问题,通过简单的 Log 或者 代码分析并不难找到原因所在。但是在 Objective-C 编程中遇到 EXC_BAD_ACCESS 问题的时候,通过简单常规的手段很难发现问题。这篇文章,给大家介绍一个...
  • 本文转自公众号接地气学堂有同学问:用户流失该怎么分析?用户流失率的数据可以算,可算出来以后呢?只看数据似乎完全看不出什么流失原因,只知道用户已经X个月没有来了,也不知道看到这个能干啥。今天...
  • 5、问题陈述法 6、科学法 7、诊断循环 8、工具法 9、USE方法 10、工作负载特征归纳 11、向下挖掘分析 12、延时分析 13、R方法 14、事件跟踪 15、基础线统计 16、静态性能调整 17、缓存调优 18、微基准...
  • 对于不会玩吃鸡的我,到这里并没有结束,找到问题和解决问题的确很重要,但是找到问题出现的根源更重要,这样就能在下次规避此类问题,作为一个程序员不要两次掉入一个坑里。 我在想这个问题,本地Window环境怎么就...
  • 酒瓶与瓶盖配合问题,直接影响酒类产品功能性,是产生漏液、拨起、打转等重大质量问题根源。正确的分析认识酒瓶与瓶盖配合问题将有效地帮助我们识别并控制产品风险。本文将着重从常见几大类酒瓶及瓶盖配合问题进行...
  • 最近公司内部提供了一份应用高危漏洞的清单,其中提到了fastjson和jackson,因为之前对fastjson因为多态问题引发的反序列化问题有过了解,所以打算也做一个简单的分析。 漏洞简述 2020年08月27日,360CERT监测发现 ...
  • 对数据异常不敏感,没有及时发现问题所在,导致企业经营损失;分析师/运营/产品同学需要每日关注实时/离线数据,人工操作费时费力,效果不佳;依赖业务人员经验设置预警规则,在特殊场景下难以精准定...
  • 公司固定资产管理存在的不足及改善提议当今企事业单位政府机关资产管理全过程中,怎样搞好固定资产管理工作中是让许多 企事业单位政府机关有费劲费时间的事儿,固定资产做为企事业单位关键构成部分,具备使用价值高...
  • 试卷分析报告(精选10篇)试卷分析是教学环节中不可缺少的部分,它可以反映出学生的学习情况,好的试卷可以准确的反映出学生得学习情况。下面和小编一起来看试卷分析报告(精选10篇),希望有所帮助!试卷分析报告1一、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,779
精华内容 16,711
关键字:

存在问题的根源分析