• 关于

    compact函数

    的搜索结果

回答

解决方案1:使用列表理解: input_list = [0, 1, 2, 3, False, ""] output_list = [i for i in input_list if i] output_list [1, 2, 3] 解决方案2:如果你真的需要这个函数,用这个: def compact(word): holder = [] for i in word: if i: holder.append(i) return holder print(compact([0, 1, 2, 3, False, ""])) [1, 2, 3]

kun坤 2019-12-27 17:24:32 0 浏览量 回答数 0

回答

您可以在每个函数定义下调用mainMenu。 """Selection number 1 Input user data into DynamoDB database """ def coach(): dynamodb = boto3.resource("dynamodb") table = dynamodb.Table('coachdata') response = table.scan() pprint.pprint(response, depth=3, compact = True) mainMenu() # <-- here """Selection number 2 Calculate exersises for beginners """ def beginner(): ... 您在定义所有其他功能之前执行mainMenu,因此仅定义了coach。 删除对mainMenu的那些调用,以仅将其保留在末尾: if __name__ == '__main__': mainMenu() 回答来源:stackoverflow

is大龙 2020-03-25 00:21:30 0 浏览量 回答数 0

回答

示例代码:/* @purpose: 使用curl并行处理url @return: array 每个url获取的数据 @param: $urls array url列表 @param: $callback string 需要进行内容处理的回调函数。示例:func(array)*/ function curl($urls = array(), $callback = ''){ $response = array(); if (empty($urls)) { return $response; } $chs = curl_multi_init(); $map = array(); foreach($urls as $url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_NOSIGNAL, true); curl_multi_add_handle($chs, $ch); $map[strval($ch)] = $url; } do{ if (($status = curl_multi_exec($chs, $active)) != CURLM_CALL_MULTI_PERFORM) { if ($status != CURLM_OK) { break; } //如果没有准备就绪,就再次调用curl_multi_exec while ($done = curl_multi_info_read($chs)) { $info = curl_getinfo($done["handle"]); $error = curl_error($done["handle"]); $result = curl_multi_getcontent($done["handle"]); $url = $map[strval($done["handle"])]; $rtn = compact('info', 'error', 'result', 'url'); if (trim($callback)) { $callback($rtn); } $response[$url] = $rtn; curl_multi_remove_handle($chs, $done['handle']); curl_close($done['handle']); //如果仍然有未处理完毕的句柄,那么就select if ($active > 0) { curl_multi_select($chs, 0.5); //此处会导致阻塞大概0.5秒。 } } } } while($active > 0); //还有句柄处理还在进行中 curl_multi_close($chs); return $response; } //使用方法function deal($data){ if ($data["error"] == '') { echo $data["url"]." -- ".$data["info"]["http_code"]."\n"; } else { echo $data["url"]." -- ".$data["error"]."\n"; } }$urls = array();for ($i = 0; $i < 10; $i++) { $urls[] = 'http://www.baidu.com/s?wd=etao_'.$i; $urls[] = 'http://www.so.com/s?q=etao_'.$i; $urls[] = 'http://www.soso.com/q?w=etao_'.$i; }curl($urls, "deal"); 参考资料 php实现并发处理之curl篇

exinnet 2019-12-02 01:32:22 0 浏览量 回答数 0

阿里云高校特惠,助力学生创业梦!0元体验,快速入门云计算!

学生动手场景应用,快速了解并掌握云服务器的各种新奇玩法!

问题

控制器资源功能无法正常工作

云产品优惠 2019-12-01 21:58:51 7 浏览量 回答数 0

回答

1.阻塞与同步2.BIO与NIO对比3.NIO简介4.缓冲区Buffer5.通道Channel6.反应堆7.选择器8.NIO源码分析9.AIO1.阻塞与同步1)阻塞(Block)和非租塞(NonBlock):阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候阻塞:往往需要等待缞冲区中的数据准备好过后才处理其他的事情,否則一直等待在那里。非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回2)同步(Synchronization)和异步(Async)的方式:同步和异步都是基于应用程序私操作系统处理IO事件所采用的方式,比如同步:是应用程序要直接参与IO读写的操作。异步:所有的IO读写交给搡作系统去处理,应用程序只需要等待通知。同步方式在处理IO事件的时候,必须阻塞在某个方法上靣等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式).对于异步来说,所有的IO读写都交给了搡作系统。这个时候,我们可以去做其他的事情,并不拓要去完成真正的IO搡作,当搡作完成IO后.会给我们的应用程序一个通知同步:阻塞到IO事件,阻塞到read成则write。这个时候我们就完全不能做自己的事情,让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大,参考:https://blog.csdn.net/CharJay_Lin/article/details/812598802.BIO与NIO对比block IO与Non-block IO1)区别IO模型 IO NIO方式 从硬盘到内存 从内存到硬盘通信 面向流(乡村公路) 面向缓存(高速公路,多路复用技术)处理 阻塞IO(多线程) 非阻塞IO(反应堆Reactor)触发 无 选择器(轮询机制)2)面向流与面向缓冲Java NIO和IO之间第一个最大的区别是,IO是面向流的.NIO是面向缓冲区的。Java IO面向流意味着毎次从流中读一个成多个字节,直至读取所有字节,它们没有被缓存在任何地方,此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的教据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,霱要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数裾。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。3)阻塞与非阻塞Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。4)选择器(Selector)Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择"通道:这些通里已经有可以处理的褕入,或者选择已准备写入的通道。这选怿机制,使得一个单独的线程很容易来管理多个通道。5)NIO和BIO读取文件BIO读取文件:链接BIO从一个阻塞的流中一行一行的读取数据image | left | 469x426NIO读取文件:链接通道是数据的载体,buffer是存储数据的地方,线程每次从buffer检查数据通知给通道image | left | 559x3946)处理数据的线程数NIO:一个线程管理多个连接BIO:一个线程管理一个连接3.NIO简介在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据,面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,这是一个面向块的I/O系统,系统以块的方式处理处理,每一个操作在一步中产生或者消费一个数据库,按块处理要比按字节处理数据快的多。在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。参考:链接image2.png | center | 851x3834.缓冲区Buffer缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:image3.png | center | 650x3681)其中的四个属性的含义分别如下:容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一个容量在缓冲区创建时被设定,并且永远不能改变。上界(Limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。标记(Mark):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。2)Buffer的常见方法如下所示:flip(): 写模式转换成读模式rewind():将 position 重置为 0 ,一般用于重复读。clear() :compact(): 将未读取的数据拷贝到 buffer 的头部位。mark(): reset():mark 可以标记一个位置, reset 可以重置到该位置。Buffer 常见类型: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、 ShortBuffer 。3)基本操作Buffer基础操作: 链接缓冲区分片,缓冲区分配,直接缓存区,缓存区映射,缓存区只读:链接4)缓冲区存取数据流程存数据时position会++,当停止数据读取的时候调用flip(),此时limit=position,position=0读取数据时position++,一直读取到limitclear() 清空 buffer ,准备再次被写入 (position 变成 0 , limit 变成 capacity) 。5.通道Channel通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。image4.png | center | 368x191在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:image5.png | center | 650x5171)使用NIO读取数据在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:从FileInputStream获取Channel 创建Buffer 将数据从Channel读取到Buffer中 例子:链接 2)使用NIO写入数据使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:从FileInputStream获取Channel 创建Buffer 将数据从Channel写入到Buffer中 例子:链接 6.反应堆1)阻塞IO模型在老的IO包中,serverSocket和socket都是阻塞式的,因此一旦有大规模的并发行为,而每一个访问都会开启一个新线程。这时会有大规模的线程上下文切换操作(因为都在等待,所以资源全都被已有的线程吃掉了),这时无论是等待的线程还是正在处理的线程,响应率都会下降,并且会影响新的线程。image6.png | center | 739x3362)NIOJava NIO是在jdk1.4开始使用的,它既可以说成“新IO”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:1.由一个专门的线程来处理所有的IO事件,并负责分发。2.事件驱动机制:事件到的时候触发,而不是同步的去监视事件。3.线程通讯:线程之间通过wait,notify等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。image7.png | center | 689x251注:每个线程的处理流程大概都是读取数据,解码,计算处理,编码,发送响应。7.选择器传统的 server / client 模式会基于 TPR ( Thread per Request ) .服务器会为每个客户端请求建立一个线程.由该线程单独负贵处理一个客户请求。这种模式带未的一个问题就是线程数是的剧增.大量的线程会增大服务器的开销,大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这又带来了新的问题,如果线程池中有 200 个线程,而有 200 个用户都在进行大文件下载,会导致第 201 个用户的请求无法及时处理,即便第 201 个用户只想请求一个几 KB 大小的页面。传统的 Sorvor / Client 模式如下围所示:image8.png | center | 597x286NIO 中非阻塞IO采用了基于Reactor模式的工作方式,IO调用不会被阻塞,相反是注册感兴趣的特点IO事件,如可读数据到达,新的套接字等等,在发生持定率件时,系统再通知我们。 NlO中实现非阻塞IO的核心设计Selector,Selector就是注册各种IO事件的地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。image9.png | center | 462x408当有读或者写等任何注册的事件发生时,可以从Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。使用NIO中非阻塞IO编写服务器处理程序,有三个步骤1.向Selector对象注册感兴趣的事件2.从Selector中获取感兴趣的事件3.根据不同事件进行相应的处理8.NIO源码分析Selector是NIO的核心epool模型1)SelectorSelector的open()方法:链接2)ServerSocketChannelServerSocketChannel.open() 链接9.AIOAsynchronous IO异步非阻塞IOBIO ServerSocketNIO ServerSocketChannelAIO AsynchronousServerSocketChannel

wangccsy 2019-12-02 01:46:51 0 浏览量 回答数 0

回答

Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。 JVM主要管理两种类型内存:堆和非堆,堆内存(Heap Memory)是在 Java 虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。 堆是Java代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JIT Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。 JVM 内存包含如下几个部分: 堆内存(Heap Memory): 存放Java对象 非堆内存(Non-Heap Memory): 存放类加载信息和其它meta-data 其它(Other): 存放JVM 自身代码等 Java内存分配 Java的内存管理实际上就是变量和对象的管理,其中包括对象的分配和释放。 JVM内存申请过程如下: JVM 会试图为相关Java对象在Eden中初始化一块内存区域 当Eden空间足够时,内存申请结束;否则到下一步 JVM 试图释放在Eden中所有不活跃的对象(这属于1或更高级的垃圾回收),释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区 Survivor区被用来作为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区 当OLD区空间不够时,JVM 会在OLD区进行完全的垃圾收集(0级) 完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory”错误 GC基本原理 GC(Garbage Collection),是JAVA/.NET中的垃圾收集器。 编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显式操作方法。所以,Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。 对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为”不可达的”.GC将负责回收所有”不可达”对象的内存空间。 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”.当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。 为了保证 GC能够在不同平台实现的问题,Java规范对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。 GC分代划分 JVM内存模型中Heap区分两大块,一块是 Young Generation,另一块是Old Generation 在Young Generation中,有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from、to),它们的大小总是一样,它们用来存放每次垃圾回收后存活下来的对象。 在Old Generation中,主要存放应用程序中生命周期长的内存对象。 在Young Generation块中,垃圾回收一般用Copying的算法,速度快。每次GC的时候,存活下来的对象首先由Eden拷贝到某个SurvivorSpace,当Survivor Space空间满了后,剩下的live对象就被直接拷贝到OldGeneration中去。因此,每次GC后,Eden内存块会被清空。 在Old Generation块中,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。 垃圾回收分多级,0级为全部(Full)的垃圾回收,会回收OLD段中的垃圾;1级或以上为部分垃圾回收,只会回收Young中的垃圾,内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。增量式GC 增量式GC(Incremental GC),是GC在JVM中通常是由一个或一组进程来实现的,它本身也和用户程序一样占用heap空间,运行时也占用CPU。 当GC进程运行时,应用程序停止运行。当GC运行时间较长时,用户能够感到Java程序的停顿,另外一方面,如果GC运行时间太短,则可能对象回收率太低. 增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。 Sun JDK提供的HotSpot JVM就能支持增量式GC。HotSpot JVM缺省GC方式为不使用增量GC,为了启动增量GC,我们必须在运行Java程序时增加-Xincgc的参数。 HotSpot JVM增量式GC的实现是采用Train GC算法,它的基本想法就是:将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁高和具有相关性的对象放在一队中,随着程序的运行,不断对组进行调整。当GC运行时,它总是先回收最老的(最近很少访问的)的对象,如果整组都为可回收对象,GC将整组回收。这样,每次GC运行只回收一定比例的不可达对象,保证程序的顺畅运行。 详解函数finalize 更多内容: https://chenhx.blog.csdn.net/article/details/83957456 https://chenhx.blog.csdn.net/article/details/84294481

谙忆 2019-12-02 03:08:20 0 浏览量 回答数 0

回答

一、Java内存分配     Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域存储不同类型的数据,这些区域的内存分配和销毁的时间也不同,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机管理的内存包括五个运行时数据区域,如下图所示:      1、方法区     方法区(Method Area)是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、成员变量信息)、常量、静态变量、以及编译器编译后的代码等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常。     运行时常量池(Runtime Constant Pool)是方法区的一部分,此区域会在两种情况下存储数据。     (1)class文件的常量池中的数据     class文件中的常量池用于存放编译期生成的各种字面值和常量,这部分内容在类被加载后存放到方法区的运行时常量池中。     字面值:private String name="zhangSan";private int age = 23+3;     常量:private final String TAG = "MainActivity";private final int age = 26;     (2)运行期间生成的常量     运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。String str = "abc".intern();当运行时常量池中存在字符串"abc时,将该字符串的引用返回,赋值给str,否则创建字符串"abc",加入运行时常量池中,并返回引用赋值给str。既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。 2、虚拟机栈     虚拟机栈是线程私有的内存空间,每个线程都有一个线程栈,每个方法被执行时都会创建一个栈帧,方法执行完成,栈帧弹出,线程运行结束,线程栈被回收。虚拟机栈就是Java中的方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,这个栈帧用于存储局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表用来存储方法中的局部变量,包括方法中声明的变量以及函数形参。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小,并且在程序执行期间局部变量表的大小是不会改变的。程序中的所有计算过程都是在借助于操作数栈来完成的。指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向当前方法所属的类的运行时常量池。方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。     在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。 3、本地方法栈     本地方法栈也是线程私有的内存空间,本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法,有的虚拟机直接把本地方法栈和虚拟机栈合二为一。 4、堆     Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,在实现上,既可以实现固定大小的,也可以是扩展的。如果堆中没有足够的内存分配给实例,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常。     堆是运行时动态分配内存,对象在没有引用变量指向它的时候,才变成垃圾,但是仍然占着内存,在程序空闲的时候(没有工作线程运行,GC线程优先级最低)或者堆内存不足的时候(GC线程被触发),被垃圾回收器释放掉,由于要在运行时动态分配内存,存取速度较慢。 5、程序计数器     程序计数器的作用可以看做是当前线程所执行的字节码的行号指示。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空。 二、Java内存回收     对于虚拟机栈空间,当方法调用结束后,基本类型变量、引用类型变量、形参占据的空间会被自动释放,但引用类型指向的对象在堆中,堆中的无用内存由垃圾回收线程回收,GC线程优先级最低,只有当没有工作线程存在时GC线程才会执行,或者堆空间不足时会自动触发GC线程工作。除了回收内存,GC线程还负责整理堆中的碎片。 1、四种引用类型     Java中的对象引用分为四种,强引用类型、软引用类型、弱引用类型、虚引用类型。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。使用软引用和弱引用可以有效的避免oom。软引用关联的对象,只有软引用关联时,才可回收,如果有强引用同时关联,不会回收对象占用的内存,弱引用也如此。 (1)强引用     强引用是使用最普遍的引用,类似Object obj = new Object()、String str = "hello"。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 (2)软引用(SoftReference)     软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示,如果内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用通常用于网页缓存、图片缓存,防止内存溢出,在内存充足的时候,缓存对象会一直存在,在内存不足的时候,缓存对象占用的内存会被垃圾收集器回收。使用示例: public void testSoftReference() { Map<String,SoftReference<Bitmap>> imagesCache = new HashMap<String,SoftReference<Bitmap>>(); Bitmap bitmap = getBitmap(); SoftReference<Bitmap> image1 = new SoftReference<Bitmap>(bitmap); imagesCache.put("image1",image1); SoftReference<Bitmap> result_SoftReference = imagesCache.get("image1"); Bitmap result_Bitmap = result_SoftReference .get(); } import java.lang.ref.SoftReference; public class Main { public static void main(String[] args) { SoftReference<String> sr = new SoftReference<String>(new String("hello")); System.out.println(sr.get()); } } (3)弱引用(WeakReference)     弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,在java中用java.lang.ref.WeakReference类来表示。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,不过由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以用于:单例类持有一个activity引用时,会造成内存泄露,把activity声明为弱引用,在activity销毁后,垃圾收集器扫描到activity对象时,会回收activity对象的内存。使用示例: public class SingleTon1 { private static final SingleTon1 mInstance = null; private WeakReference<Context> mContext; private SingleTon1(WeakReference<Context> context) { mContext = context; } public static SingleTon1 getInstance(WeakReference<Context> context) { if (mInstance == null) { synchronized (SingleTon1.class) { if (mInstance == null) { mInstance = new SingleTon1(context); } } } return mInstance; } } public class MyActivity extents Activity { public void onCreate (Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); SingleTon1 singleTon1 = SingleTon1.getInstance(new WeakReference<Context>(this)); } }import java.lang.ref.WeakReference; public class Main { public static void main(String[] args) { WeakReference<String> sr = new WeakReference<String>(new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); } } 输出结果: hellonull     第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。 (4)虚引用     虚引用和软引用、弱引用不同,它并不影响对象的生命周期,也无法通过虚引用来取得一个对象实例,在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列(ReferenceQueue)联合使用,如下: import java.lang.ref.PhantomReference;import java.lang.ref.ReferenceQueue; public class Main { public static void main(String[] args) { ReferenceQueue<String> queue = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue); System.out.println(pr.get()); } } 2、垃圾回收算法 (1)标记-清除(Mark-Sweep)    标记-清除(Mark-Sweep)算法,分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。 标记-清除算法主要问题是:1、效率问题,标记和清除过程的效率很低2、空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集 (2)复制(Copying)算法     复制算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。 复制算法的主要问题是:1、复制算法将内存缩小为原来的一半,过于浪费2、对象存活率较高时就要执行较多的复制操作,造成频繁GC,效率将会变低 (3)标记-整理(Mark-Compact)     标记-整理算法的标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样连续的内存空间就比较多了。     如上图所示,所有存活的对象依次向左上角移动,(0,4)移动到(0,2),(1,0)移动到(0,3),依次类推,当所有的存活对象移动完成后,把剩余的所有空间清空,也就是清空(1,1)后的所有空间。 (4)分代回收(generational collection) 程序创建的大部分对象的生命周期都很短,只有一小部分对象的生命周期比较长,根据这样的规律,一般把Java堆分为Young Generation(新生代),Old Generation(老年代)和Permanent Generation(持久代),上面几种算法是通过分代回收混合在一起的,这样就可以根据各个年代的特点采用最适当的回收算法。 (1)新生代     在新生代中,有一个叫Eden Space的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from、to), 这两个区域大小相等,相当于copying算法中的两个区域,它们用来存放每次垃圾回收后存活下来的对象。在新生代中,垃圾回收一般用Copying的算法,速度快。     当新建对象无法放入eden区时,将触发minor collection(minorGC 是清理新生代的GC线程,eden的清理,from、to的清理都由MinorGC完成),将eden区与from区的存活对象复制到to区,经过一次垃圾回收,eden区和from区清空,to区中则紧密的存放着存活对象;当eden区再次满时,minor collection将eden区和to区的存活对象复制到from区,eden区和to区被清空,from区存放eden区和to区的存活对象,就这样from区和to区来回切换。如果进行minor collection的时候,发现to区放不下,则将eden区和from区的部分对象放入成熟代。另一方面,即使to区没有满,JVM依然会移动世代足够久远的对象到成熟代。 (2)成熟代     在成熟代中主要存放应用程序中生命周期长的内存对象,垃圾回收一般用mark-compact的算法,速度慢些,但减少内存要求。如果成熟代放满对象,无法从新生代移入新的对象,那么将触发major collection(major GC清理整合OldGen的内存空间)。 (3)永久代    在永久代中,主要用来放JVM自己的反射对象,比如类对象、方法对象、成员变量对象、构造方法对象等。     此外,垃圾回收一般是在程序空闲的时候(没有工作线程,GC线程优先级较低)或者堆内存不足的时候自动触发,也可以调用System.gc()主动的通知Java虚拟机进行垃圾回收,但这只是个建议,Java虚拟机不一定马上执行,启动时机的选择由JVM决定,并且取决于堆内存中Eden区是否可用 作者:喜六六 来源:CSDN 原文:https://blog.csdn.net/qq_29078329/article/details/78929457 版权声明:本文为博主原创文章,转载请附上博文链接!

auto_answer 2019-12-02 01:50:42 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站 阿里云双十一主会场 阿里云双十一新人会场 1024程序员加油包 阿里云双十一拼团会场 场景化解决方案 阿里云双十一直播大厅