精华内容
下载资源
问答
  • 1. map过程产生大量对象导致内存溢出 这种溢出的原因是在单个map中产生了大量的对象导致的。 例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这...

    1. map过程产生大量对象导致内存溢出

    这种溢出的原因是在单个map中产生了大量的对象导致的。

    例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这肯定很容易产生内存溢出的问题。针对这种问题,在不增加内存的情况下,可以通过减少每个Task的大小,以便达到每个Task即使产生大量的对象Executor的内存也能够装得下。具体做法可以在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。例如:rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)。

    面对这种问题注意,不能使用rdd.coalesce方法,这个方法只能减少分区,不能增加分区,不会有shuffle的过程。

    2.数据不平衡导致内存溢出

    数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题,解决方法和上面说的类似,就是调用repartition重新分区。这里就不再累赘了。

    3.coalesce调用导致内存溢出

    这是我最近才遇到的一个问题,因为hdfs中不适合存小问题,所以Spark计算后如果产生的文件太小,我们会调用coalesce合并文件再存入hdfs中。但是这会导致一个问题,例如在coalesce之前有100个文件,这也意味着能够有100个Task,现在调用coalesce(10),最后只产生10个文件,因为coalesce并不是shuffle操作,这意味着coalesce并不是按照我原本想的那样先执行100个Task,再将Task的执行结果合并成10个,而是从头到位只有10个Task在执行,原本100个文件是分开执行的,现在每个Task同时一次读取10个文件,使用的内存是原来的10倍,这导致了OOM。解决这个问题的方法是令程序按照我们想的先执行100个Task再将结果合并成10个文件,这个问题同样可以通过repartition解决,调用repartition(10),因为这就有一个shuffle的过程,shuffle前后是两个Stage,一个100个分区,一个是10个分区,就能按照我们的想法执行。

    4.shuffle后内存溢出

    shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的。在Spark中,join,reduceByKey这一类型的过程,都会有shuffle的过程,在shuffle的使用,需要传入一个partitioner,大部分Spark中的shuffle操作,默认的partitioner都是HashPatitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions) , spark.default.parallelism参数只对HashPartitioner有效,所以如果是别的Partitioner或者自己实现的Partitioner就不能使用spark.default.parallelism这个参数来控制shuffle的并发量了。如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量。

    5. standalone模式下资源分配不均匀导致内存溢出

    在standalone的模式下如果配置了–total-executor-cores 和 –executor-memory 这两个参数,但是没有配置–executor-cores这个参数的话,就有可能导致,每个Executor的memory是一样的,但是cores的数量不同,那么在cores数量多的Executor中,由于能够同时执行多个Task,就容易导致内存溢出的情况。这种情况的解决方法就是同时配置–executor-cores或者spark.executor.cores参数,确保Executor资源分配均匀。

    6.在RDD中,共用对象能够减少OOM的情况

    这个比较特殊,这里说记录一下,遇到过一种情况,类似这样rdd.flatMap(x=>for(i <- 1 to 1000) yield (“key”,”value”))导致OOM,但是在同样的情况下,使用rdd.flatMap(x=>for(i <- 1 to 1000) yield “key”+”value”)就不会有OOM的问题,这是因为每次(“key”,”value”)都产生一个Tuple对象,而”key”+”value”,不管多少个,都只有一个对象,指向常量池。具体测试如下:

     

    这个例子说明(“key”,”value”)和(“key”,”value”)在内存中是存在不同位置的,也就是存了两份,但是”key”+”value”虽然出现了两次,但是只存了一份,在同一个地址,这用到了JVM常量池的知识.于是乎,如果RDD中有大量的重复数据,或者Array中需要存大量重复数据的时候我们都可以将重复数据转化为String,能够有效的减少内存使用.

     

    展开全文
  • 操作系统采用哪些方法提高内存利用率 从操作系统的需求开始说起 要想搞清楚操作系统采用哪些方法来提高内存利用率,首先,我们应该明白操作系统为什么需要提高内存的利用率。 计算机的系统资源分为处理机、存储器、I...

    操作系统采用哪些方法提高内存利用率

    从操作系统的需求开始说起

    要想搞清楚操作系统采用哪些方法来提高内存利用率,首先,我们应该明白操作系统为什么需要提高内存的利用率。

    计算机的系统资源分为处理机、存储器、I/O设备以及文件(数据和程序),相应地,操作系统的主要功能也是对这四类资源的有效管理。

    存储器作为计算机系统的重要组成部分,虽然容量一直在随着计算机技术的发展而不断扩大,但仍不能够满足现代软件发展的需要。它仍然是一种宝贵而又稀缺的资源。因此,如何对存储器进行有效的管理对系统的性能有重大的影响

    操作系统进行存储器管理的主要任务,是为多道程序的运行提供良好的运行环境提高存储器的利用率方便用户使用,并能从逻辑上扩充内存。而用户程序需要在系统中运行,必须先将它装入内存,然后再经过编译、链接、装入将其转变为一个可以执行的程序。

    至此,可以说操作系统对存储器的管理至关重要,其中最需要关注的问题是对内存的分配和回收

    速度和容量,我们永恒的目标

    在计算机执行时,几乎每一条指令都涉及对存储器的访问,因此要求对存储器的访问速度跟得上处理机的运行速度。或者说,存储器的速度必须非常快,能与处理机的速度相匹配,否则会明显地影响到处理机的运行。此外还要求存储器具有非常大的容量,而且存储器的价格还应该很便宜,可靠性好等。单个存储器件无法同时满足,所以现代计算机中采用了多层结构的存储器系统。

    存储器的分配、回收以及对存储层次间数据移动的管理都会影响到存储器的利用效率。在操作系统的层面,我们主要考虑如何提高对存储器的访问速度,并从逻辑上扩充其容量。

    因此我们对存储器的目标是,提高其存取速度,使之与CPU的取指速度相匹配;逻辑扩充其容量,使之能装下当前运行的程序和数据

    将程序装入内存时遇到的问题

    早期的操作系统的做法是为一个用户程序分配一个连续的内存空间,即程序中代码或数据的逻辑地址相邻,体现在内存空间分配时物理地址的相邻。为了适用于多道程序的运行环境,程序的装入和链接需要在真正执行时执行。

    最初的固定分区分配,是将整个用户空间划分为若干个固定大小的区域,在每个分区中之只装入一道作业。这样必然会造成存储空间的很大浪费。我们更希望根据进程的实际需要动态分配内存空间需要分配内存时,在空闲分区中按请求的大小划分出一块内存空间;在内存回收时再将分区释放,并插回空闲分区

    为了能够提高内存利用率,进一步提升系统性能,现行有很多动态分区分配的算法。主要分为基于顺序搜索和基于索引搜索两种。

    基于顺序搜索的动态分区分配算法,是指一次搜索空闲分区链上的空闲分区。可以按照顺序搜索时的分区策略分为首次适应算法(优先利用低址部分的空闲分区)、循环首次适应算法(从上次找到的空闲分区开始查找的首次适应算法,均匀利用空闲分区)、最佳适应算法(优先利用满足要求的最小的空闲分区)和最坏适应算法(优先利用最大的空闲分区)。

    但是,不管采用什么样的优先级,剩余的不连续小空间都无法被很好的利用。并且,对大内存的系统来说,顺序搜索每次的查找操作的开销较大,需要进一步搜索空闲分区的速度。由此,我们考虑采用基于索引搜索的动态分区分配算法

    如果考虑将相同容量的空闲分区放在同一类,并设立管理索引表进行管理,每次只需根据进程的长度查找索引,便可以很大程度上减少查找的开销。这就是快速适应算法。但是,由于设立额外的索引表需要占用空间,所以快速适应算法的时间性能虽然优于顺序搜索算法,但是它的空间性能与顺序搜索相比较差。

    进一步进行算法优化,在伙伴系统算法中,我们规定分配分区和空闲分区的大小都为2k(1≤k≤m,内存空间的大小为2m)。需要分配2i大小的空间时,优先找到大于当前要求的最小空闲分区2i+1,如果没有,则找大小为2i+2的空闲分区,以此类推,在找到后对其进行分割。需要回收2i大小的空间时,将其与大小相同的2i的空闲分区进行合并,并将合并后的2i+1的空闲分区与和其大小相同的空闲分区进行合并,以此类推。由于需要对分区进行分隔和合并操作,其时间性能优于顺序搜索,但差于快速适应算法。在空间上,由于提高了空闲分区的可使用率,其空间性能优于快速适应算法,但差于顺序搜索算法。

    但是使用分类的方法仍会需要较大的空间开销,直到哈希算法的出现才解决了这一问题。以空闲分区的大小为关键字建立哈希函数,构造一张能够记录空闲分区链表表头的哈希表。这样一来,既可以优化时间性能,又可以避免设立索引表的空间开销。能够较好地提高内存的利用率。

    但是无论是顺序搜索还是索引搜索,以上的算法仍然遇到了一个共同的问题——“碎片”问题内存空间会被分割为许多难以利用的、很小的空闲分区由于小分区并不连续,即便其容量总和大于要装入的程序仍无法利用。这样的小分区被称为“碎片”。

    如何解决碎片问题对内存分配和回收至关重要。一种解决想法是,在产生的“碎片”之后再次对其进行重新利用。将内存中的所有作业进行移动,使之相邻接,把原来分赛的多个空闲小分区拼接成大的分区,这就是动态可重定位分区分配算法。要实现对地址的修改,必须有硬件的支持,即设置重定位寄存器,并且算法必须具有使已使用的内存空间“紧凑”的功能。这样便会大大地影响到系统的效率。同时,考虑到用户程序共享程序和数据的需要,动态重定位的算法仍会造成许多内存空间的浪费。

    为了能够进一步提高内存的利用率,彻底解决“碎片”问题,考虑打破为进程分配连续内存空间的连续分配方式,而将进程分散放入各个不相邻的分区中。这就是离散化的分配方式

    离散化的分配方式

    离散化分配空间,是指将用户程序的地址空间分成若干个区域,分别进行内存的分配和回收。在划分区域时,不同的划分方式。为了提高内存利用率,我们首先想到的是分页存储管理

    分页存储管理,是将用户程序的逻辑地址空间分为若干个固定大小的区域,这些区域称为 “页面”, 简称为 “页”,相应地,将内存物理地址空间也分为若干个固定大小的 “物理块”,简称为 “块”。在为进程分配内存时,将进程的若干页以块为单位装入内存中

    为了能够在内存中找到每个页面所对应的物理块,需要为进程创建一张页面映像表,简称为页表。页表的表项记录相应页在内存中对应的物理块号

    程序逻辑地址由页号和页内地址组成内存中的物理地址由块号和块内地址组成。这样就可以通过查找页表完成从逻辑地址到内存中物理地址的转换。

    在进程运行期间,每次存取一个数据都需要进行地址变换,这都会进行两次的内存访问操作(一次访问页表,一次访问物理地址)。反复进行地址变换造成的时间开销很大。为了提高地址变换的速度,可在地址变换机构中增加一个“联想寄存器”,也叫做 “快表”。对快表进行存取的速度快于内存,在快表中存放较常访问到的页表表项,并在查找地址时优先访问快表,可以极大地缩短地址变换的时间开销。

    现代计算机系统的逻辑地址空间多为32位或64位,其页表所占空间也非常大,需要占用相当大的内存空间。这时我们考虑再次对页表进行离散化的分配方式

    为页表再次进行分页,并建立 “外层页表”进行存储。外层页表的表项记录页表分页的首地址。可以通过外层页表和页表共同实现从逻辑地址到内存中物理地址的转换。这时程序逻辑地址由外层也好,外层页内地址和页内地址组成

    相似地,可以将两级分页拓展到多级的离散化分页。目前在64位计算机中,多数使用三级页表结构。

    随着操作系统的不断发展,为了能够满足程序员用户在使用上的要求,并且反映了程序本身的逻辑结构,又引入了分段存储管理的离散化分配方式。虽然其目的不是直接提高内存的利用率,但由于分段存储管理便于实现、分段可共享、易于保护、可动态连接等优点,采用分段存储管理方式也有利于高效管理内存空间。

    分段存储管理,是将用户程序的逻辑地址空间分为若干个大小不同的区域,这些区域称为 “段”。每一个段是一个相对独立的逻辑单位。需要创建段映射表,简称为段表。段表的表项记录相应段在内存中对应的物理块号。分段存储的程序逻辑地址由段号和段内地址组成。也可以设置一个“联想寄存器”缩短对分段式存储的地址变换的时间开销。由于段比页大,段表项的数目比页表项多,所需要的联想寄存器较小,可以显著减少存取数据的时间。

    分页系统以页面为内存分配的基本单位,能有效地提高内存利用率;分段系统作为内存分配的基本单位,能够更好地满足用户多方面的需要。如果能够将两种存储方式结合,则可形成一种新的存储器管理方式——段页式存储管理方式

    段页式系统的基本原理是分段和分页原理的结合,即先将用户程序分成若干个段,再把每个段分成若干个页。为了实现从逻辑地址到物理地址的变换,系统需要同时配置段表和页表。段表的内容是段号,页表大小和页表始址,同时,为段表和页表都创建了一个状态位。段页式系统的逻辑地址由段号,段内页号和页内地址组成

    由于在访问内存时需要经过访问段表,访问页表,访问物理地址共三次内存访问操作,将访问内存的次数大大增加。为了提高执行速度,需要在地址变换机构中需要增设一个快速联想寄存器。每次访问它时,同时利用段号和页号检索寄存器,其基本原理与分页及分段的情况相似。

    由覆盖到对换的虚拟存储

    存储器性能的提升分为存取速度和存储容量两个方面。随操作系统的发展,虽然存取速度已经基本能与CPU持平,但处理空间很大的作业时,其存储容量仍然有限。存储管理从连续分配方式发展到离散分配方式,是通过改变作业大小提供解决方案。更直观的想法是增加内存的容量。

    在物理上增加内存容量往往会增加系统的成本,并且很难达到理想的容量大小。为了解决内存空间不足的问题,进一步提高内存利用率,早期的操作系统采用了基于覆盖技术的存储管理。

    覆盖技术的基本思想是,对同一个进程进行逻辑上互斥划分,分为各个覆盖相互独立的覆盖可以不同时被调入内存运行

    虽然能够将较大的作业装入内存运行,但是,由于覆盖程序段的最大程度仍然受到内存容量大小的限制,覆盖技术仍然有较大的局限。

    如果考虑把暂时不用的程序进程全部由内存移到外存中,在需要时再将其调入内存,便可以更大程度上解决内存空间不足的问题。这就是对换技术。对换技术的出现打破了一个程序一旦进入内存便必须一直运行直到结束的限制,能够提高内存的利用率。但是,由于对换技术换入换出内存的基本单位是整个进程,一个进程的大小仍然收到内存容量大小的限制。

    虚拟存储器技术作为操作系统存储器管理的一项重要技术,它能够从逻辑上扩充内存容量解决这一问题。它允许一个作业部分装入内存就可以运行,并且支持多个用户程序并发执行

    在虚拟存储技术中,由于换入换出是以页或者段为基础单位的,程序的大小不再受到内存容量的限制,只受到CPU地质结构的限制。对请求分页的虚拟存储器技术来说,只要采用适当的调页策略,选择合适的页面置换算法,进行合理的内存分配策略,便能大大地提升系统的性能。请求分段的虚拟存储技术也与其类似。

    总的来说,系统提高内训利用率的方法,主要有离散化内存分配方式和虚拟存储器技术两种。一种是将进程作业进行划分,减少对内存空间的浪费;一种是在逻辑上扩充容量,使得作业能够避免内存空间的限制。同时,设置高速的联想寄存器,能够有效节省访问内存的时间,提高存储器的存取速度。从操作系统的发展可以看出,内存利用率的提升离不开硬件的支持和合适的算法选择,为了提高内存的利用率,每一种硬件和算法的选择都需要开发人员的精心设计和细心实现。

    在学习操作系统的设计原理的过程中,不仅仅要看到设计的原因,更要看到每一种设计的思维脉络,体会其中的思想。并且内化这样的思维方式,将其运用在代码开发中去。这才是我们学习操作系统的目的。

    展开全文
  • static 关键字 1 ...两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。另一种情形是,希望某个方法不与包含它的类的...

    我的问题

            之前写 Dialog 除了自定义炫酷弹窗时独立写个类,其他情况基本都是用默认样式的 Dialog,因为直接调用系统 API 比较方便,也可以避免内存泄漏问题。但是在重构优化公司项目时,发现之前有个仁兄搞了个 Dialog 的单例类,我就瞬间无语了,说好的防止内存泄漏呢?!然后就想另起个工具类来替代【总觉得怪怪的 ~~】。因为项目的普通弹窗很多,且这些弹窗对于需要的按钮数量不同,点击按钮后也不需要后续操作,所以之前的哥们就懒,抽了这么一个单例。到我这了,改一下吧,样式是特定的,那就改成工具类写静态方法吧。那么问题来了,那时突然有点蒙了,静态变量可能会引起内存泄漏,静态方法会不会引起内存泄漏呢?!其实这是不会的。
            每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 1

            方法体的局部变量(其中包括基础数据类型、对象的引用)会在栈区创建空间,并在方法执行结束后自动释放变量的空间和内存。也就是说,不管是静态方法还是非静态方法的局部变量都会在使用后等待被回收。而在这里讨论的可能会出现的内存泄漏问题都是静态变量引起的:我们知道,静态方法只能使用静态的全局变量,不能使用非静态的全局变量,如果在静态方法中使用了静态的全局变量,而这个静态变量又持有经静态方法传入的参数对象的引用,就有可能引起内存泄漏。说到底都是静态变量的锅!

    静态方法和非静态方法 2

    1、在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载,调用的速度基本上没有差别。

    2、方法占不占用更多内存,和它是不是 static 没什么关系。因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以 每个实例对象的所有字段都会在内存中有一分拷贝,也因为这样才能用它们来区分现在操作的是哪个对象。但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是 static 还是 non-static 的方法,都只存在一份代码,也就是只占用一份内存空间。 同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用 class 的成员变量的值。

    3、大家都以为,实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单,事实上 如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以,所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。

    4、从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据该方法和实例化对象是否具有逻辑上的相关性,如果是就应该使用实例化对象,反之使用静态方法。这只是从面向对象角度上来说的。如果从线程安全、性能、兼容性上来看,也是选用实例化方法为宜。

    5、我们为什么要把方法区分为:静态方法和实例化方法?如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是 “静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建 c++、java、c# 这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。

    大家对这个问题都有一个共识:那就是实例化方法更多被使用比较稳妥,静态方法少使用。

    static 关键字 3

            通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用 new 创建那个类的对象,否则,实际上并未获得任何对象。执行 new 来创建对象时,数据存储空间才被分配,其方法才供外界调用。
            有两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用这个方法
            通过 static 关键字可以满足这两方面的需要。当声明一个事物是 static 时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其 static 方法或访问其 static 域。通常,你必须创建一个对象,并用它来访问数据或方法。因此非 static 域和方法必须知道它们一起运作的特定对象。

    当然,由于在用 static 方法前不需要创建任何对象,所以对于 static 方法,不能简单的通过调用其他非 static 域或方法而没有指定某个命名对象,来直接访问非 static 域或方法(因为非 static 域或方法必须与某一特定对象关联)

            有些面向对象语言采用类数据 class data 和类方法 class methods 两个术语,代表那些数据和方法只是作为整个类,而不是类的某个特定对象而存在的。有时,一些 Java 文献里也有用到这两个术语 。
            只须将 static 关键字放在定义之前,就可以将字段或方法设定为 static。例如,下面的代买就生成了一个 static 字段,并对其进行了初始化:

    class StaticTest {
    	static int i = 47;
    }
    

            现在,即使你创建了两个 StaticTest 对象,StaticTest.i 也只有一份存储空间,这两个对象共享同一个 i。再看看下面代码:

    StaticTest st1 = new StaticTest();
    StaticTest st2 = new StaticTest();
    

            在这里,st1.i 和 st2.i 指向同一个存储空间,因此它们具有相同的值 47.
            引用 static 变量有两种方法。如前例所示,可以通过一个对象去定位它,比如 st2.i;也可以通过其类名直接引用,而这对于非静态成员则不行。

    StaticTest.i++;
    

            其中,++ 运算符对变量进行递加操作。此时,st1.i 和 st2.i 仍具有相同的值 48.
            使用类名是引用 static 变量的首选方式,这不仅是因为它强调了变量的 static 结构,而且在某些情况下它还为编译器进行优化提供了更好的机会。
            类似逻辑也应用于静态方法。既可以像其他方法一样,通过一个对象来引用某个静态方法,也可以通过特殊的语法形式 ClassName.method() 加以引用。定义静态方法的方式也与定义静态变量的方式相似:

    class Incrementable {
    	static void increment() {
    		StaticTest.i++;
    	}
    }
    

            可以看到,Incrementable 的 increment() 方法通过 ++ 运算符将静态数据 i 递加。可以采取典型的方式,通过对象来调用 increment():

    Incrementable sf = new Incrementable();
    sf.increment();
    

            或者因为 increment() 是一个静态方法,所以也可以通过它的类直接调用:

    Incrementable .increment();
    

            尽管当 static 作用于某个字段时,肯定会改变数据创建的方式(因为一个 static 字段对每个类来说都只有一份存储空间,而非 static 字段则是对每个对象有一个存储空间),但是 如果 static 作用于某个方法,差别却没有那么大。static 方法的一个重要用法就是在不创建任何对象的前提下就可以调用它。正如我们将会看到的那样,这一点对定义 main() 方法很重要,这个方法是运行应用时的入口点。
            和其他任何方法一样,static 方法可以创建或使用与其类型相同的被命名对象,因此,static 方法常常拿来做“牧羊人”的角色,负责看护与其隶属同一类型的实例群。


    1. 《深入理解 Java 虚拟机》【P39】 ↩︎

    2. Java内存的一点理解, 静态方法和实例方法的区别及使用场景↩︎

    3. 《Thinking inJava 中文第 4 版》【P29】 ↩︎

    展开全文
  • 注意:变量指基本数据类型非对象,局部变量不能被静态修饰 1、(静态)成员变量存放在data segment区(数据区),字符串常量也存放在该区 2、非静态变量,new出来的对象存放在堆内存,所有局部变量和引用地址存放在栈...

    注意:变量指基本数据类型非对象,局部变量不能被静态修饰
    1、(静态)成员变量存放在data segment区(数据区),字符串常量也存放在该区
    2、非静态变量,new出来的对象存放在堆内存,所有局部变量和引用地址存放在栈内存
    3、方法(包括静态方法)存放在code segment(方法块),存放代码片段,且只存放一次

    这里写图片描述

    public class Cat {
        //private static int sid = 0;
        private int sid = 0;
        private String name; 
        int id;
        Cat(String name) {
            this.name = name;  
            id = sid++;
        }
        public void info(){
            System.out.println
                   ("My name is "+name+" No."+id);
        }
        public static void main(String arg[]){
    
            //Cat.sid = 100;
            Cat mimi = new Cat("mimi");
            mimi.sid = 2000;
            Cat pipi = new Cat("pipi");
            mimi.info(); 
            pipi.info();
        }
    }

    这里写图片描述

    静态方法和实例方法区别及使用场景

    从实际项目开发说起,这里有开发项目的三种方式:
    开发项目中把BLL和DAL分开,在BLL调用DAL的代码。
    一、在DAL中使用静态方法,不创建实例直接调用(大概有很多人都使用这种方式开发 )
    class DAL
    {
    public static string GetUserName(…);
    }
    在BLL中调用:
    DAL.GetUserName();
    二、在DAL中使用实例方法,采用静态成员模式(或Singleton)通过实例来调用:
    class DAL
    {
    public static readonly DAL dal = new DAL();
    public string GetUserName(…);
    }
    在BLL中调用:
    DAL.dal.GetUserName();
    三、在DAL中使用实例方法,每次调用之前先创建DAL对象的实例:
    class DAL
    {
    public string GetUserName(…);
    }
    在BLL中调用:
    DAL dal = new DAL();
    dal.GetUserName();

    开发方式一:我以为在一些情况下(比如 调用多个数据库、GetUserName(…)内部处理操作部分)会出现线程安全的嫌疑。这种开发方式不用New出对象,所以很普遍。
    开发方式二:应该多是应用在cs模式下,DAL在整个项目只会有一个对象存在,如果出现在B/S 我想不能兼容多种问题情况。而且也有线程安全的问题。
    开发方式三:应该是普遍使用的,能够兼容各种问题,也不会有线程不安全的嫌疑出现。

    特别说明一下:在MS的pet3.0以前的版本 都采用了方式二,而到pet3.0和以后的版本 都采用了方式三,而且特别在开发说明文档中明确的解释了一下。我想应该是从兼容性上考虑的,从性能上方式二并不比方式三真正的高多少。

    我特意以“你怎么理解并使用静态方法和实例化方法的?”这样的问题询问了多位程序员,他们开发的语言也不尽相同(c 、c++、java、c#)
    以下是他们的回答:
    海龙说:
    公用的方法,而且是一些零散的 一般用静态方法
    张伟说:
    几乎没有区别,如果不需要实例化,就用静态方法;如果为了稳妥,就用实例方法,这样才可调用其他实例方法和变量 。
    萧远山说:
    静态方法比较少用,因为他在一启动就实例化了,比较占资源,当然,,配合单例模式还是比较好用的
    比较多的用在数据连接上,我避免使用的原则就是减少资源消耗。
    张新波说:
    静态方法意味着我在调用前不需要进行对其所属的类进行new操作,我主要会在工具类里面用到静态方法。

    向详说:
    静态就是类的,实例就是对象的。
    静态方法和实例方法的区别之处还有一个地方:静态方法不需要依赖类当中的属性,能在这个方法中封闭的完成一个功能。实例方法更多的会使用到类当中的属性。
    winson_张林说:
    最大的区别在于内存。
    静态方法在程序开始时生成内存,实例方法在程序运行中生成内存,
    所以静态方法可以直接调用,实例方法要先成生实例,通过实例调用方法,静态速度很快,但是多了会占内存。
    任何语言都是对内存和磁盘的操作,至于是否面向对象,只是软件层的问题,底层都是一样的,只是实现方法不同。
    静态内存是连续的,因为是在程序开始时就生成了,而实例申请的是离散的空间,所以当然没有静态方法快,
    而且静态内存是有限制的,太多了程序会启动不了。
    showlover说:
    静态方法与实例方法各有自己的用处…
    是定义成静态方法,还是定义成实例方法,还要看具体情况,比如方法本身与类型没有太大的关系,可以定义成静态方法..
    用实例方法,当然需要你先创建实例,才能调用实例方法,而静态方法则不需要..
    从性能上说,静态方法效率要稍微高一些,但是它会常驻内存…
    一些情况下使用静态方法是有好处的,因为对于静态方法无论你有多少个实例,
    内存中要维护的一份拷贝。同时,某些方法,确实使用静态是比较恰当的..
    Q.yuhen说:
    这个问题牵扯到的东西比较多,诸如设计模式等等。简单点说,静态方法用来执行无状态的一个完整操作,实例方法则相反,它通常是一个完整逻辑的一部分,并且需要维护一定的状态值。
    如果用内存和效率来区分使用Static Method、Instance Method 就回到过去结构化编程了。使用那种方法的根本出发点还是围绕面向对象来进行的。

    陈亮说:
    静态方法和全局函数差不多的,实例方法是一个类里面的方法。

    总结:大家对这个问题都有一个共识:那就是实例化方法更多被使用和稳妥,静态方法少使用。
    有时候我们对静态方法和实例化方法会有一些误解。
    1、大家都以为“ 静态方法常驻内存,实例方法不是,所以静态方法效率高但占内存。”
    事实上,他们都是一样的,在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载。调用的速度基本上没有差别。
    2、大家都以为“ 静态方法在堆上分配内存,实例方法在堆栈上”
    事实上所有的方法都不可能在堆或者堆栈上分配内存,方法作为代码是被加载到特殊的代码内存区域,这个内存区域是不可写的。
    方法占不占用更多内存,和它是不是static没什么关系。
    因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以每个实例对象的所有字段都会在内存中有一分拷贝,也因为这样你才能用它们来区分你现在操作的是哪个对象。
    但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是static还是non-static的方法,都只存在一份代码,也就是只占用一份内存空间。
    同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用class的成员变量的值……
    3、大家都以为“实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单”
    事实上如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。
    当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。
    从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据是否该方法和实例化对象具有逻辑上的相关性,如果是就应该使用实例化对象 反之使用静态方法。这只是从面向对象角度上来说的。
    如果从线程安全、性能、兼容性上来看 也是选用实例化方法为宜。
    我们为什么要把方法区分为:静态方法和实例化方法 ?
    如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。
    拿别人一个例子说事:
    比如说“人”这个类,每个人都有姓名、年龄、性别、身高等,这些属性就应该是非静态的,因为每个人都的这些属性都不相同;但人在生物学上属于哪个门哪个纲哪个目等,这个属性是属于整个人类,所以就应该是静态的——它不依赖与某个特定的人,不会有某个人是“脊椎动物门哺乳动物纲灵长目”而某个人却是“偶蹄目”的。

    展开全文
  • 方法 (Method) 是一种类型定义,所以,它被存放在 Type Object 上,Type Object 是一个被分配在托管堆上的特殊类型,在同一个 AppDomain 中,每一个类型,都对应一个全局的 Type Object。每个引用类型的实例,都包含...
  • Java内存区域介绍(附带JDK1.8后方法区的变化)

    千次阅读 多人点赞 2019-07-13 13:51:23
    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(JavaSE7版)》的规定,java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示 程序计数器...
  • -- 面向对象:主要关注对象【独立体】能完成哪些功能。 优点:耦合度低,扩展力强。 缺点:前期投入成本较高,需要进行独立体的抽取,大量的系统设计 -- c语言是纯面向过程的、C++半面向对象、java纯面向...
  • JavaScript内存泄漏的排查方法

    千次阅读 2016-08-19 18:11:50
    本文转自JS内存泄漏排查方法——Chrome Profiles 概述Google Chrome浏览器提供了非常强大的JS调试工具,Heap Profiling便是其中一个。Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该...
  • 若需将数据存储在内存中,以便进一步运算、处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空间。为了便于内存的分配和释放,AWorks提供了两种内存管理工具:堆和内存池。 本文为《面向...
  • Android手机的内存大小信息存放在手机系统的 /proc/meminfo 文件里面,我们可以通过读取这个文件来获取内存信息。 使用 cmd 命令行打开终端或直接在 Android Studio 中使用命令终端,运行 adb 命令可以看到文件详细...
  • float和double类型数据在内存中的存储方法 浮点数(单精度的float和双精度的double)在内存中以二进制的科学计数法表示,表达式为N = 2^E * F;其中E为阶码(采用移位存储),F为尾数。float和double都由符号位、阶...
  • java优化占用内存方法(一)

    万次阅读 2015-09-02 19:26:56
    原文出自【雪的痕迹】 ... java做的系统给人的印象是什么?占内存!说道这句话就会N多人站...其实从理论上来讲java做的系统并不比其他语言开发出来的系统更占用内存,那么为什么却这么N多理由来证明它确实占内存
  • C++ 字符串 对象 C++ 字符串 对象 创建方法 C++ 字符串 对象 输出到控制台 C++ 字符串 拼接 ...C++ 字符串 方法调用 ( 栈内存对象 ) C++ 字符串 方法调用 ( 堆内存对象 ) C / C++ 字符串 完整代码示例
  • iOS 内存泄漏排查方法及原因分析

    千次阅读 2018-11-22 09:03:18
    内存泄漏排查方法(工具) 内存泄漏原因分析(解决方案) 在正式开始前,我们先区分两个基本概念: 内存泄漏(memory leak):是指申请的内存空间使用完毕之后未回收。 一次内存泄露危害可以忽略,但若一直泄漏...
  • java优化占用内存的几种方法

    千次阅读 2018-08-07 21:07:40
    其实从理论上来讲java做的系统并不比其他语言开发出来的 系统更占用内存,那么为什么却这么N多理由来证明它确实占内存呢?两个字,陋习。 (1)别用new Boolean()。 在很多场景中Boolean类型是必须的,比如JDBC中...
  • 最近在看ArrayList源码的时候看到了ArrayList的clear方法,源码如下: public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = nu...
  • static静态方法执行--内存分析

    千次阅读 2019-05-07 00:52:35
    静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭。 非静态方法(Non-Static Method)又叫实例化方法,属于...
  • 内存泄漏以及常见的解决方法

    万次阅读 2015-06-13 11:18:03
    之所以撰写这篇文章是因为前段时间花费了很大的精力在已经成熟的代码上再去处理memory leak问题。...1. 什么是内存泄漏(memory leak)?  指由于疏忽或错误造成程序未能释放已经不再使用的内存
  • c++指针和内存结构总结

    千次阅读 2017-07-04 16:05:14
    c++指针、内存模型、函数指针
  • 4种JavaScript的内存泄露及避免方法

    万次阅读 多人点赞 2017-06-08 15:31:54
    这篇文章里面我们会讨论客户侧...即使使用有内存管理的语言,也有内存可能会泄漏的情况。泄漏是很多问题的起因:变慢,崩溃,高延迟,甚至是一些和其他应用一起用所出现的问题。 内存泄漏是什么? 本质上,
  • Android内存泄漏的轻松解决方法

    千次阅读 2019-07-02 08:08:53
    这篇文章主要给大家介绍了关于Android内存泄漏的轻松解决方法,文中通过示例代码介绍的非常详细,对各位Android具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 前言 内存管理的目的就是让我们在开发...
  • java方法内存空间

    千次阅读 2017-11-13 21:48:08
    方法介绍方法在C或其它面向过程的语言中又称为函数: method or function 我们接触过的第一方法为: public static void main(String args[]){ ...访问修饰符:缺省,public, protected, private
  • Swift进阶之内存模型和方法调度

    千次阅读 2016-11-13 16:08:01
    前言Apple今年推出了Swift3.0,较2.3来说,3.0是一次重大的升级。关于这次更新,在这里都可以找到,最主要的还是提高了Swift的性能,优化了Swift API的设计(命名)规范。...但是,一点要注意:3.0的API设计规范较2.3
  • JVM的体系结构(1) Class Loader类加载器 负责加载 .class文件,class文件在文件开头特定的文件标示,并且ClassLoader负责class文件的加载等,至于它是否可以运行,则由Execution Engine决定。① 定位和导入二进制...
  • JDK1.7 及之后版本的 JVM 已经将运行时...JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。 已知: String A="abc"; String B="abc"; String C=new String("abc"); String D=new Str...
  • C++内存泄漏及解决方法

    万次阅读 多人点赞 2017-07-13 13:17:03
    1.首先说到c++内存泄漏时要知道它的含义? 内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,...
  • java 内存方法区详解

    千次阅读 2017-05-09 10:23:16
    (一)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态...(1) 在一个 JVM 实例的内部,类型信息都会被存储在一个称为方法区的内存逻辑区中。类型信息是由类加载时从类文件
  • Java常见的几种内存溢出及解决方法

    千次阅读 2018-04-27 00:00:31
    ava常见的几种内存溢出及解决方法【情况一】:java.lang.OutOfMemoryError:Javaheapspace:这种是java堆内存不够,一个原因是真不够(如递归的层数太多等),另一个原因是程序中死循环; 如果是java堆内存不够的...
  • Java内存区域——堆,栈,方法区等

    千次阅读 多人点赞 2018-01-02 16:15:48
    运行时数据区域 程序计数器 Java虚拟机栈 栈帧 本地方法栈 堆 方法区 运行时常量池 ...jdk1.7中, Java虚拟机在...1. 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行的字节码
  • Java中类,对象,方法内存分配

    千次阅读 2017-06-09 10:22:01
    重新理解类,对象,内存分配以下针对引用数据类型: 在内存中,类是静态的概念,它存在于内存中的CodeSegment中。 当我们使用new关键字生成对象时,JVM根据类的代码,去堆内存中开辟一块控件,存放该对象,该对象...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,584,477
精华内容 633,790
关键字:

内存有哪些分类方法