第 13 页的文章列表

c++和Java的对象内存异同

##前言 好久都没有写博客了。经过一段时间的实习,收获上感觉并不是很大,还好自己也有看一些东西。因为毕业设计做的是c++方面的编程,算是初探c++的内容。这里简单记录自己对c++和java的对象内存区别,谈谈c++为什么性能要比java高。想到哪里就记到哪里,因为很多东西自己也要查证才能确定。 ##1.栈和堆 栈和堆在数据结构上是两种不同的结构,栈的特点是先进后出,而堆则可以看做是一大堆数据的集合。在c++和Java的内存中,也有堆和栈的概念。 首先理解一下,内存中栈中的每一个元素都被称为栈帧,每个栈帧中存储一个方法(function)中的用到的变量内存。程序执行时,就是一个栈中的每一帧被读取执行的过程。堆则是主要用来存储对象。那么在c++和java中到底有什么不同呢? ```c++ object* processObject(int param){ object obj; obj.param = param; return &obj; } int main(){ object* obj = processObject(10); obj->do_something(); //1 }


1处是会出现一个空指针错误的。而如果这是一段java的代码则完全没有问题。 在c++和java中,一个栈帧被执行完,这部分内存就直接被释放了。而在c++中,所有通过声明产生的对象都是在栈上开辟内存空间,所以函数执行完后这个对象也就被释放了,那么返回出来就是一个空指针了。只有通过new生成的对象才会在堆中申请空间,因此通过new出来的对象都需要在不用时手动释放内存,不然就会内存泄露。而在java中,对象是在堆中生成的(JDK7中的字符串是在常量池中,int等字面值在限定范围内也会在常量池中申请内存),所有方法内的对象都是一个指向堆中相应对象的指针(注意是指针而不是引用,很多人认为java中向方法传递参数是引用传递,但其实是值传递,只不过这个值是指向对象的指针)。而在java中不需要手动释放内存则是因为Java拥有GC机制(Garbage collect,垃圾回收)。这个在之后再谈。 ##2 java的强弱引用和c++的智能指针 java的强弱引用和c++的智能指针都是在希望可以更好的进行内存管理的前提下出现的,java的强弱引用可以帮助GC机制进行粒度更细的内存回收,而c++的智能指针则是让没有GC机制的c++有了一定能力的自动释放内存的能力。 ###a. java的强弱引用 java的引用共有四种,分别为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。 之所以定义出这么多引用,是希望在GC发生时,可以更灵活的进行对象的销毁。 强引用就是通过new出来的对象,强引用表示的都是必需的对象,这是无论如何都不会被回收的。 而软引用则表示有用但非必需的对象。当系统内存不够将要发生内存溢出异常的时候,会将软引用的对象列入回收范围,进行二次回收。只有这次回收仍然内存不足才会抛出内存溢出异常。JDK1.2之后提供了SoftReference类来实现这个。 弱引用也用来表示非必需的对象,不同的是,这个引用并不能帮助对象躲过任何的GC,也就是说无论如何,发生GC时这个对象都会被回收,弱引用的唯一作用就是用来取得一个对象实例。JDK1.2之后提供了WeakReference类来实现这个。 综合上述,虚引用的存在也比较清晰了。它不仅不能帮助对象躲过GC,甚至不能取得对象的实例。唯一存在的用处就是当其引用的对象被GC回收时会收到一个系统的通知。JDK1.2之后使用PhantomReference类来实现。 ###b. C++的智能指针 相比于Java的GC机制以及为GC服务的各种引用,C++的智能指针则稍显简单。但是这个看似简单的c++的智能指针,对于c++来说意义也许并不是那么低。在我的理解中,c++之所以实用,因为相对于c,c++有着丰富高效的STL库和被大多数开发者接受的OOP。对于一个巨大的项目来说,OOP往往可以更好的帮助系统模块化,降低耦合,而STL库则是开发者进行高效工作的基础。相对于Java,单单从c++没有GC机制,就意味着它有着更高的性能,另外Java的虚拟机也是影响性能的一个方面。可也是因为c++没有GC机制,对于一个多人合作的巨大的项目来说,内存泄露则是面临的首要问题。举个很低端的例子 ```c++ class Example{ Mysql* getMysqlConnect(){ Mysql* mysql = new Mysql; return mysql; } }; 一个项目中所有的mysql对象指针都通过上面的getMysqlConnect()获取,看似没有问题,但是没有人敢保证一个项目中所有使用这个方法的人都会在使用完mysql对象后,手动将其释放。因此这里需要一个shared_ptr或者unique_ptr去包装一下指针,这样调用函数的人就不需要在外部释放对象内存,也就避免了内存泄露的问题。当然在使用shared_ptr时也一定要注意,因为可能会出现循环引用的问题,循环引用则会导致对象内存一直不被释放。 当然了,上面的例子只是为了说明这个例子而列举出来的,事实上可以直接返回对象而不是指针。 c++ class Example{ Mysql getMysqlConnect(){ Mysql mysql; return mysql; } }; 这里我一开始以为是会将对象复制一遍返回出来,但事实上在c++11中有了移动语义,即这种在拷贝语义和移动语义中,会优先使用移动语义来将mysql对象从方法中移出来,而不是拷贝mysql对象返回,并把方法中的对象删除。 ##3 c++如何尽量避免内存泄露 后天再写啦

分类:c++, java

linux下c/c++的内存泄漏分析

使用valgrind进行内存分析

简介

官网地址:http://valgrind.org 主要提供以下工具:

Memcheck 是一个内存错误的检测工具,帮助你的程序,尤其是c/c++程序出现更少内存的问题。 Cachegrind 是一个缓存和分支预测探查工具,帮助你的程序运行的更快。 Callgrind 也是一个和缓存相关的调用图工具,和Cachegrind有一部分重叠,但也生成一些Cachegrind不提供的信息。 Helgrind 是一个线程错误的检测工具,在多线程场景下能派上用场。 DRD 也是一个线程错误的检测工具,与Helgrind功能一样,但是使用了不同的分析技术,可能会发现不同的问题。 Massif 是一个堆分析工具,帮助程序使用更少的内存。 DHAT 是不同与Massif的堆分析工具,帮助你理解块的生命周期, 块的利用率, 以及layout的低效。 SGcheck 是一个实验性的工具,帮助检测栈的超支和全局数组。是Memcheck的功能方面的补充:可以检测出Memcheck无法发现的问题,反之亦然。 BBV 是一个实验SimPoint基本块向量生成器。对于进行计算机体系结构研究和开发的人来说,这是有用的。

这里记录的只是Memcheck的使用,其他的使用可以参考上述的官网的网址。

ubuntu下的安装

sudo apt-get install valgrind

使用方法

valgrind [valgrind-options] your-prog [your-prog-options]

例如,对ls -l进行分析:

valgrind –tool=memcheck ls -l

valgrind的默认工具就是memcheck,所以使用memcheck工具时可以省略–tool参数

注意事项:

1、valgrind工具会减慢程序的运行速度 2、程序编译时需要开启-g,帮助valgrind可以更精准的定位到错误 3、程序编译时最好关闭优化,否则可能产生不正确的未初始化错误信息,以及遗漏未初始化错误。 4、程序编译时最好使用-Wall,帮助valgrind在高优化等级的程序中精准识别一些甚至是全部的问题

具体使用说明

Valgrind会记录下一些注释,文本流,具体的错误报告以及其他的重要的事件。类似与以下的格式: ==12345== some-message-from-Valgrind 12345代表进程Id,这个格式方便区分程序输出和Valgrind的注释输出,以及区分多个进程的输出。Valgrind只会输出最重要的信息,如果需要一些次要的信息,可以使用-v参数。 你可以使用三种方式去导出这些错误 1、默认情况:会直接在控制台打印出来 2、使用文件记录,这个时候需要使用参数–log-file=filename,filename代表存储的文件名 3、通过socket发送:使用参数–log-socket=192.168.0.1:12345,不加端口号会使用默认的1500端口,Valgrind提供了一个叫Valgrind-listener的工具去监听这个网络流。

读懂memcheck工具产生的错误信息

1.非法读/非法写的错误(Illegal read/Illegal write errors)

例如: Invalid read of size 4 at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9) by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9) by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326) by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621) Address 0xBFFFF0E0 is not stack’d, malloc’d or free’d 出现这个错误是因为程序读或写了Valgrind认为不应该读写的内存区域

2.使用了为初始化的值

例如: Conditional jump or move depends on uninitialised value(s) at 0x402DFA94: _IO_vfprintf (_itoa.h:49) by 0x402E8476: _IO_printf (printf.c:36) by 0x8048472: main (tests/manuel1.c:8) 这样一段错误可能就是由以下的代码产生

int main()
{
int x;
printf ("x = %d\n", x);
}

Valgrind会跟踪变量x,直到x被使用时才会报错。在这里x被传入了printf,进而进入_IO_printf,但是这些都不会报错,只有当x被传递到_IO_vfprintf,_IO_vfprintf开始检查x是否可以被转换为ASCII码时才报错。 未初始化值一般有两种情况:

  • 1、局部变量没有被初始化,就像上面一样。
  • 2、The contents of heap blocks (allocated with malloc, new, or a similar function) before you (or a constructor) write something there.

为了找到未初始化变量一开始的位置,可以使用–track-origins=yes参数。当然这会减慢Valgrind的使用速度。

3.在系统调用中使用了未初始化或者不可寻址的值

Valgrind会检查所有系统调用的参数,一般有以下3类:

  • 1、检查所有直接调用的参数,即使已经初始化了。
  • 2、如果系统调用需要你的程序申请的缓冲区,Valgrind会检查所有的缓冲区内容,看它是否可寻址,内容是否初始化了。
  • 3、如果系统调用需要写入用户提供的缓冲,Valgrind会检查是否可寻址。

下面是两个使用了无效参数的系统调用的例子:

#include
#include
int main( void )
{
char* arr = malloc(10);
int* arr2 = malloc(sizeof(int));
write( 1 /* stdout */, arr, 10 );
exit(arr2[0]);
}

得到这样的错误信息: Syscall param write(buf) points to uninitialised byte(s) at 0x25A48723: __write_nocancel (in /lib/tls/libc-2.3.3.so) by 0x259AFAD3: __libc_start_main (in /lib/tls/libc-2.3.3.so) by 0x8048348: (within /auto/homes/njn25/grind/head4/a.out) Address 0x25AB8028 is 0 bytes inside a block of size 10 alloc’d at 0x259852B0: malloc (vg_replace_malloc.c:130) by 0x80483F1: main (a.c:5) Syscall param exit(error_code) contains uninitialised byte(s) at 0x25A21B44: __GI__exit (in /lib/tls/libc-2.3.3.so) by 0x8048426: main (a.c:8) write(a)和exit(b)都是错误的,a从堆中向标准输出中写入了未初始化的arr。b向exit传递了为初始化的值。注意a的错误在于arr指向的内存区域,而b的错误直接是arr2[0]。

4.非法的释放(Illegal frees)

例如: Invalid free() at 0x4004FFDF: free (vg_clientmalloc.c:577) by 0x80484C7: main (tests/doublefree.c:10) Address 0x3807F7B4 is 0 bytes inside a block of size 177 free’d at 0x4004FFDF: free (vg_clientmalloc.c:577) by 0x80484C7: main (tests/doublefree.c:10) 这个例子中,一块区域被free了两次,所以出现Illegal frees的错误。

5.使用不合适的释放函数去释放堆区域的内存

例如: Mismatched free() / delete / delete [] at 0x40043249: free (vg_clientfuncs.c:171) by 0x4102BB4E: QGArray::~QGArray(void) (tools/qgarray.cpp:149) by 0x4C261C41: PptDoc::~PptDoc(void) (include/qmemarray.h:60) by 0x4C261F0E: PptXml::~PptXml(void) (pptxml.cc:44) Address 0x4BB292A8 is 0 bytes inside a block of size 64 alloc’d at 0x4004318C: operator new[](unsigned int) (vg_clientfuncs.c:152) by 0x4C21BC15: KLaola::readSBStream(int) const (klaola.cc:314) by 0x4C21C155: KLaola::stream(KLaola::OLENode const *) (klaola.cc:416) by 0x4C21788F: OLEFilter::convert(QCString const &) (olefilter.cc:272) 这个错误是因为使用new[]开辟内存空间,却使用了free去释放内存。 使用malloc, calloc, realloc, valloc or memalign,必须使用free释放内存。 使用new, 必须使用delete释放内存。 使用new[],必须使用delete[]释放内存。

6.源内存区域和目标内存区域重叠

memcpy, strcpy, strncpy, strcat, strncat这些函数可以从源内存区域复制内容到目标内存区域。这两块区域是不可以重叠的。POSIX标准规定这种行为是未定义的。 例如 ==27492== Source and destination overlap in memcpy(0xbffff294, 0xbffff280, 21) ==27492== at 0x40026CDC: memcpy (mc_replace_strmem.c:71) ==27492== by 0x804865A: main (overlap.c:40)

7.Fishy argument values

所以的内存分配函数都指定了分配的内存大小,这个大小必定为正数,或者一般情况下不会极度的大。例如在64位的机器上,不会申请分配2^63大小的内存。这种为负数的或者过于大的参数被成为Fishy argument。 例如: ==32233== Argument ‘size’ of function malloc has a fishy (possibly negative) value: -3 ==32233== at 0x4C2CFA7: malloc (vg_replace_malloc.c:298) ==32233== by 0x400555: foo (fishy.c:15) ==32233== by 0x400583: main (fishy.c:23)

8.内存泄漏检测

Valgrind会跟踪所有由malloc或new申请的内存,所以当程序退出时,Valgrind知道哪些内存没有被主动释放。 如果–leak-check参数设置得当,对于每一个未被释放的内存块,Valgrind判断从root-set中的指针是否能到达这些内存块。root-set包含(a)普通的所有线程使用的寄存器,(b)初始化的, 对齐的, 指针大小的数据块,包括栈。一个数据块有两种方式可到达,第一种是“start-pointer”,即指针在数据块的开头;第二种是“interior-pointer”,即指针在数据块的中间。“interior-pointer”有多种方式出现:

  • 指针一开始是“start-pointer”,被程序有意或无意的移动到中间。

  • 可能只是巧合。

  • std::string中的char的指针。

  • 有些代码分配块内存,使用前8个去存储作为64位的数。例如sqlite3MemMalloc就是这样做的。

  • 可能是一个c++对象(具有析构函数)数组的指针,由new[]来分配内存。这种情况下,有些编译器存储一个“magic cookie”,包含数组长度存储在分配的块开头。

  • 可能是一个多重继承产生的c++对象的内部部分的指针。

在使用了启发式(heuristics)的情况下,stdstring, length64, newarray and multipleinheritance情况下的“interior-pointer”会被当成“start-pointer”对待。 考虑下面这九种情况: Pointer chain AAA Leak Case BBB Leak Case


(1) RRR ————> BBB DR (2) RRR —> AAA —> BBB DR IR (3) RRR BBB DL (4) RRR AAA —> BBB DL IL (5) RRR ——?—–> BBB (y)DR, (n)DL (6) RRR —> AAA -?-> BBB DR (y)IR, (n)DL (7) RRR -?-> AAA —> BBB (y)DR, (n)DL (y)IR, (n)IL (8) RRR -?-> AAA -?-> BBB (y)DR, (n)DL (y,y)IR, (n,y)IL, (_,n)DL (9) RRR AAA -?-> BBB DL (y)IL, (n)DL Pointer chain legend: - RRR: a root set node or DR block(一个root set或者直接可达的块) - AAA, BBB: heap blocks(堆块) - —>: a start-pointer (头指针) - -?->: an interior-pointer (内部指针) Leak Case legend: - DR: Directly reachable (直接可达) - IR: Indirectly reachable (不直接可达) - DL: Directly lost (直接丢失) - IL: Indirectly lost (不直接丢失) - (y)XY: it’s XY if the interior-pointer is a real pointer (内部指针是一个真实的指针) - (n)XY: it’s XY if the interior-pointer is not a real pointer (内部指针不是一个真实的指针) - (_)XY: it’s XY in either case (任意一个情况) 任意一种情况都可以被归为上述9种情况之一,Valgrind合并其中一些情况,得出4种可能

  • “Still reachable(依然可达)”。 这包含情况 1 和 2 (for the BBB blocks) 。 一个内存块的头指针的或者头指针的链被发现,程序员至少在原理上释放了这块内存在程序退出之前。这是一个非常普遍并且不算是一个问题,Valgrind默认不报告这个问题。

  • “Definitely lost(绝对丢失)”。 这包含情况3 (for the BBB blocks) 。这意味着这个数据块没有指针可达。数据块被归为丢失,因为程序员在程序结束时不能主动释放它,原因是没有指针指向这块内存。 这可能是在较早之前丢失了指向内存区域的指针。

  • “Indirectly lost(非直接丢失)”。这包含情况4和9 (for the BBB blocks)。这意味着数据块丢失不是因为没有指针指向它,而是因为所有的指向数据块的指针自己丢失了。 举例来说,如果你有一个二叉树,根节点丢失,所有的他的子节点都变成非直接丢失。因为根节点的直接丢失问题被解决,子节点的非直接丢失问就会消失。Valgrind默认不报告这个问题。

  • “Possibly lost(可能丢失)”。 这包含情况5、6、7、8 (for the BBB blocks) 。 这意味着一个或多个数据块指针被发现,但是至少一个指针是内部指针。这可能只是一个内存中的随机值,刚好指向一个数据块,所以你不需要考虑这个情况除非你知道你的代码中出现了内部指针。

下面是一个内存泄漏的总结的例子 LEAK SUMMARY: definitely lost: 48 bytes in 3 blocks. indirectly lost: 32 bytes in 2 blocks. possibly lost: 96 bytes in 6 blocks. still reachable: 64 bytes in 4 blocks. suppressed: 0 bytes in 0 blocks. 如果开启的启发式的选项,类似于以下输出 LEAK SUMMARY: definitely lost: 4 bytes in 1 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 0 bytes in 0 blocks still reachable: 95 bytes in 6 blocks of which reachable via heuristic: stdstring : 56 bytes in 2 blocks length64 : 16 bytes in 1 blocks newarray : 7 bytes in 1 blocks multipleinheritance: 8 bytes in 1 blocks suppressed: 0 bytes in 0 blocks 如果 –leak-check=full 被指定, Memcheck 会给出每一个绝对丢失或可能丢失块的详细情况,包括他们在哪里被分配。它不能告诉你何时、如何、为何指向一个泄露内存块的指针丢失了;这个需要自己解决。通常,你需要保证在程序退出时,你的程序没有任何的绝对丢失或者可能丢失的内存块。 例如 8 bytes in 1 blocks are definitely lost in loss record 1 of 14 at 0x……..: malloc (vg_replace_malloc.c:…) by 0x……..: mk (leak-tree.c:11) by 0x……..: main (leak-tree.c:39) 88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 14 at 0x……..: malloc (vg_replace_malloc.c:…) by 0x……..: mk (leak-tree.c:11) by 0x……..: main (leak-tree.c:25) 第一条信息描述了一种简单的情况,一个8byte的内存块绝对丢失了。第二种情况描述了另外一个8byte内存块绝对丢失;不同在于第二种情况会引起在另外内存块中的更多的80bytes内存非直接丢失了。loss number没有任何特殊的含义。这个loss number可以在Valgrind gdbserver中用来列出泄漏内存块的地址,或者给出更多的信息关于为何一个内存块仍然可达。 当 –leak-check=full 被指定时,选项–show-leak-kinds= 控制显示的泄漏类型。 有下面几种类型:

  • 单独指定一或多个: definite indirect possible reachable。
  • all代表所有。
  • none 代表空集合。

例如使用 –show-leak-kinds=definite,possible 来只显示绝对或者可能的内存丢失。

注意事项

在调试php时,因为php自己实现了内存管理机制,所有使用valgrind时,会检测出很多php上的内存泄露,我们可以通过export USE_ZEND_ALLOC=0来让php直接向内存申请内存,这样有助于发现问题

分类:linux, c++

unable to make backup link of `./usr/bin/chattr' before installing new version: Operation not permitted

在公司服务器上使用apt-get upgrade遇到这个问题。通过查阅资料,发现问题的关键在于,chattr需要升级但是chatter无法被删除。 使用:

lsattr /usr/bin/chattr

发现chattr的属性包括i和a,i代表immutable,不可更改,a代表append only,只能增加。这样问题就清楚了,chattr不可更改导致无法升级,至于出现这个情况的原因也不清楚。于是使用chattr更改自己的i和a属性

chattr -i /usr/bin/chattr
chattr -a /usr/bin/chattr

没有任何效果,而且提示我chattr的用法。出现这种提示的原因往往都是用错了指令,我反复确认指令都没有错。 于是在本地机器上验证chattr的属性,发现是没有i和a属性的,而且上述指令也可以正常工作。 实在没有办法,使用sftp将本地的chattr传到服务器上,命名为chattr_new,再用传上去的chattr_new更改chattr的属性

chattr_new -i /usr/bin/chattr
chattr_new -a /usr/bin/chattr

然后在执行apt-get upgrade,没有任何报错。 注: chattr是用来防止误删操作的,即使是root用户,在chattr为文件添加了i属性后,root用户也无法删除。 lsattr则是用来查看文件的这方面的属性的。

分类:linux, 问题

Ubuntu下Docker安装遇到的问题记录

Ubuntu下安装docker的几点问题记录 1.要注意系统的内核和版本是否支持,内核最低要求为3.10,版本最低为12.04,这两点必须同时满足。 使用uname -a查看系统内核 Linux jiang-PC 4.4.0-72-generic #93-Ubuntu SMP Fri Mar 31 14:07:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 这里4.4.0-72则是内核的版本号 使用cat /etc/issue查看系统版本号 Ubuntu 16.04 LTS \n \l 2.注意查看docker的日志记录 使用service docker start(或者systemctl docker start)启动docker时,没有任何输出提示,但是往后执行可能就发现docker没有正常启动,但是又不知道问题出在哪里。/var/log/upstart/docker.log记录了docker启动日志。 3.AppArmor的问题 AppArmor enabled on system but the docker-default profile could not be loaded。出现这个问题时,使用 apt-get install apparmor即可解决 附一段webserver的Dockerfile:

# 这是服务器环境的Docker,会安装好php,nginx等环境,并且进行配置
# author:jiangpengfei
# date: 2017-04-19

FROM ubuntu:16.04
RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common \
    && DEBIAN_FRONTEND=noninteractive apt-add-repository -y ppa:nginx/stable \
    && DEBIAN_FRONTEND=noninteractive apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y php7.0
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y php-redis
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y php-mysql
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y php-imagick \
    && apt-get autoremove \
    && apt-get autoclean
# 上面是从ubuntu的源中安装必要组件,下面开始进行配置
RUN mkdir /var/www/family \
    && mkdir -p /var/log/nginx/access/ \
    && touch /var/log/nginx/access/default.log

COPY family /etc/nginx/sites-enabled 
COPY start.sh /usr/local/bin
EXPOSE 9090 81
CMD ["start.sh"]
分类:linux, 问题