今天分享一个高频面试题:深拷贝和浅拷贝和copy-on-write假设B拷贝了A,当A被修改时,看是否B会改变。如果B也发生了变化,说明这是一个浅拷贝;如果B没有改变,它就是深拷贝。1、浅拷贝:直接将原对象的引用赋值给新对象,新对象只是对原对象的引用。2、深拷贝:新建对象和数组,复制原对象属性的“值”(数组的所有元素),是“值”而不是“引用”。浅拷贝只是指针的拷贝。拷贝后,两个指针指向同一个内存空间。深拷贝不仅复制了指针,还复制了指针指向的内容。深拷贝后的指针指向两个不同的地址。.3.Copy-on-write技术:最初产生于Unix系统,用于实现傻瓜式的进程创建:当fork()系统调用发出时,内核将父进程的整个地址空间复制为它是并将复制的分配给子进程。这种行为非常耗时,因为它需要:为子页表分配页为子页分配页初始化子页表将父页复制到子页对应页这种创建地址空间的方法,涉及很多内存访问,消耗许多CPU周期,并完全破坏缓存内容。在大多数情况下,这通常是没有意义的,因为许多子进程通过加载新程序开始执行,从而完全丢弃继承的地址空间。今天的Unix内核(包括Linux)使用一种更有效的方法,称为写时复制(或COW)。这个想法相当简单:父进程和子进程共享页面而不是复制它们。但是,只要页面是共享的,就不能修改它们。每当父进程和子进程尝试写入共享页面时,就会发生错误,内核会将页面复制到新页面并将其标记为可写。原始页仍处于写保护状态:当其他进程试图写入它时,内核会检查写入进程是否是该页的唯一所有者;如果是这样,它将页面标记为可由该进程写入。Linux的fork()使用copy-on-write传统的fork()系统调用直接将所有资源复制到新创建的进程中。此实现简单且低效,因为它复制的数据可能会被共享。更糟糕的是,如果新进程打算立即执行新图像,则所有先前的副本都将丢失。Linux的fork()是使用写时复制页面实现的。写时复制是一种可以延迟甚至避免复制数据的技术。内核此时并没有复制整个进程的地址空间,而是让父子进程共享同一个地址空间。地址空间只有在需要写入的时候才被复制,这样每个进程都有自己的地址空间。也就是说,资源复制只在需要写的时候进行,在此之前,只能以只读的方式进行共享。该技术将地址空间中页面的复制推迟到实际写入发生时。在页面根本不会被写入的情况下——例如,exec()紧接在fork()之后,地址空间不需要被复制。fork()的实际成本是复制父进程的页表并为子进程创建进程描述符。一般情况下,进程创建后,会立即运行一个可执行文件。这种优化可以避免复制大量根本不会使用的数据(地址空间通常包含数十兆字节的数据)。这种优化很重要,因为Unix强调进程快速执行的能力。初识COW技术:在Linux程序中,fork()会生成一个与父进程一模一样的子进程,但是子进程之后往往会调用exec系统。出于效率的考虑,Linux引入了“copy-on-write”技术,即只有当进程空间各段内容发生变化时,才会将父进程的内容复制到子进程中。那么子进程的物理空间中没有代码,如何取指令执行exec系统调用呢?fork之后exec之前,两个进程使用同一个物理空间(内存区),子进程的代码段、数据段、栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但对应的物理空间是相同的。当父子进程发生改变对应段的行为时,再为子进程的对应段分配物理空间。如果不是因为exec,内核会为子进程的data段和stack段分配相应的物理空间(至此两者有独立的进程空间,相互独立),而code段继续共享父进程的物理空间(两者的代码完全一样)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配一个单独的物理空间。还有一个细节问题,是在网上看到的。fork之后,内核会将子进程放在队列前面让子进程先执行,从而防止父进程执行copy-on-write,然后子进程执行exec系统调用,所以无意义的重复导致效率下降。
