从几个音素是从什么角度划分出来的谈谈内存的管理和划分

从JVM的内存管理角度分析Java的GC垃圾回收机制
转载 &更新时间:日 15:42:55 & 投稿:goldensun
这篇文章主要介绍了从JVM的内存管理角度分析Java的GC垃圾回收机制,带有GC是Java语言的重要特性之一,需要的朋友可以参考下
一个优秀的Java程序员必须了解GC的工作原理、如何优化GC的性能、如何与GC进行有限的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统、实时系统等,只有全面提升内存的管理效率 ,才能提高整个应用程序的性能。本篇文章首先简单介绍GC的工作原理之后,然后再对GC的几个关键问题进行深入探讨,最后提出一些Java程序设计建议,从GC角度提高Java程序的性能。
&&&   GC的基本原理
&&&   Java的内存管理实际上就是对象的管理,其中包括对象的分配和释放。
&&&   对于程序员来说,分配对象使用new关键字;释放对象时,只要将对象所有引用赋值为null,让程序不能够再访问到这个对象,我们称该对象为\"不可达的\".GC将负责回收所有\"不可达\"对象的内存空间。
&&&   对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象(详见 参考资料1 )。通过这种方式确定哪些对象是\"可达的\",哪些对象是\"不可达的\".当GC确定一些对象为\"不可达\"时,GC就有责任回收这些内存空间。但是,为了保证GC能够在不同平台实现的问题,Java规范对GC的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候进行回收等重要问题都没有明确的规定。因此,不同的JVM的实现者往往有不同的实现算法。这也给Java程序员的开发带来行多不确定性。本文研究了几个与GC工作相关的问题,努力减少这种不确定性给Java程序带来的负面影响。
&&&   增量式GC( Incremental GC )
&&&   GC在JVM中通常是由一个或一组进程来实现的,它本身也和用户程序一样占用heap空间,运行时也占用CPU.当GC进程运行时,应用程序停止运行。因此,当GC运行时间较长时,用户能够感到Java程序的停顿,另外一方面,如果GC运行时间太短,则可能对象回收率太低,这意味着还有很多应该回收的对象没有被回收,仍然占用大量内存。因此,在设计GC的时候,就必须在停顿时间和回收率之间进行权衡。一个好的GC实现允许用户定义自己所需要的设置,例如有些内存有限有设备,对内存的使用量非常敏感,希望GC能够准确的回收内存,它并不在意程序速度的放慢。另外一些实时网络游戏,就不能够允许程序有长时间的中断。增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。虽然,增量式GC在整体性能上可能不如普通GC的效率高,但是它能够减少程序的最长停顿时间。
&&&   Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式为不使用增量GC,为了启动增量GC,我们必须在运行Java程序时增加-Xincgc的参数。HotSpot JVM增量式GC的实现是采用Train GC算法。它的基本想法就是,将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁高和具有相关性的对象放在一队中,随着程序的运行,不断对组进行调整。当GC运行时,它总是先回收最老的(最近很少访问的)的对象,如果整组都为可回收对象,GC将整组回收。这样,每次GC运行只回收一定比例的不可达对象,保证程序的顺畅运行。
&&&   详解finalize函数
&&&   finalize是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。由于,finalize函数没有自动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句通常是super.finalize()。通过这种方式,我们可以实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。
&&&   根据Java语言规范,JVM保证调用finalize函数之前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。
&&&   很多Java初学者会认为这个方法类似与C++中的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三,其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作。其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用finalize会降低GC的运行性能。其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。
&&&   通常,finalize用于一些不容易控制、并且非常重要资源的释放,例如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。
&&&   下面给出一个例子说明,finalize函数被调用以后,仍然可能是可达的,同时也可说明一个对象的finalize只可能运行一次。
class MyObject{
   T //记录Test对象,在finalize中时用于恢复可达性
   public MyObject(Test t)
   main=t; //保存Test 对象
   protected void finalize()
   main.ref=// 恢复本对象,让本对象可达
   System.out.println(\"This is finalize\");//用于测试finalize只运行一次
  class Test {
   public static void main(String[] args) {
   Test test=new Test();
   test.ref=new MyObject(test);
   test.ref= //MyObject对象为不可达对象,finalize将被调用
   System.gc();
   if (test.ref!=null) System.out.println(\"My Object还活着\");
&&&   运行结果:
This is finalize
MyObject还活着
  此例子中,需要注意的是虽然MyObject对象在finalize中变成可达对象,但是下次回收时候,finalize却不再被调用,因为finalize函数最多只调用一次。
  程序如何与GC进行交互
  Java2增强了内存管理功能, 增加了一个java.lang.ref包,其中定义了三种引用类。这三种引用类分别为SoftReference、WeakReference和PhantomReference.通过使用这些引用类,程序员可以在一定程度与GC进行交互,以便改善GC的工作效率。这些引用类的引用强度介于可达对象和不可达对象之间。
  创建一个引用对象也非常容易,例如如果你需要创建一个Soft Reference对象,那么首先创建一个对象,并采用普通引用方式(可达对象);然后再创建一个SoftReference引用该对象;最后将普通引用设置为null.通过这种方式,这个对象就只有一个Soft Reference引用。同时,我们称这个对象为Soft Reference 对象。
  Soft Reference的主要特点是据有较强的引用功能。只有当内存不够的时候,才进行回收这类内存,因此在内存足够的时候,它们通常不被回收。另外,这些引用对象还能保证在Java抛出OutOfMemory 异常之前,被设置为null.它可以用于实现一些常用图片的缓存,实现Cache的功能,保证最大限度的使用内存而不引起OutOfMemory.以下给出这种引用类型的使用伪代码;
//申请一个图像对象
  Image image=new Image();//创建Image对象
  //使用 image
  //使用完了image,将它设置为soft 引用类型,并且释放强引用;
  SoftReference sr=new SoftReference(image);
  image=
   //下次使用时
   if (sr!=null) image=sr.get();
   else{
   //由于GC由于低内存,已释放image,因此需要重新装载;
   image=new Image();
  sr=new SoftReference(image);
  Weak引用对象与Soft引用对象的最大不同就在于:GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象,GC总是进行回收。Weak引用对象更容易、更快被GC回收。虽然,GC在运行时一定回收Weak对象,但是复杂关系的Weak对象群常常需要好几次GC的运行才能完成。Weak引用对象常常用于Map结构中,引用数据量较大的对象,一旦该对象的强引用为null时,GC能够快速地回收该对象空间。
  Phantom引用的用途较少,主要用于辅助finalize函数的使用。Phantom对象指一些对象,它们执行完了finalize函数,并为不可达对象,但是它们还没有被GC回收。这种对象可以辅助finalize进行一些后期的回收工作,我们通过覆盖Reference的clear()方法,增强资源回收机制的灵活性。
  一些Java编码的建议
  根据GC的工作原理,我们可以通过一些技巧和方式,让GC运行更加有效率,更加符合应用程序的要求。以下就是一些程序设计的几点建议。
  1.最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null.我们在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组,队列,树,图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null.这样可以加速GC的工作。 [Page]
  2.尽量少用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。
  3.如果需要使用经常使用的图片,可以使用soft应用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory.
  4.注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。
  5.当程序有一定的等待时间,程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。使用增量式GC可以缩短Java程序的暂停时间。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具内存条的好坏主要看哪几个方面的参数??_百度知道
内存条的好坏主要看哪几个方面的参数??
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
根据您的提问,内存条主要看以下三个方面的参数:时钟频率:它代表了DDR所能稳定运行的最大频率,也就是我们平时讲的PC-1600和PC-2100等,它们分别表示可在200MHz和266MHz的时钟频率下稳定运行。另外我们也要注意到,传统的内存规格命名是基于内存的时钟频率,而现行的DDR内存是基于传输速率命名的。实际上PC-1600和PC-2100按照SDRAM的划分标准也就是相对应的PC-200和PC-266。 存取时间:存取时间代表了读取数据所延迟的时间。以前人们有个误区,认为它和系统时钟频率有着某种联系,其实二者在本质上是有着显著区别的,可以说完全是两回事。例如SDRAM同样是PC133的内存,市面上有-7和-6的,它们的存取时间分别为7ns和6ns,但它们的时钟频率均为133MHz。存取时间和时钟频率不一样,越小则越优。在DDR内存上亦一样,各位一定要注意。 CAS的延迟时间:这是指纵向地址脉冲的反应时间,也是在一定频率下衡量支持不同规范的内存的重要标志之一。我们用CAS Latency(CL)这个指标来衡量。对于PC-1600和PC-2100的内存来说,其规定的CL应该为2(即它读取数据的延迟时间是两个时钟周期),也就是说,它必须在CL=2的情况下稳定工作在其工作频率。
内存颗粒,内存颗粒的好坏决定了内存整体质量的主要因素PCB板层,良好的PCB能使内存发挥高性能,尤其在高频下,防磁干扰等,一般是4层,层数越多越好,但价格随之翻倍,时钟频率,最常见的DDR2 800
就是我们选购的主流产品,体制好的能超上1066甚至更高内存的好坏,用着才感受得到,比如 突然蓝屏,死机, 数据经常发生冲突,出现不能读写等,都是内存不佳的表现,新内存开 MEN TEST 烤 5小时,没事的话,基本可以
品牌和内存颗粒!!如果是性能就要看它的频率内存颗粒!!
为您推荐:
其他类似问题
您可能关注的内容
内存条的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。天极传媒:天极网全国分站
您现在的位置:
& &&经验:内存不能为read的解决办法
玩家经验之谈:内存不能为read的解决办法Yesky 09:45
【百万玩家最喜爱的游戏娱乐媒体,把最带劲的娱乐资讯,最权威的游戏推荐,最齐全的手游礼包放进你的口袋,却不用你多安装一个APP,还等什么?赶紧就关注微信号 【kdyx91】 每日七点不见不散~】
  相信各位众多的XP用户都曾经经历过类似饿经历,当运行某些程序的时候,有时会出现错误的提示,然后该程序就关闭。 大概形式如下:
  “0x????????”指令引用的“0x????????”内存。该内存不能为“read”。
  “0x????????”指令引用的“0x????????”内存,该内存不能为“written”。
  不知你出现过类似这样的故障吗?(0x后面内容有可能不一样。)
  下面我整理了一些资料供大家参考参考,这些资料都是来自网上的。
  原因:
  一般出现这个现象有方面的,一是硬件,即内存方面有问题,二是软件,这就有多方面的问题了。
  一、下面先说说硬件:
  一般来说,内存出现问题的可能性并不大,主要方面是:坏了、内存质量有问题,还有就是不同牌子不同容量的内存混插(特别是,在新增加了内存后,由于各种原因也有可能会出现这种问题),也比较容易出现不兼容的情况,同时还要注意散热问题,特别是超频后。你可以使用MemTest 这个软件来检测一下内存,它可以彻底的检测出内存的稳定度。
  假如你是双内存,而且是不同品牌的内存条混插或者买了二手内存时,出现这个问题,这时,你就要检查是不是内存出问题了或者和其它硬件不兼容。
  二、如果都没有,那就从软件方面排除故障了。
  先说原理:内存有个存放数据的地方叫缓冲区,当程序把数据放在缓冲区,需要提供的“功能函数”来申请,如果内存分配成功,函数就会将所新开辟的内存区地址返回给应用程序,应用程序就可以通过这个地址使用这块内存。这就是“动态内存分配”,内存地址也就是编程中的“光标”。内存不是永远都招之即来、用之不尽的,有时候内存分配也会失败。当分配失败时系统函数会返回一个0值,这时返回值“0”已不表示新启用的光标,而是系统向应用程序发出的一个通知,告知出现了错误。作为应用程序,在每一次申请内存后都应该检查返回值是否为0,如果是,则意味着出现了故障,应该采取一些措施挽救,这就增强了程序的“健壮性”。若应用程序没有检查这个错误,它就会按照“思维惯性”认为这个值是给它分配的可用光标,继续在之后的执行中使用这块内存。真正的0地址内存区储存的是系统中最重要的“中断描述符表”,绝对不允许应用程序使用。在没有保护机制的操作系统下(如DOS),写数据到这个地址会导致立即当机,而在健壮的操作系统中,如等,这个操作会马上被系统的保护机制捕获,其结果就是由操作系统强行关闭出错的应用程序,以防止其错误扩大。这时候,就会出现上述的内存不能为“read”错误,并指出被引用的内存地址为“0x“。内存分配失败故障的原因很多,内存不够、系统函数的版本不匹配等都可能有影响。因此,这种分配失败多见于操作系统使用很长时间后,安装了多种应用程序(包括无意中“安装”的病毒程序),更改了大量的系统参数和系统档案之后。
  在使用动态分配的应用程序中,有时会有这样的情况出现:程序试图读写一块“应该可用”的内存,但不知为什么,这个预料中可用的光标已经失效了。有可能是“忘记了”向操作系统要求分配,也可能是程序自己在某个时候已经注销了这块内存而“没有留意”等等。注销了的内存被系统回收,其访问权已经不属于该应用程序,因此读写操作也同样会触发系统的保护机制,企图“违法”的程序唯一的下场就是被操作终止执行,回收全部资源。计算机世界的法律还是要比人类有效和严厉得多啊!像这样的情况都属于程序自身的BUG,你往往可在特定的操作顺序下重现错误。无效光标不一定总是0,因此错误提示中的内存地址也不一定为“0x”,而是其它随机数字。
  下面有一个比较简单点的解释:
  内存有个存放数据的地方叫缓冲区,当程序把数据放在其一位置时,因为没有足够空间,就会发生溢出现象。举个例子:一个桶子只能将一斤的水,当你放入两斤的水进入时,就会溢出来。而系统则是在屏幕上表现出来。这个问题,经常出现在windows2000和XP系统上,Windows 2000/XP对硬件的要求是很苛刻的,一旦遇到资源死锁、溢出或者类似里的非法操作,系统为保持稳定,就会出现上述情况。另外也可能是硬件设备之间的兼容性不好造成的。
  首先建议:
  1、 检查系统中是否有木马或病毒。这类程序为了控制系统往往不负责任地修改系统,从而导致操作系统异常。平常应加强信息安全意识,对来源不明的可执行程序绝不好奇。
  2、 更新操作系统,让操作系统的安装程序重新拷贝正确版本的系统档案、修正系统参数。有时候操作系统本身也会有BUG,要注意安装官方发行的升级程序。
  3、 尽量使用最新正式版本的应用程序、Beta版、试用版都会有BUG。
  4、 删除然后重新创建 Winnt\**32\Wbem\Repository夹中的文件:在桌面上右击我的,然后单击管理。 在"服务和应用程序"下,单击服务,然后关闭并停止 Windows Management Instrumentation 服务。 删除 Winnt\**32\Wbem\Repository 文件夹中的所有文件。(在删除前请创建这些文件的备份副本。) 打开"服务和应用程序",单击服务,然后打开并启动 Windows Management Instrumentation 服务。当服务重新启动时,将基于以下注册表项中所提供的信息重新创建这些文件: HKEY_LOCAL_MACHINE\SOFTWARE\\WBEM\CIMOM\Autorecover MOFs
  下面我从几个例子给大家分析:
  例一:打开IE浏览器或者没过几分钟就会出现"0x70dcf39f"指令引用的"0x"内存。该内存不能为“read”。要终止程序,请单击“确定”的信息框,单击“确定”后,又出现“发生内部错误,您正在使用的其中一个窗口即将关闭”的信息框,关闭该提示信息后,IE浏览器也被关闭。 解决方法:修复或升级IE浏览器,同时打上补丁。看过其中一个修复方法是,Win2000自升级,也就是Win2000升级到Win2000,其实这种方法也就是把系统还原到系统初始的状态下。比如你的IE升级到了6.0,自升级后,会被IE5.0代替。
(作者:舞灵责任编辑:周丽京)
天极新媒体&最酷科技资讯扫码赢大奖
* 网友发言均非本站立场,本站不在评论栏推荐任何网店、经销商,谨防上当受骗!
数码整机手机软件Java编程入门官方教程(第7版)
阅读: 2557下载: 32
UNIX入门经典
阅读: 1778下载: 27
c语言实用之道
阅读: 2079下载: 22
学习编程第一步
零基础上手Python开发
阅读: 602下载: 12
沈金堤@滴滴出行
阅读: 1280下载: 9
阅读: 21147下载: 3362
阅读: 21207下载: 3291
阅读: 21145下载: 3220
阅读: 21122下载: 3066
阅读: 21105下载: 2298
从JVM内存管理的角度谈谈静态方法和静态属性-和-java对象引用与JVM自动内存
文件大小:195.39KBMB所需财富值:50
您当前剩余财富值:
从JVM内存管理的角度谈谈静态方法和静态属性-和-java对象引用与JVM自动内存
文件大小:195.39KBMB所需财富值:40
您当前剩余财富值:
网站帮助:
盛拓传媒: |从内存分配角度分析c和java里的static
&&& 即使作为Java的初学者, 对this 和 static 这两个关键字都不会陌生. 其实也不难理解:
&&& this 关键字:& 指的是对象的本身(注意不是类本身)& 跟.net 语言的Me 关键字类似.
&&& static 关键字: 静态分配的对象或对象成员.& 也就是指被static 修饰的成员只属于类本身, 而不会想其他成员一样会对每个对象单独分配.
&&& 但是c语言也有static关键字, 但是c语言中的static并不只是静态分配的意思,如果用在静态局部变量(函数内部), 则是说明这个变量是静态的,&& 如果用在全局变量或函数, 则是防止函数或全程变量被其他c文件中的函数访问(通过include 头文件).& 为什么Java里的static 会跟c 语言里的有这种区别呢.
&&& 下面会从内存分配的角度浅析一下这个问题.
一. C语言程序所占内存的大概结构
&& & & 我们知道, static 的意思是静态分配, 那么到底什么是静态分配和动态分配呢.& 其实内存的静态分配和动态分配是对于C/C++ 来讲的.& 而Java 作为由C/C++ 发展而来的类C语言, 虽然把内存管理这一块砍掉了(对程序员屏蔽, 在Java底层处理), 但是还是继承了C语言的一些特性.
&&&&&& 所以Java里有些特性和概念, 通过C语言分析能更好的理解.
&&&&&& 首先, 1个由C语言编译的程序所使用的内存大概分成如下几个部分
&&&&&& 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
&&&&&& 2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
&&&&&& 3、全局区(静态区)(static) --- 用来存放全局变量和静态局部变量.
&&&&&& 4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 &
&&&&&& 5、程序代码区—存放函数体的二进制代码。
&&&&&& 下面是大致的图解.
二. C语言内存的静态分配和动态分配.
2.1 静态分配内存
&&&&& 对于C语言来讲, 静态分配内存就是只用 类型 + 变量名 定义的变量. 不管这个变量是局部变量(函数中) 或全局变量(函数外).
<span style="color:#.1.1 局部变量 &
&&&& 第一种情况,& 函数中静态分配内存的局部变量.
&&&& 如上面那个简单的例子,& 在f()函数里定义的局部变量j 就是1个静态分配内存的变量(注意不是静态变量).
&&&& 这种静态分配的变量的生存周期就是函数执行一次的周期.
&&&& 什么意思呢,& 就是当f()执行时, 操作系统会为变量j 分配1个字节(32位系统)的内存,& 但是当f() 执行完时.& 变量j所占的内存就会被释放. 可以操作系统用作它用.
&&&&& 也就是说, 当f() 被循环执行1万次, 程序并不会额外占用9MB多的内存,& 因为j所占的内存会不断地释放分配.
&&&&& 上面提过了,& 局部变量所占的内存是分配在内存里的Stuck(栈)区的. 因为j是int 类型, 所以它会在stuck区占用1字节
注: 局部变量还有另1种形式就是函数的参数:
如下面的变量i也是局部变量:
int f(int i){
<span style="color:#.1.2 全局变量
&&&& 第二种情况就是定义在函数外(c文件中)静态分配的变量.
&&&& 上面的j就是全局变量了.
&&&& 全局变量可以被各个函数调用, 所以全局变量j的生存周期跟函数f()无关.
&&&& 也就是说, 全局变量的生存周期是就是程序的生存周期.
&&&&& 即是,如果程序一直在运行,& 变量j所占的内存就不会被释放.& 理论上讲, 定义的全局变量越多, 程序所占的内存就越大.
&&&& 可见全局变量在内存中是静态的, 一旦被分配内存.它不会被释放和重新分配. 所以它占用的内存被分配在内存里的静态区:
2.2 动态分配内存
&&&&& c语言所谓动态分配内存就是使用malloc 函数(必须引用stdlib.h) 在内存中划分一个连续区间(heap区), 然后用1个指针变量接受把这区间的头部地址.& 这个指针变量所指向的内存就是动态分配的内存.
&&&&& 举个例子:
#include &stdio.h&
#include &stdlib.h&
int * f(){
variable with static memory allocation cannot be past to another function.
//int j = 20; int
* p = (int *)malloc(sizeof(int)); //good
int main(){
int * q = f();
printf (&*q is %d\n&, *q);
参考上面那个小程序.
在函数f()中.
* p = (int *)malloc(sizeof(int));
这条语句首先定义了1个int类型的指针 p. 这个指针本身是静态分配的.
在heap去划分了1个size为1个int(1字节)的动态内存,& 并且将该动态内存的头部地址赋给指针p.
最终这个函数f()返回了指针p的&#20540;, 也就是动态内存的头部地址
在main()函数中,
再定义1个静态分配的指针q, 用来接受函数f()返回的动态内存的头部地址.
一旦f()函数执行完,& f()里的指针p本身会被释放,& 但是p指向的动态内存仍然存在, 而且它的头部地址被赋给了main()函数的指针q.
然后, q使用了这个动态内存.(赋&#20540;20)
动态内存的生命周期也是整个程序, 但是动态内存可以被手动释放. 在main()函数的最后使用了free()函数释放了指针q,也就是q指向的动态内存.
如果不手动释放, 那么当f() 和 main()函数被循环多次执行时, 就会多次在heap区划分内存, 造成可用内存越来越少,就是所谓的内存泄露了.
1. main()函数定义指针q, 这个指针q本身是1个局部变量, 在栈区分配内存.
2. main()函数调用f()函数, 局部变量指针q在f()里定义, 在栈区分配内存
3. 在heap区划分一块动态内存(malloc函数)
4& 把动态内存的头部地址赋给f()函数里的p
5. f()函数执行完成, p的&#20540;(就是动态内头部地址)赋给了main()函数的指针q, 这时q指向了动态内存. p本身被释放.
6. 当动态内存被使用完后, 在mian()函数的最后利用free()函数把动态内存释放, 这个动作相当重要.
7. 当mian()执行完时, 指针p本身也会被释放.
2.3 动态分配内存和静态分配内存的区别.
由上面的例子可以看出, 动态内存与静态内存有如下的区别.
1.静态分配内存的变量用 类型名(结构体名) &#43; 变量名 定义,& 动态分配的内存变量用malloc划分, 然后必须把地址传给另1个指针.
2.静态变量在内存里的栈区(局部变量)或全局区(全局变量 or 静态局部变量(后面会提到))里分配.& 动态分配内存的变量在heap区分配.
3.静态分配内存变量生命周期有两种, 其中局部变量,在函数结束后就会被释放, 而全局变量在程序结束后才释放.& 而动态变量需要程序员手动释放, 否则会在程序结束后才释放.
2.4 动态分配内存的优缺点
<span style="color:#.4.1 动态分配内存的三个优点
看起来动态分配内存的变量的使用貌&#20284;比静态分配内存的变量使用麻烦啊.& 单单1个malloc函数都令新人觉得头痛.
但是动态分配的内存有三个优点.
1.可以跨函数使用. 参见上面的例子, main()函数使用了f()函数定义的动态内存.
&& 有人说全局变量也可跨函数使用啊, 的确. 但是全局变量必须预先定义(预先占用内存), 而动态分配内存可以再需要时分配内存. 更加灵活.
&&& 关于跨函数使用内存可以参考我另1篇博文:
http://blog.csdn.net/nvd11/article/details/8749395
2.& 可以灵活地指定或分内存的大小.
&&& 例如 (int *)malloc(sizeof(int) * 4) 就划分了4个字节的内存(动态数组).&& 这个特性在定义动态数组时特别明显.
&&& 而且可以用realloc 函数随时扩充或减少动态内存的长度.&
&&&& 这个特性是静态分配内存的变量不具备的.
3. 动态变量可以按需求别手动释放.
&&& 虽然局部变量随函数结束会自动释放,& 而动态分配的内存甚至能在函数结束前手动释放.
&&& 而全局变量是不能释放的. 所以使用动态内存比使用全局变量更加节省内存.
<span style="color:#.4.2 动态分配内存的两个硬伤
但是动态分配内存也有2个硬伤:
1.& 就是必须手动释放....& 否则会造成内存泄露.
其实上面都讲过了, 这里举个具体例子:
#include &stdio.h&
#include &stdlib.h&
int f(int i){
* p = (int *)malloc(sizeof(int));
printf (&*p is %d\n&, *p);
int main(){
for (i=0; i&100; i++){
程序1的f() 函数被main()函数循环执行了100次,&& 所以f()在内存heap区划分了100次动态内存,& 但是每一次f()结束前都会用free函数将其手动释放.
所以并不会造成内存浪费.& 这里再提一提, free(p) 这个函数作用是释放p所指向的动态内存,& 但是指针p的&#20540;不变, 仍然是那个动态内存的地址.& 如果下次再次使用p就肯定出错了.所以保险起见加上p=NULL, 以后使用p之前,也可以用NULL&#20540;判断它是否被释放过.
#include &stdio.h&
#include &stdlib.h&
int f(int i){
* p = (int *)malloc(sizeof(int));
printf (&*p is %d\n&, *p);
int main(){
for (i=0; i&100; i++){
上面就是反面教材. 如果没有手动释放, 当f()函数被循环执行时就会多次划分动态内存, 导致可用内存越來越少. 也就是程序所占的内存越來越多, 这就是传说中的内存泄露.
可能有人认为, 不就是加多1个free()函数吗?& 算不上缺点.
但是有时候程序猿很难判断一个指针该不该被free() 函数释放..
#include &stdio.h&
#include &stdlib.h&
int main(){
* p = (int *)malloc(sizeof(int));
printf (&*p is %d\n&, *p);
printf (&*q is %d\n&, *q);
free(q); //error, the dynamic memory is released already.
看看上面的例子,
指针p和q指向同1个动态内存.
当执行free(p)时,& 释放的是动态内存, 而不是释放p本身,& 所以再此执行free(q)就出错了,&& 因为那个动态内存已经被释放过了嘛..
有人觉得, 这个错误也不难发现嘛.., 小心点就ok了.
首先, 上面的代码编译时并不会报错, 至少gcc会编译通过, 执行时才会出错...
而且, 当项目越來越复杂时, 可能有多个指针指向同1个动态内存,& 而且某个指针指向的动态内存是其他程序员在其他函数内分配的..
这时你就很难判断了,& 如果不释放怕造成内存泄露,& 如果释放了, 别的程序猿不知道的话再次使用...就会出错.
所以有时候在项目中程序猿很难判断1个指针该不该释放啊... 特别是多个程序猿合作的大型c项目.
这就是为什么说c语言功能强大, 但是不适合编写大型项目的原因之一,& 需要程序猿有相当扎实的内存管理能力.
2.& 另个硬伤就是内存溢出.
什么是内存溢出呢,& 就是使用了动态分配内存长度之外的内存...
#include &stdio.h&
#include &stdlib.h&
int main(){
* p = (int *)malloc(sizeof(int) * 4);
*(p + 1) = 2;
*(p + 2) = 3;
*(p + 3) = 4;
*(p + 4) = 5; // error,memory overflow
for (i=0;i & 5;i++){ // error, should be i & 4
printf(&no.%d is %d\n&,i,*(p+i));
上面定义了长度为4的连续内存空间. (动态整形数组)
但是这个程序却使用了长度为5的内存空间, 也就是这个动态内存后面额外的的那一个字节的内存被使用了.
其实就是p&#43;4 这个地址的内存并没有定义, 但是却被使用, 这就是传说中的内存溢出.
这个代码可以被正常编译, 可怕的是, 很多情况下它会正常执行...
但是如果p&#43;4刚好被这个程序的其他变量或其他程序正在使用, 而你却往它写入数据, 则可能会发生导致程序漰溃的错误...
这就是有些c \ c&#43;&#43; 程序不够健壮的原因, 有时候会发生崩溃..
所以说c语言很难就难在这里, 内存管理啊.
三. C语言的static关键字
终于讲到正题了, 下面就说说c语言static关键字对内存分配的影响.
首先, c的static 关键字是不能修饰动态分配内存的.
int * p = static (int *)malloc(sizeof(int))
是错误的.&&
&但是 下面写法是合法的.
static int * p = (int *)malloc(sizeof(int))上面的static 不是修饰动态分配的内存,& 而是修饰静态分配的指针变量p
上面也提到过了, c语言的static 可以修饰如下三种对象:
1. 全局变量和函数
2. 函数内的局部变量.
注意, c语言结构体的成员不能用static 修饰
3.1 static 修饰全局变量或函数.
在全局变量和函数名前面的 static函数并不影响 对象的内存分配方式,
static 修饰的全局变量还是被分配与全局区中.&&&
而被static修饰的函数的2进制代码还是被分配于程序代码区中.
这种情况下 static 的作用只是简单地对其他c文件的函数屏蔽.
也就是1个c文件a.c,& 引用了另一个c文件b.c
那么a.c 文件就不能访问b.c 文件里用static修饰的 全局变量和函数.
3.2 static 修饰局部变量.
如果用static 来修饰c语言函数中的局部变量, 那么这个局部变量是静态局部变量了.
如下面的例子:
#include &stdio.h&
#include &stdlib.h&
int i = 1; // local variable
static int j = 1; // static local variable
printf(&i is %d, j is %d\n&,i,j);
int main(){
for (i=0;i & 10 ;i++){
上面的f() 函数i就是 一般的局部变量了, 而 j 前面有static修饰, 所以j是1个静态局部变量.
如果上面的f()函数被连续执行10次, 那么 i 和 j的&#20540;是不同的.
gateman@TFPC tmp $ ./a.out
i is 2, j is 2
i is 2, j is 3
i is 2, j is 4
i is 2, j is 5
i is 2, j is 6
i is 2, j is 7
i is 2, j is 8
i is 2, j is 9
i is 2, j is 10
i is 2, j is 11
可以见到, 每次f()执行,& i 的&#20540;都是2,& 而 j 的 &#20540; 会不断加1.
原因就是static 用在局部变量前面就会改变该局部变量的内存分配方式.
上面说过, 一般局部变量是放在内存Stuck区的,& 而静态局部变量是放在全局(静态)区的.
当程序执行时,& f()作为1个函数,& 它的2进制代码是存放在内存里的程序代码区的.
对于变量i:
&&&&&&& f()每次执行时都会在Stuck区为变量i初始化一块内存.& 而结束时会自动地把该内存释放,
&&&&&&& 也就是说int i = 1; 这个语句每次执行时都会执行.& 所以i每次输出的&#20540;都是一样的.
对于静态局部变量j:
&&&&&&& f() 会在内存Static区检测有无属于变量j的内存.
&&&&&&& 如果无, 则执行初始化语句 static int j = 1;& 并记录下该内存的地址.
&&&&&&& 如果有, 则直接使用该内存.
&&&&&&& 当f() 执行完成时, 该内存不会被释放.
&&&&&&& 也就是讲, 当f()下一次执行时, 就不会执行 static int j =1; 这条语句.
&&&&&&& 所以当f()循环执行时, j的&#20540;就会递增了.
也就是讲, 当static 修饰1个局部变量时, 会更改局部变量的内存分配方式.而且这个局部变量的生命周期就会变成全局变量一样.
全局变量与静态局部变量的区别:
& & & &由此可见, 静态局部变量与全局变量的内存分配方式是类&#20284;的, 它们的内存都是被分配在static 区, 那么它们的生命周期也是一样的,都是整个程序的生命周期.
& & & &而它们的区别如下:
& & & &1. 全局变量在程序开始时就分配内存, &而静态局部变量在对应函数第一次执行时分配内存.
& & & &2. 全局变量能被各个函数访问, 所以一般用于传递数据. &静态局部变量只能被定义它的函数访问, &一般用于保存特定数据. & &
四. Java语言程序所占内存的大概结构
Java 作为C/C&#43;&#43; 发展出来的语言, 最大的区别就是对程序员管理屏蔽了内存管理的部分. & 也就是说Java没有了指针这个概念. 所有动态内存的分配和释放都在Java底层里自动完成.
所以说Java 的功能和性能都远比不上C/C&#43;&#43; .
但是正因为从根本上避免了内存泄漏等内存操作容易产生的错误, 所以Java编写的程序的健壮性会很好, 也就是Java比C语言更适合大型项目的原因.
Java毕竟也是类C语言的一种, 所以Java的内存结构跟C语言类&#20284;:
可见java的程序会把其占用的内存大概分成4个部分.
Stuck 区: 跟c一样, 存放局部变量, 也就是函数内定义的变量.
Heap 区: 跟c一样, 存放动态分配内存的变量, 只不过动态分配内存的方式跟c不通, 下面会重点提到.
数据区: & &相当于c的static区, 存放静态(static)变量和字符串常量
代码区: & &跟c一样, 存放2进制代码.
五. Java内存的静态分配和动态分配.
跟C一样, Java的变量的内存分配也可以分成静态分配内存和动态分配两种, 只不过形式上跟c语言可以讲存在很大的差别.
5.1 Java静态分配内存
上面提到了, c语言静态分配的内存(非static 修饰)可以分成两种: &全局变量和局部变量. &
其中全局变量在函数外定义, 内存分配在static区. & &而局部变量在函数内定义, 内存分配在stuck区.
而Java 里是不存在全局变量这玩意的. &因为Java是1个完全面向对象的语言, &一旦1个变量不是在函数里定义, 那么他就是在类里面定义, 就是1个类的成员了.
如下面这个例子:
public class A{
int i = 0;
其中, 变量j是A的1个成员, 而不是全局变量.
而变量i 跟c一样, 是属于函数f的1个局部变量.
java里局部变量的内存方式跟c语言是一样的, 都是属于静态分配, &内存被分配在stuck区.
那么其生命周期就也会随函数执行完成而结束, 这里就不细讲了.
5.2 Java动态分配内存
我们看回上面那个例子.
Class A里面的变量j 其实就是A的一个成员, &而Java里1个类里面的所有非Static修饰的成员都是动态分配内存的.
也就是讲, 其实那个变量j是动态分配内存的, 内存被分配在heap区里.
怎么讲呢, &还是要借助c语言:..
2.2.1 c语言静态分配内存的结构体
面向对象跟本上是1种编程的思想, 其实作为面向过程的c语言, 也可以用面向对象的思想来编程..
大家都知道c语言里具有类的雏形---& 结构体. 其本质就是让不同的数据类型集合存储.
首先看看结构体的静态分配内存用法:
#include &stdio.h&
#include &stdlib.h&
#include &string.h&
char name[16];
int main(){
strcpy(a.name, &Jack&);
printf(&%d, %s\n&, a.id, a.name);
上面的简单例子, 定义并使用了1个结构体A.
下面1句1句从内存分配的角度详细讲解...
char name[16];
这几句代码定义了结构体A的结构, 包括1个整形和1个字符数组的两个成员.
在程序执行过程中, &定义代码会作为2进制代码存放于内存里的代码区中.
这代码静态定义了1个结构体a. &它的长度是4 &#43; 16 =20 byte
而内存是被分配在 stuck区的.
注意, 这个时候, A里面的两个成员: &id 和 name里面是垃圾&#20540;, 并没有初始赋&#20540;的.
strcpy(a.name, &Jack&);
这两个就是为结构体a的两个成员赋&#20540;了. 不多说..
2.2.2 c语言静态分配内存的结构体的缺点
这种静态定义使用的结构体优点很简答: 方便使用, 安全性好.
缺点是什么呢?
当然了, 上面都提过:
1. 不能夸函数使用, 生命周期随函数结束.
2. 不能灵活释放.
其实这两个缺点都系虚的.
真正的问题是, 在生产中, &1个结构体往往定义得十分复杂. &也就是包含几十个成员, 几十个函数指针(方法).
那么这个结构体所占的内存就很客观了,
栈的内存大小有限, &而在heap区能申请更大的内存, &这个才是动态分配内存的结构体的必要性.
2.2.3 c语言动态分配内存的结构体
看下面的例子:
#include &stdio.h&
#include &stdlib.h&
#include &string.h&
char name[16];
void (* A_prinf)(struct A *);
void printf_A(struct A * b){
printf(&%d, %s\n&, b-&id, b-&name);
struct A * A_new(int id, char * name){
struct A * b = (struct A *)malloc(sizeof(struct A));
b-&A_prinf = printf_A;
strcpy(b-&name,name);
int main(){
a = A_new(1,&Jack&);
a-&A_prinf(a);
上面就是动态分配内存的结构体最常用的用法..
再一句一句来:
char name[16];
void (* A_prinf)(struct A *);
这段定义了1个结构体A, 跟之前例子不同的是只不过, 多了1个函数指针, 用于打印这个结构体的成员.
这段2进制代码一样存放子在代码区中.
void printf_A(struct A * b){
printf(&%d, %s\n&, b-&id, b-&name);
上面是打印函数的定义了, &我们会将结构体A的指针指向这个函数.
也会放在代码区中.
struct A * A_new(int id, char * name){
struct A * b = (struct A *)malloc(sizeof(struct A));
b-&A_prinf = printf_A;
strcpy(b-&name,name);
A_new()函数相当于1个初始化函数.
无论静态或动态定义1个结构体之后, 只会在stuck区或heap区分配该内存. 而内存里结构体的成员是垃圾数据().
也就是说,定义1个结构体A的&对象&b后, &b的id, name, 函数指针A_prinft都是垃圾数据.
如果不经初始化, 直接使用成员, 例如函数指针的话, 系统就出错了.
所以初始化函数最重要的作用就是把每1个&对象的函数指针指针向正确的函数. &这个就是初始化函数的必要性.(当然你也可以在main函数内手动指向).
这里顺便加两个参数, 把id和name也初始化了.
注意, main函数里这1句定义的是1个结构体A指针, 而不是结构体.
任何类型的指针长度都是4byte(32 位系统), &而这个指针是局部变量, &会被分配在stuck区
a = A_new(1,&Jack&);这里是关键了, 调用A_new()函数, &在heap去分配1个结构体的内存, 然后把头部地址赋给a.&
那么在栈区的指针a就指向堆区的内存了.
a-&A_prinf(a)这里调用了结构体对象a的函数指针A_prinft, &这个指针在初始化函数A_new()执行时已经被指向了真实函数prinft_A().
所以, 实际上是调用了printf_A(). &但是,参数是必要的.
后面的就是释放内存和致空指针. 因为c语言不会自动释放动态内存.
其实单单只看这个例子main()函数的代码, 是不是觉得很像面向对象语言java 或 C&#43;&#43;.
所以讲, 面向对象其实是1种编程思想. &而Java的内部实现还是离不开c/c&#43;&#43;.
我们也可以在这里看出面向对象的一些特性, 这实际上也是动态分配内存的优点:
1. 对象的指针存放在栈区, 而无论1个结构体的内存占用有多么庞大, 栈区的对象指针只会保存结构体内存的头部指针.&
& & 所以栈区的单个对象指针只占用4byte. &相对于静态分配的结构体, 大大节省了栈区的空间.
2. 多个不同的对象会利用不同的heap区内存存放各种的成员(例如id, name), &但是各自的函数指针指向相同的代码区函数.
& &也就是说, 每1个结构体对象的内部函数实际上都是一样的 (除非手动再指向). 只不过参数不同.
2.2.3 Java里的类动态分配.
终于讲回java了, 上面提到那么多c语言的东西其实是为了与java作对比, 更好地了解java里类的内存分配.
Java 是完全面向对象语言, 所以任何东西都必须用类来描述. &而Java的类实际上是由c语言的结构体扩展而来的. & 而上面说过, 生产环境中的类往往非常复杂.
所以 Java 里的类都是动态分配内存的.
看下面这个简单例子:
int i = 10;
System.out.printf(&i is %d\n&, i);
public class A{
public static void main(String[] args){
b = new B();
上面的例子定义了两个类,
在A的入口函数中, &定义并实例化了类B.
下面讲下, 类B是如何被分配内存的.
int i = 10;
System.out.printf(&i is %d\n&, i);
上面就是类B的定义代码, &跟c语言的结构体定义代码一样, 它也会被转成2进制代码而存放在代码区中.
跟c语言的结构体代码做下对比:
char name[16];
void (* A_prinf)(struct A *);
实际上类与结构体成员的定义都是类&#20284;的.
但是 结构体只是一个简单的不同类型的集合体. 里面的成员(包括函数指针)不允许具有初始&#20540;.
而类是允许的. 而且函数的定义代码也写在类里面..
b = new B();
我们看看这四句在入口函数的代码.
&这个语句在c语言中可以理解成 静态定义1个B的结构体对象b.
但是在Java中, 我们应该理解成为定义1个类B的指针b, &注意java里虽然取消了指针操作, 但是java里的底层很多东西都还是需要指针来实现.
相当于c 语言里的
所以这一句理解为简单地定义1个局部指针变量b, 它的内存是分配在stuck区的.
既然对象b只是相当于一个指针. 当执行完这一句时, 它只是1个空指针, 所以它指向的内存并不能使用.&
而我们就说对象b并没有实例化.
当我们直接对对象b的非static成员操作时就会弹出错误: 对象没有实例化了, &就是这个原因.
b = new B();
接下来这一句就比较重要了.
首先 new B() &这个作用就是在heap区动态分配1个类B的内存,其实就是相当于c语言里的 (B *)malloc(sizeof(B))啦.
只不过在Java里, &java把 malloc分配内存的动作隐藏在new这个语句里面. &&
跟c语言一样, 分配内存后还需要把内存的头部指针赋于对象b. &所以会有&b =& 这个写法.
其实就相当于c语言里的
b = (B *)malloc(sizeof(B))
当执行完这一句后
对象b实际上就指向了heap区的对应内存.
这时我们就可以对对象b的成员进行操作. 也就是对象b已经被实例化.
所以其实实例化的真正意思就是1个对象指向了heap区的对应内存.
这时, 我们看看对象b的成员i, &它随着对象b的内存, 同样被分配在heap区里面.
所以我们就说 在函数外定义的非static变量不是全局变量, &而是类的成员, 它们是动态分配的.
既然new出来的东西是动态分配, 那么就需要手动释放? &java里有自动释放的机制, 所以不必程序猿手动释放了.
六. Java里的static关键字
但是有一种情况, &有些类的成员并不需要实例化就可以使用, &那么这种成员就是静态成员, 肯定是用static修饰的.
int i = 1;
System.out.printf(&i is %d\n&, i);
static int j = 1;
static int g(){
System.out.printf(&j is %d\n&, j);
public class A{
public static void main(String[] args){
b = new B();
看看上面这个经过简单修改过的例子.
B增加1个静态成员j, 1个静态函数g();
接下来从这个例下分析下java静态成员的一些特性.
6.1 static 成员不需实例化就可以使用.
上面入口函数中直接使用了类B的静态成员j 和 静态函数g()
这种情况其实就是把 成员j的内存分配在了java的数据区中, 而不是heap区, 这个内存地址在程序执行时是不变的. 而且在代码区的类定义代码里保留了这个地址(指针)
6.2 多个不同的对象共享1个static 成员.
如上面的例子, 当执行B.j &#43;= 1后, &j的&#20540;变成2. &然后再实例1个对象b, 执行b.j &#43;= 1, 那么输出对象b的成员的&#20540;j就是3了.
其实因为对象b里面的静态成员j指针还是指向static 区的同1个内存地址.
也就是说类B的多个不同对象实际上共享同1个静态成员.
那么java里实例化1个具有静态成员的对象时, 会同时把静态成员的地址放入栈区.
6.3 静态函数不允许使用非静态成员.
这个也不难理解, 看上图, 静态函数g()是可以在实例化之前使用的, 但是如果g() 引用了非静态成员i. 那么当没有实例化对象b调用g()时, 就会找不到成员i的地址.
因为成员i必须在实例化之后才会被分配内存啊.
这个就是java不允许静态函数调用非静态成员的原因!
6.4 静态函数和非静态函数的区别.
的确, 静态函数与非静态函数的2进制代码都是存放在代码区里面..
int i = 10;
System.out.printf(&i is %d\n&, i);
看看上面里的例子.
非静态函数f() 是不带参数的.. 而且里面的成员i 也不带参数.
再看看c语言的结构体定义:
char name[16];
void (* A_prinf)(struct A *);
里面的函数指针必须带参数.
其实在java里面, 非静态函数已经隐藏了1个自带参数&this&.
因为上面说过了, 所有不同的实例化后的对象里面的成员内存都是不同的, 但是它们的函数都是指向代码区同1个函数.
那么调用这个函数时, 这个函数必须知道到底是哪个对象调用它, 在这里就是到底要输出哪个对象的成员i.
在底层里, 我任务非静态函数自带参数&this&这个隐藏指针.
但是静态函数不同, 因为它不允许使用非静态成员. 就无需&this&了.
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?}

我要回帖

更多关于 从参与主体的角度划分 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信