当前位置:首页 » 服务存储 » 内存存储模型
扩展阅读
webinf下怎么引入js 2023-08-31 21:54:13
堡垒机怎么打开web 2023-08-31 21:54:11

内存存储模型

发布时间: 2022-10-19 15:57:00

⑴ maya如何导出占内存小的模型

1、打开maya导入或者制作你需要进行保存的模型。
2、接着在次级菜单中选择“插件管理器并打开。
3、在菜单栏中选择“需要进行导出的模型。
4、然后选择保存的路径和文件的命名,按回车键就可以导出文件了。

⑵ java内存模型里面为什么要分主存和本地内存

你说的应该是主存和工作内存吧,主存是公共空间,基本可以类比为虚拟机模型中的堆,对象创建好了都是在主存里,所有线程都可以访问,工作内存是线程的私有内存,只有本线程可以访问,如果线程要操作主存中的某个对象,必须从主存中拷贝到工作内存,在对工作内存中的副本进行操作,操作后再写入主存,而不能对主存的对象直接操作

⑶ java课程分享java多线程的内存模型



硬件的内存模型

物理机并发处理的方案对于jvm的内存模型实现,也有很大的参考作用,毕竟jvm也是在硬件层上来做事情,底层架构也决定了上层的建筑建模方式。


计算机并发并非只是多个处理器都参与进来计算就可以了,会牵扯到一些列硬件的问题,最直接的就是要和内存做交互。但计算机的存储设备与处理器的预算速度相差太大,完全不能满足处理器的处理速度,怎么办,这就是后续加入的一层读写速度接近处理器运算速度的高速缓存来作为处理器和内存之间的缓冲。


高速缓存一边把使用的数据,从内存复制搬入,方便处理器快速运算,一边把运算后的数据,再同步到主内存中,如此处理器就无需等待了。


高速缓存虽然解决了处理器和内存的矛盾,但也为计算机带来了另一个问题:缓存一致性。特别是当多个处理器都涉及到同一块主内存区域的时候,将可能会导致各自的缓存数据不一致。


那么出现不一致情况的时候,以谁的为准?


为了解决这个问题,处理器和内存之间的读写的时候需要遵循一定的协议来操作,这类协议有:MSI、MESI、MOSI、Synapse、Firefly 以及 Dragon Protocol等。这就是上图中处理器、高速缓存、以及内存之间的处理方式。


另外除了高速缓存之外,为了充分利用处理器,处理器还会把输入的指令码进行乱序执行优化,只要保证输出一致,输入的信息可以乱序执行重组,所以程序中的语句计算顺序和输入代码的顺序并非一致。



JVM内存模型

上面我们了解了硬件的内存模型,以此为借鉴,我们看看jvm的内存模型。



jvm定义的一套java内存模型为了能够跨平台达到一致的内存访问效果,从而屏蔽掉了各种硬件和操作系统的内存访问差异。这点和c和c++并不一样,C和C++会直接使用物理硬件和操作系统的内存模型来处理,所以在各个平台上会有差异,这一点java不会。


java的内存模型规定了所有的变量都存储在主内存中,java课程http://www.kmbdqn.com/发现每个线程拥有自己的工作内存,工作内存保存了该线程使用到的变量的主内存拷贝,线程对变量所有操作,读取,赋值,都必须在工作内存中进行,不能直接写主内存变量,线程间变量值的传递均需要主内存来完成。


⑷ java内存模型的JMM简介

1)JSR133:
在Java语言规范里面指出了JMM是一个比较开拓性的尝试,这种尝试视图定义一个一致的、跨平台的内存模型,但是它有一些比较细微而且很重要的缺点。其实Java语言里面比较容易混淆的关键字主要是synchronized和volatile,也因为这样在开发过程中往往开发者会忽略掉这些规则,这也使得编写同步代码比较困难。
JSR133本身的目的是为了修复原本JMM的一些缺陷而提出的,其本身的制定目标有以下几个: 保留目JVM的安全保证,以进行类型的安全检查: 提供(out-of-thin-air safety)无中生有安全性,这样“正确同步的”应该被正式而且直观地定义 程序员要有信心开发多线程程序,当然没有其他办法使得并发程序变得很容易开发,但是该规范的发布主要目标是为了减轻程序员理解内存模型中的一些细节负担 提供大范围的流行硬件体系结构上的高性能JVM实现,现在的处理器在它们的内存模型上有着很大的不同,JMM应该能够适合于实际的尽可能多的体系结构而不以性能为代价,这也是Java跨平台型设计的基础 提供一个同步的习惯用法,以允许发布一个对象使他不用同步就可见,这种情况又称为初始化安全(initialization safety)的新的安全保证 对现有代码应该只有最小限度的影响 2)同步、异步【这里仅仅指概念上的理解,不牵涉到计算机底层基础的一些操作】:
在系统开发过程,经常会遇到这几个基本概念,不论是网络通讯、对象之间的消息通讯还是Web开发人员常用的Http请求都会遇到这样几个概念,经常有人提到Ajax是异步通讯方式,那么究竟怎样的方式是这样的概念描述呢?
同步:同步就是在发出一个功能调用的时候,在没有得到响应之前,该调用就不返回,按照这样的定义,其实大部分程序的执行都是同步调用的,一般情况下,在描述同步和异步操作的时候,主要是指代需要其他部件协作处理或者需要协作响应的一些任务处理。比如有一个线程A,在A执行的过程中,可能需要B提供一些相关的执行数据,当然触发B响应的就是A向B发送一个请求或者说对B进行一个调用操作,如果A在执行该操作的时候是同步的方式,那么A就会停留在这个位置等待B给一个响应消息,在B没有任何响应消息回来的时候,A不能做其他事情,只能等待,那么这样的情况,A的操作就是一个同步的简单说明。
异步:异步就是在发出一个功能调用的时候,不需要等待响应,继续进行它该做的事情,一旦得到响应了过后给予一定的处理,但是不影响正常的处理过程的一种方式。比如有一个线程A,在A执行的过程中,同样需要B提供一些相关数据或者操作,当A向B发送一个请求或者对B进行调用操作过后,A不需要继续等待,而是执行A自己应该做的事情,一旦B有了响应过后会通知A,A接受到该异步请求的响应的时候会进行相关的处理,这种情况下A的操作就是一个简单的异步操作。
3)可见性、可排序性
Java内存模型的两个关键概念:可见性(Visibility)和可排序性(Ordering)
开发过多线程程序的程序员都明白,synchronized关键字强制实施一个线程之间的互斥锁(相互排斥),该互斥锁防止每次有多个线程进入一个给定监控器所保护的同步语句块,也就是说在该情况下,执行程序代码所独有的某些内存是独占模式,其他的线程是不能针对它执行过程所独占的内存进行访问的,这种情况称为该内存不可见。但是在该模型的同步模式中,还有另外一个方面:JMM中指出了,JVM在处理该强制实施的时候可以提供一些内存的可见规则,在该规则里面,它确保当存在一个同步块时,缓存被更新,当输入一个同步块时,缓存失效。因此在JVM内部提供给定监控器保护的同步块之中,一个线程所写入的值对于其余所有的执行由同一个监控器保护的同步块线程来说是可见的,这就是一个简单的可见性的描述。这种机器保证编译器不会把指令从一个同步块的内部移到外部,虽然有时候它会把指令由外部移动到内部。JMM在缺省情况下不做这样的保证——只要有多个线程访问相同变量时必须使用同步。简单总结:
可见性就是在多核或者多线程运行过程中内存的一种共享模式,在JMM模型里面,通过并发线程修改变量值的时候,必须将线程变量同步回主存过后,其他线程才可能访问到。
可排序性提供了内存内部的访问顺序,在不同的程序针对不同的内存块进行访问的时候,其访问不是无序的,比如有一个内存块,A和B需要访问的时候,JMM会提供一定的内存分配策略有序地分配它们使用的内存,而在内存的调用过程也会变得有序地进行,内存的折中性质可以简单理解为有序性。而在Java多线程程序里面,JMM通过Java关键字volatile来保证内存的有序访问。 1)简单分析:
Java语言规范中提到过,JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个线程又存在自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。而在多核处理器下,大部分数据存储在高速缓存中,如果高速缓存不经过内存的时候,也是不可见的一种表现。在Java程序中,内存本身是比较昂贵的资源,其实不仅仅针对Java应用程序,对操作系统本身而言内存也属于昂贵资源,Java程序在性能开销过程中有几个比较典型的可控制的来源。synchronized和volatile关键字提供的内存中模型的可见性保证程序使用一个特殊的、存储关卡(memory barrier)的指令,来刷新缓存,使缓存无效,刷新硬件的写缓存并且延迟执行的传递过程,无疑该机制会对Java程序的性能产生一定的影响。
JMM的最初目的,就是为了能够支持多线程程序设计的,每个线程可以认为是和其他线程不同的CPU上运行,或者对于多处理器的机器而言,该模型需要实现的就是使得每一个线程就像运行在不同的机器、不同的CPU或者本身就不同的线程上一样,这种情况实际上在项目开发中是常见的。对于CPU本身而言,不能直接访问其他CPU的寄存器,模型必须通过某种定义规则来使得线程和线程在工作内存中进行相互调用而实现CPU本身对其他CPU、或者说线程对其他线程的内存中资源的访问,而表现这种规则的运行环境一般为运行该程序的运行宿主环境(操作系统、服务器、分布式系统等),而程序本身表现就依赖于编写该程序的语言特性,这里也就是说用Java编写的应用程序在内存管理中的实现就是遵循其部分原则,也就是前边提及到的JMM定义了Java语言针对内存的一些的相关规则。然而,虽然设计之初是为了能够更好支持多线程,但是该模型的应用和实现当然不局限于多处理器,而在JVM编译器编译Java编写的程序的时候以及运行期执行该程序的时候,对于单CPU的系统而言,这种规则也是有效的,这就是是上边提到的线程和线程之间的内存策略。JMM本身在描述过程没有提过具体的内存地址以及在实现该策略中的实现方法是由JVM的哪一个环节(编译器、处理器、缓存控制器、其他)提供的机制来实现的,甚至针对一个开发非常熟悉的程序员,也不一定能够了解它内部对于类、对象、方法以及相关内容的一些具体可见的物理结构。相反,JMM定义了一个线程与主存之间的抽象关系,其实从上边的图可以知道,每一个线程可以抽象成为一个工作内存(抽象的高速缓存和寄存器),其中存储了Java的一些值,该模型保证了Java里面的属性、方法、字段存在一定的数学特性,按照该特性,该模型存储了对应的一些内容,并且针对这些内容进行了一定的序列化以及存储排序操作,这样使得Java对象在工作内存里面被JVM顺利调用,(当然这是比较抽象的一种解释)既然如此,大多数JMM的规则在实现的时候,必须使得主存和工作内存之间的通信能够得以保证,而且不能违反内存模型本身的结构,这是语言在设计之处必须考虑到的针对内存的一种设计方法。这里需要知道的一点是,这一切的操作在Java语言里面都是依靠Java语言自身来操作的,因为Java针对开发人员而言,内存的管理在不需要手动操作的情况下本身存在内存的管理策略,这也是Java自己进行内存管理的一种优势。
[1]原子性(Atomicity):
这一点说明了该模型定义的规则针对原子级别的内容存在独立的影响,对于模型设计最初,这些规则需要说明的仅仅是最简单的读取和存储单元写入的的一些操作,这种原子级别的包括——实例、静态变量、数组元素,只是在该规则中不包括方法中的局部变量。
[2]可见性(Visibility):
在该规则的约束下,定义了一个线程在哪种情况下可以访问另外一个线程或者影响另外一个线程,从JVM的操作上讲包括了从另外一个线程的可见区域读取相关数据以及将数据写入到另外一个线程内。
[3]可排序性(Ordering):
该规则将会约束任何一个违背了规则调用的线程在操作过程中的一些顺序,排序问题主要围绕了读取、写入和赋值语句有关的序列。
如果在该模型内部使用了一致的同步性的时候,这些属性中的每一个属性都遵循比较简单的原则:和所有同步的内存块一样,每个同步块之内的任何变化都具备了原子性以及可见性,和其他同步方法以及同步块遵循同样一致的原则,而且在这样的一个模型内,每个同步块不能使用同一个锁,在整个程序的调用过程是按照编写的程序指定指令运行的。即使某一个同步块内的处理可能会失效,但是该问题不会影响到其他线程的同步问题,也不会引起连环失效。简单讲:当程序运行的时候使用了一致的同步性的时候,每个同步块有一个独立的空间以及独立的同步控制器和锁机制,然后对外按照JVM的执行指令进行数据的读写操作。这种情况使得使用内存的过程变得非常严谨!
如果不使用同步或者说使用同步不一致(这里可以理解为异步,但不一定是异步操作),该程序执行的答案就会变得极其复杂。而且在这样的情况下,该内存模型处理的结果比起大多数程序员所期望的结果而言就变得十分脆弱,甚至比起JVM提供的实现都脆弱很多。因为这样所以出现了Java针对该内存操作的最简单的语言规范来进行一定的习惯限制,排除该情况发生的做法在于:
JVM线程必须依靠自身来维持对象的可见性以及对象自身应该提供相对应的操作而实现整个内存操作的三个特性,而不是仅仅依靠特定的修改对象状态的线程来完成如此复杂的一个流程。
[4]三个特性的解析(针对JMM内部):
原子性(Atomicity):
访问存储单元内的任何类型的字段的值以及对其更新操作的时候,除开long类型和double类型,其他类型的字段是必须要保证其原子性的,这些字段也包括为对象服务的引用。此外,该原子性规则扩展可以延伸到基于long和double的另外两种类型:volatile long和volatile double(volatile为java关键字),没有被volatile声明的long类型以及double类型的字段值虽然不保证其JMM中的原子性,但是是被允许的。针对non-long/non-double的字段在表达式中使用的时候,JMM的原子性有这样一种规则:如果你获得或者初始化该值或某一些值的时候,这些值是由其他线程写入,而且不是从两个或者多个线程产生的数据在同一时间戳混合写入的时候,该字段的原子性在JVM内部是必须得到保证的。也就是说JMM在定义JVM原子性的时候,只要在该规则不违反的条件下,JVM本身不去理睬该数据的值是来自于什么线程,因为这样使得Java语言在并行运算的设计的过程中针对多线程的原子性设计变得极其简单,而且即使开发人员没有考虑到最终的程序也没有太大的影响。再次解释一下:这里的原子性指的是原子级别的操作,比如最小的一块内存的读写操作,可以理解为Java语言最终编译过后最接近内存的最底层的操作单元,这种读写操作的数据单元不是变量的值,而是本机码,也就是前边在讲《Java基础知识》中提到的由运行器解释的时候生成的Native Code。
可见性(Visibility):
当一个线程需要修改另外线程的可见单元的时候必须遵循以下原则: 一个写入线程释放的同步锁和紧随其后进行读取的读线程的同步锁是同一个从本质上讲,释放锁操作强迫它的隶属线程【释放锁的线程】从工作内存中的写入缓存里面刷新(专业上讲这里不应该是刷新,可以理解为提供)数据(flush操作),然后获取锁操作使得另外一个线程【获得锁的线程】直接读取前一个线程可访问域(也就是可见区域)的字段的值。因为该锁内部提供了一个同步方法或者同步块,该同步内容具有线程排他性,这样就使得上边两个操作只能针对单一线程在同步内容内部进行操作,这样就使得所有操作该内容的单一线程具有该同步内容(加锁的同步方法或者同步块)内的线程排他性,这种情况的交替也可以理解为具有“短暂记忆效应”。这里需要理解的是同步的双重含义:使用锁机制允许基于高层同步协议进行处理操作,这是最基本的同步;同时系统内存(很多时候这里是指基于机器指令的底层存储关卡memory barrier,前边提到过)在处理同步的时候能够跨线程操作,使得线程和线程之间的数据是同步的。这样的机制也折射出一点,并行编程相对于顺序编程而言,更加类似于分布式编程。后一种同步可以作为JMM机制中的方法在一个线程中运行的效果展示,注意这里不是多个线程运行的效果展示,因为它反应了该线程愿意发送或者接受的双重操作,并且使得它自己的可见区域可以提供给其他线程运行或者更新,从这个角度来看,使用锁和消息传递可以视为相互之间的变量同步,因为相对其他线程而言,它的操作针对其他线程也是对等的。 一旦某个字段被申明为volatile,在任何一个写入线程在工作内存中刷新缓存的之前需要进行进一步的内存操作,也就是说针对这样的字段进行立即刷新,可以理解为这种volatile不会出现一般变量的缓存操作,而读取线程每次必须根据前一个线程的可见域里面重新读取该变量的值,而不是直接读取。 当某个线程第一次去访问某个对象的域的时候,它要么初始化该对象的值,要么从其他写入线程可见域里面去读取该对象的值;这里结合上边理解,在满足某种条件下,该线程对某对象域的值的读取是直接读取,有些时候却需要重新读取。这里需要小心一点的是,在并发编程里面,不好的一个实践就是使用一个合法引用去引用不完全构造的对象,这种情况在从其他写入线程可见域里面进行数据读取的时候发生频率比较高。从编程角度上讲,在构造函数里面开启一个新的线程是有一定的风险的,特别是该类是属于一个可子类化的类的时候。Thread.start由调用线程启动,然后由获得该启动的线程释放锁具有相同的“短暂记忆效应”,如果一个实现了Runnable接口的超类在子类构造子执行之前调用了Thread(this).start()方法,那么就可能使得该对象在线程方法run执行之前并没有被完全初始化,这样就使得一个指向该对象的合法引用去引用了不完全构造的一个对象。同样的,如果创建一个新的线程T并且启动该线程,然后再使用线程T来创建对象X,这种情况就不能保证X对象里面所有的属性针对线程T都是可见的除非是在所有针对X对象的引用中进行同步处理,或者最好的方法是在T线程启动之前创建对象X。 若一个线程终止,所有的变量值都必须从工作内存中刷到主存,比如,如果一个同步线程因为另一个使用Thread.join方法的线程而终止,那么该线程的可见域针对那个线程而言其发生的改变以及产生的一些影响是需要保证可知道的。 注意:如果在同一个线程里面通过方法调用去传一个对象的引用是绝对不会出现上边提及到的可见性问题的。JMM保证所有上边的规定以及关于内存可见性特性的描述——一个特殊的更新、一个特定字段的修改都是某个线程针对其他线程的一个“可见性”的概念,最终它发生的场所在内存模型中Java线程和线程之间,至于这个发生时间可以是一个任意长的时间,但是最终会发生,也就是说,Java内存模型中的可见性的特性主要是针对线程和线程之间使用内存的一种规则和约定,该约定由JMM定义。
不仅仅如此,该模型还允许不同步的情况下可见性特性。比如针对一个线程提供一个对象或者字段访问域的原始值进行操作,而针对另外一个线程提供一个对象或者字段刷新过后的值进行操作。同样也有可能针对一个线程读取一个原始的值以及引用对象的对象内容,针对另外一个线程读取一个刷新过后的值或者刷新过后的引用。
尽管如此,上边的可见性特性分析的一些特征在跨线程操作的时候是有可能失败的,而且不能够避免这些故障发生。这是一个不争的事实,使用同步多线程的代码并不能绝对保证线程安全的行为,只是允许某种规则对其操作进行一定的限制,但是在最新的JVM实现以及最新的Java平台中,即使是多个处理器,通过一些工具进行可见性的测试发现其实是很少发生故障的。跨线程共享CPU的共享缓存的使用,其缺陷就在于影响了编译器的优化操作,这也体现了强有力的缓存一致性使得硬件的价值有所提升,因为它们之间的关系在线程与线程之间的复杂度变得更高。这种方式使得可见度的自由测试显得更加不切实际,因为这些错误的发生极为罕见,或者说在平台上我们开发过程中根本碰不到。在并行程开发中,不使用同步导致失败的原因也不仅仅是对可见度的不良把握导致的,导致其程序失败的原因是多方面的,包括缓存一致性、内存一致性问题等。
可排序性(Ordering):
可排序规则在线程与线程之间主要有下边两点: 从操作线程的角度看来,如果所有的指令执行都是按照普通顺序进行,那么对于一个顺序运行的程序而言,可排序性也是顺序的 从其他操作线程的角度看来,排序性如同在这个线程中运行在异步方法中的一个“间谍”,所以任何事情都有可能发生。唯一有用的限制是同步方法和同步块的相对排序,就像操作volatile字段一样,总是保留下来使用 【*:如何理解这里“间谍”的意思,可以这样理解,排序规则在本线程里面遵循了第一条法则,但是对其他线程而言,某个线程自身的排序特性可能使得它不定地访问执行线程的可见域,而使得该线程对本身在执行的线程产生一定的影响。举个例子,A线程需要做三件事情分别是A1、A2、A3,而B是另外一个线程具有操作B1、B2,如果把参考定位到B线程,那么对A线程而言,B的操作B1、B2有可能随时会访问到A的可见区域,比如A有一个可见区域a,A1就是把a修改称为1,但是B线程在A线程调用了A1过后,却访问了a并且使用B1或者B2操作使得a发生了改变,变成了2,那么当A按照排序性进行A2操作读取到a的值的时候,读取到的是2而不是1,这样就使得程序最初设计的时候A线程的初衷发生了改变,就是排序被打乱了,那么B线程对A线程而言,其身份就是“间谍”,而且需要注意到一点,B线程的这些操作不会和A之间存在等待关系,那么B线程的这些操作就是异步操作,所以针对执行线程A而言,B的身份就是“异步方法中的‘间谍’。】
同样的,这仅仅是一个最低限度的保障性质,在任何给定的程序或者平台,开发中有可能发现更加严格的排序,但是开发人员在设计程序的时候不能依赖这种排序,如果依赖它们会发现测试难度会成指数级递增,而且在复合规定的时候会因为不同的特性使得JVM的实现因为不符合设计初衷而失败。
注意:第一点在JLS(Java Language Specification)的所有讨论中也是被采用的,例如算数表达式一般情况都是从上到下、从左到右的顺序,但是这一点需要理解的是,从其他操作线程的角度看来这一点又具有不确定性,对线程内部而言,其内存模型本身是存在排序性的。【*:这里讨论的排序是最底层的内存里面执行的时候的NativeCode的排序,不是说按照顺序执行的Java代码具有的有序性质,本文主要分析的是JVM的内存模型,所以希望读者明白这里指代的讨论单元是内存区。】 JMM最初设计的时候存在一定的缺陷,这种缺陷虽然现有的JVM平台已经修复,但是这里不得不提及,也是为了读者更加了解JMM的设计思路,这一个小节的概念可能会牵涉到很多更加深入的知识,如果读者不能读懂没有关系先看了文章后边的章节再返回来看也可以。
1)问题1:不可变对象不是不可变的
学过Java的朋友都应该知道Java中的不可变对象,这一点在本文最后讲解String类的时候也会提及,而JMM最初设计的时候,这个问题一直都存在,就是:不可变对象似乎可以改变它们的值(这种对象的不可变指通过使用final关键字来得到保证),(Publis Service Reminder:让一个对象的所有字段都为final并不一定使得这个对象不可变——所有类型还必须是原始类型而不能是对象的引用。而不可变对象被认为不要求同步的。但是,因为在将内存写方面的更改从一个线程传播到另外一个线程的时候存在潜在的延迟,这样就使得有可能存在一种竞态条件,即允许一个线程首先看到不可变对象的一个值,一段时间之后看到的是一个不同的值。这种情况以前怎么发生的呢?在JDK 1.4中的String实现里,这儿基本有三个重要的决定性字段:对字符数组的引用、长度和描述字符串的开始数组的偏移量。String就是以这样的方式在JDK 1.4中实现的,而不是只有字符数组,因此字符数组可以在多个String和StringBuffer对象之间共享,而不需要在每次创建一个String的时候都拷贝到一个新的字符数组里。假设有下边的代码:
String s1 = /usr/tmp;
String s2 = s1.substring(4); // /tmp
这种情况下,字符串s2将具有大小为4的长度和偏移量,但是它将和s1共享“/usr/tmp”里面的同一字符数组,在String构造函数运行之前,Object的构造函数将用它们默认的值初始化所有的字段,包括决定性的长度和偏移字段。当String构造函数运行的时候,字符串长度和偏移量被设置成所需要的值。但是在旧的内存模型中,因为缺乏同步,有可能另一个线程会临时地看到偏移量字段具有初始默认值0,而后又看到正确的值4,结果是s2的值从“/usr”变成了“/tmp”,这并不是我们真正的初衷,这个问题就是原始JMM的第一个缺陷所在,因为在原始JMM模型里面这是合理而且合法的,JDK 1.4以下的版本都允许这样做。
2)问题2:重新排序的易失性和非易失性存储
另一个主要领域是与volatile字段的内存操作重新排序有关,这个领域中现有的JMM引起了一些比较混乱的结果。现有的JMM表明易失性的读和写是直接和主存打交道的,这样避免了把值存储到寄存器或者绕过处理器特定的缓存,这使得多个线程一般能看见一个给定变量最新的值。可是,结果是这种volatile定义并没有最初想象中那样如愿以偿,并且导致了volatile的重大混乱。为了在缺乏同步的情况下提供较好的性能,编译器、运行时和缓存通常是允许进行内存的重新排序操作的,只要当前执行的线程分辨不出它们的区别。(这就是within-thread as-if-serial semantics[线程内似乎是串行]的解释)但是,易失性的读和写是完全跨线程安排的,编译器或缓存不能在彼此之间重新排序易失性的读和写。遗憾的是,通过参考普通变量的读写,JMM允许易失性的读和写被重排序,这样以为着开发人员不能使用易失性标志作为操作已经完成的标志。比如:
Map configOptions;
char[] configText;
volatile boolean initialized = false;
// 线程1
configOptions = new HashMap();
configText = readConfigFile(filename);
processConfigOptions(configText,configOptions);
initialized = true;
// 线程2
while(!initialized)
sleep();
这里的思想是使用易失性变量initialized担任守卫来表明一套别的操作已经完成了,这是一个很好的思想,但是不能在JMM下工作,因为旧的JMM允许非易失性的写(比如写到configOptions字段,以及写到由configOptions引用Map的字段中)与易失性的写一起重新排序,因此另外一个线程可能会看到initialized为true,但是对于configOptions字段或它所引用的对象还没有一个一致的或者说当前的针对内存的视图变量,volatile的旧语义只承诺在读和写的变量的可见性,而不承诺其他变量,虽然这种方法更加有效的实现,但是结果会和我们设计之初大相径庭。

⑸ 关于C#访问内存

与C#内存管理对比分析
与C#管理内存方式概述

C#最大的一个改进其实就是对内存访问与管理方法的改进。在.NET中内存的管理是全权委托给垃圾回收器,由垃圾回收器来决定何时该释放内存空间。现在普遍采用两种技术来释放程序动态申请的系统内存:首先是以C++为代表的必须以手工方式使应用程序代码完成这些工作,让对象维护引用计数。然后是以.NET以及Java使用的垃圾回收器来完成内存释放工作。

在C++中让应用程序代码负责释放内存是低级、高性能的语言使用技术。这种技术非常有效,且可以让资源在不需要时就释放,因为这种技术可以直接访问内存,所以其最大的缺点是可能导致错误。而且如果程序员的记性不太好的话,也会常常忘记释放内存而导致内存泄漏。

在C#中内存的管理是依靠垃圾回收器,垃圾回收器是一个清理内存的程序。所有采用new关键字申请的动态内存空间都会分配到堆上,当.NET检测到给定过程的堆已经满时,需要清理时,就会调用垃圾回收器。垃圾回收器将采用垃圾回收算法将那些不再被引用的对象所占用的内存空间释放掉。显然由于程序员无法直接控制内存的释放,所开发出的软件性能和效率上一定会受到很大的影响。不过这种影响是随着计算机硬件技术的发展日益缩小的。

究竟是C++中直接由程序员管理内存好,还是像.NET中那样由单独一个程序来统一管理好呢?这个问题是公说公有理,婆说婆有理。但是我相信随着计算机硬件技术不断的发展、存储器空间越来越大、软件的复杂性和软件健壮性要求的不断提高,程序员直接管理内存的方式必将会退出历史舞台。当今的程序员不必再为该如何把程序分块放到容量有限的内存中运行而担心,因为这项任务已经交给了操作系统的虚拟内存来管理。相信不久将来人们也会习惯完全交由诸如垃圾回收器一类的专门程序来管理程序申请的内存空间。

中内存的分配方式

在C++中内存的分配方式大致有三种:

(1) 从静态存储区域分配。内存在程序编译的时候已经分配完毕了。并且这块内存中的所有数据在程序的整个运行期间都始终存在的。例如:全局变量,static变量等等。

(2) 在栈上创建。在函数执行期间,无论什么时候到达一个特殊的执行点(左花括号)时,存储单元都可以在栈上被创建。出了执行点(右花括号),这个存储单元自动被释放。这些栈分配运算内置在处理器的指令集中,非常有效,并且不存在失败的危险,但是可供分配的内存容量很有限。

(3) 存储单元也可以从一块称为堆(也被称为自由存储单元)的地方分配,从堆(heap)上分配,亦成为动态分配。在C++中,程序在运行期间可以用malloc或者new申请任意数量的内存,程序员自己掌握释放内存的适当时机(使用free或者delete)。动态内存的生存期间是由程序员决定,使用非常灵活,但也最容易产生问题。

用户管理内存常出现的问题

在C++中,我们必须非常小心第三种内存分配方式,因为内存的分配和释放都得由程序员来控制,一不小心就会出错。下面我就分析下在C++中,由于第三种内存分配方式而导致的一些常见的内存泄漏以及一系列的指针问题。

#include<iostream.h>

#include<string.h>

void GetMemory(char *p, int num)

{

p = new char[12];

}

void main()

{

char *str = NULL;

GetMemory(str,100);

strcpy(str,"hello");

}

注意到函数GetMemory(char *p,int num),中的第一个字符型指针参数。写程序的人的本意可能是希望通过此函数为str指针申请内存。但事实上却是str并不会得到所期望得到的内存,str依旧是NULL。因为函数GetMemory(char *p,int num)中所得到的只是指针str的一个副本使得p = str ,他们所存储的内容均是指向同一个内存的地址,但是由于p申请了新的内存,但str指针的值并没被改变,所以函数GetMemory并不能得到任何有用的东西。并且由于每执行一次GetMemory就会泄漏一块内存,因为没有使用free释放内存。

#include<iostream>

using namespace std;

class X

{

public:

int *ptrArray;

int size;

X(int *ptr , int size)

{

ptrArray = new int[size]; //(A)

for(int i=0;i<size;i++)ptrArray[i]=ptr[i];

}

};

int main()

{

int arrayData[100]={0};

X *bill = new X(arrayData,100); //(B)

delete bill; //(C)

return 0;

}

例如上面程序中的X类,程序员忘记了编写析构函数来释放在类中所动态申请内存空间。注意到程序第B行代码处,声明了一个X类的对象指针bill。然后在代码第A行处代码动态申请了一段内存空间,并且把数组arrayData中的数值都复制到类中。

接着在代码第C行处,我们删除了指针bill所指向的内存空间。但是类中第A行所申请的内存空间并不会被删除,所以将造成内存泄漏。

#include<iostream.h>

#include<string.h>

void main()

{

char *pointer = new char[100];

strcpy(pointer,"Hello , I am Howells !");

cout<<pointer<<endl;

cout<<"Before call delete function, the address of pointer is : "<<&pointer<<endl;

delete pointer; //(A)

cout<<"After call delete function, the address of pointer is : "<<&pointer<<endl;

//其间程序非常长,程序员也许忘记了p所指向的内存空间已经释放。

if(pointer != NULL)

{

strcpy(pointer, "world"); // (B)

}

}

程序运行结果:

Hello , I am Howells !

Before call delete function, the address of pointer is : 0x0013FF7C

After call delete function, the address of pointer is : 0x0013FF7C

Press any key to continue

注意到程序中第A行代码,虽然在第A行代码对pointer指针进行了delete操作,但是delete方法只是会释放掉该地址所指向的内存,而pointer指针仍然指向原来所指向的内存地址。通常由于程序比较长,程序员有时可能记不住pointer所指向的内存是否被释放掉了,所以我们会使用一个语句if(pointer != NULL)来进行测试,但是很遗憾的是,pointer并不是NULL,它指向一块不合法的内存单元。第B行的代码在VC++6.0是可行的,也就是说strcpy操作会将一块不属于自己的一块内存单元赋值,这非常可怕会对其他程序造成潜在的危害。

#include<iostream.h>

class A

{

public:

void Func(void){cout<<"A::func() " <<endl;}

int I;

};

A *Test(void)

{

A a;

return &a; / /(A)

}

void main()

{

A *p = NULL;

p = Test();

p->Func();

}

在Test函数中我们声明了一个对象a,然后返回对象a的地址。我们知道由于a是临时变量,所以当函数出了执行点(右花括号)时,a被退栈了,但是a所在的存储单元并没有被清除掉。所以仍然可以通过指针p来访问函数Func()。这显然将造成潜在的危险。

中内存管理机制

中动态内存分配方式
在C#中对内存的管理是依靠.NET 垃圾回收器来完成的,垃圾回收器为高速的分配服务提供了很好的内存使用机制。它可以恢复正在运行中的应用程序需要的内存。垃圾回收器负责清理内存,当.NET检测到给定过程的堆已满时,需要清理时,就需要调用垃圾回收器,下面我将详细介绍.NET的内存分配机制。和C++一样,在.NET中用户所申请的动态内存空间将被分配到堆上,不同的是在.NET上的堆是托管堆。自动内存管理是公共语言运行库在托管执行过程过程中提供的服务之一。公共语言运行库的垃圾回收器为应用程序管理内存的分配和释放。对开发人员而言,这就意味着在开发托管应用程序时不必编写执行内存管理任务的代码。

在C#中大致有三种不同的存储单元:

(1) Managed Heap:这是动态配置(Dynamic Allocation)的存储单元,由Gargage Collector在执行时自动管理,整个进程将公用一个Managed Heap。

(2) Call Stack:这是由.NET CLR在执行时自动管理的存储单元,每个Thread都有自己专门的Call Stack。每呼叫一次method,就会使得Call Stack上多一个Record Frame;方法执行完毕之后,此Record Frame会被丢弃。这一点与C++类似。

(3) Evaluation Stack:这是由.NET CLR在执行时自动管理的存储单元,每个Thread都有自己专门的Evaluation Stack。这个堆栈也叫做堆叠式虚拟机,既程序执行时的资料都是先放在堆叠中,再进行运算。

其三种存储单元的物理结构模型如下:

图1-1

下图是托管堆的简化模型。

图1-2

在C#中动态分配内存时,.NET是采用如下规则进行内存管理的。

(1) 堆被划分为代,以便只需查找堆的一小部分就能清除大多数垃圾。

(2) 同代中的对象大体上均为同龄。

(3) 代的编号越高,表示堆的这一片区域所包含的对象越老,这些对象就越有可能是稳定的。最老的对象位于最低的地址内,而新的对象则创建在增加的地址内。

(4) 新对象的分配指针标记了内存的已使用(已分配)内存区域和未使用(可用)内存区域之间的边界。

(5) 通过删除死对象并将活对象转移到堆的低地址末尾,堆周期性地进行压缩。这就扩展了在创建新对象的图表底部的未使用区域。

(6) 对象在内存中的顺序仍然是创建它们的顺序,以便于定位。

(7) 在堆中,对象之间永远不会有任何空隙。

(8) 只有某些可用空间是已提交的。需要时,操作系统会从“保留的”地址范围中分配更多的内存。

(9) 所有可进行垃圾回收的对象都分配在一个连续的地址空间范围内。

中动态内存回收机制
在C#中大致有三种垃圾回收机制:完全回收、部分回收、使代与写入屏障配合工作。

图1-3

1) 完全回收

在完全回收时,程序将停止执行,并且到托管堆中找到所有的根。这些根以各种形式出现,它们可以是堆栈上的指针或者指向堆中的全局变量。从根开始,我们访问每个对象,并沿途追溯包含在每个被访问对象内的每个对象指针,指针用于标记这些对象。一旦找出了不可达到的对象,我们就需要回收空间以便随后使用;在这里,回收器的目标是要将活的对象向上移动,并清除浪费的空间。在执行过程停止的情况下,回收器可以安全地移动所有这些对象,并修复所有指针,以便所有对象在新的位置上被正确链接。幸存的对象将被提升到下一代的编号(就是说,代的边界得到更新),并且执行过程可以恢复。

2) 部分回收

假设最近执行了一次完全回收,程序继续执行,在发生足够多的分配之后,内存管理系统决定是进行回收的时候了。假设我们非常的幸运,自从上一次回收以后,在我们运行的所有时间里,我们根本没有对任何较老的对象执行写操作,而只是对新分配的(第零代 (gen0))对象执行了写操作。因此,当执行垃圾回收的时候,只需要检查所有的根,如果有任何根指向旧对象,就忽略这些对象。而对于其他根(指向 gen0 的根)我们进行追溯所有指针。一旦我们发现有内部指针指回较老的对象,我们就忽略它。完成以后,我们就访问完gen0中的所有活的对象,但没有访问过任何老的对象(gen1,gen2对象)。接着就对gen0区域进行回收空间处理。

3) 使代与写入屏障配合工作

但事实上,部分回收算法的充分条件是不太可能的,因为总会有一些较老的对象肯定会发生更改。发生这种情况时,.NET使用另外一种辅助的数据结构来配合部分回收算法。card table的数据结构来记住脏对象的位置;牌桌中的每个位代表堆中的一个内存范围,比如说是 128 个字节。程序每次将对象写入某个地址时,写入屏障代码必须计算哪个 128 字节块被写入,然后在牌桌中设置相应的位。

如果我们正在执行一次 gen0 垃圾回收,我们可以使用上面讨论的算法(忽略指向较老代的任何指针),但一旦我们完成该操作,那么我们还必须查找位于牌桌中被标记为已修改的块中的每个对象中的每个对象指针。我们必须像对待根一样对待这些指针。如果我们同样地考虑这些指针,那么我们将准确无误地只回收 gen0 对象。

中动态分配内存注意事项
我们了解了.NET垃圾回收器的工作原理后,就可以针对它来制定出编写高效程序的准则:

(1) 最大程度地减少对象指针的写入次数,尤其是对较老对象的写入。

(2) 减少数据结构中的指针密度。第一,将有很多对象写入。第二,当回收该数据结构的时间到来时,您将使垃圾回收器追溯所有这些指针,如果需要,还要随着对象的到处移动全部更改这些指针。如果您的数据结构的生命周期很长,并且不会有很多更改,那么,当完全回收发生时(在 gen2 级别),回收器只需要访问所有这些指针。但如果您创建的此类结构的生命周期短暂(就是说,作为处理事务的一部分),那么您将支付比正常情况下大出很多的开销。

(3) 如果可以通过只增加少量的程序复杂性,则应该避免过多的动态内存临时分配。如在比较两个字符串的时候,应该避免使用String.Split。因为 String.Split 将创建一个字符串数组,这意味着原来在关键字字符串中的每个关键字都有一个新的字符串对象,再加上该数组也有一个对象。现在,您的两行比较函数就创建了数量非常多的临时对象。垃圾回收器突然因为您而负载大增,甚至使用最智能的回收方案也会有很多垃圾需要清理。最好编写一个根本不需要分配内存的比较函数。

(4) 尽量避免使用析构函数。一个带有析构函数的对象意味着它是需要终结的对象。垃圾回收器第一次遇到应死而未死但仍需要终结的对象时,它必须在这个时候放弃回收该对象的空间的尝试。而是将对象添加到需要终结的对象列表中,而且,回收器随后必须确保对象内的所有指针在终结完成之前仍然继续有效。这基本上等同于说,从回收器的观察角度来看,需要终结的每个对象都像是临时的根对象。回收完成后,终结线程将遍历需要终结的对象列表,并调用终结器。该操作完成时,对象再一次成为死对象,并且将以正常方式被自然回收。

与C++内存优缺点对比总结

在对C#以及C++的内存管理机制分析完毕以后,我们可以对比出它们间的优缺点如下:

(1) C#内存分配比C++更加有效率:因为不需要像传统分配器那样搜索可用的内存块;所有需要发生的操作只是需要移动在可用的和已分配的区域之间的边界。

(2) C#清理内存机制可以使得程序员无需为管理内存而单独编写在大多数时候都是重复的代码(内存紧缩)。

(3) 在相当出色的程序员编写的程序中没有任何操纵与内存相关的错误代码(通常非常难), 利用C++中程序员直接控制内存方式肯定比C#利用垃圾回收器更加有效。因为程序员通常更加清楚何时回收内存是最佳时刻。

(4) 由于C#中由垃圾回收器回收无用已分配的内存快,所以不会发生由于程序员疏忽而产生的内存泄漏。当然也可能会丢失一些资源,如忘记关闭与数据库的连接等。

http://blog.csdn.net/hardwayboy/archive/2009/08/31/4499242.aspx

⑹ 关于java内存模型中read和load操作的一个问题,急求大神指点

我这是么个理解。
所谓的“线程的工作内存”就是线程栈,这个栈大小是通过 vm参数 -xss 来设置的。不同版本的jdk对应的大小不一样,有的是512k。上面的read操作和load操作,我理解实际整体是在一个操作步骤中,所谓的read应是根据引用地址从堆中(即通过-Xms -Xmx 参数来设置的)读出来;然后load过程,就是在线程运算需要使用此变量时,将变量的具体值(即引用指向的内存地址存储的数据)压入线程栈。
这也就是为什么如果在并发时,同一个变量不加锁会导致结果不可预料的问题。因为不同线程运算时,都会将变量值压入当前线程栈而不是直接对堆上变更直接进行操作,这样就存在一个时间窗口会导致不同的线程对堆中同一个变量值的不同“副本”进行操作,从而导致结果不可预料,也就引用了并发冲突问题。

⑺ 解释概念:主存、辅存、Cache、RAM、SRAM、DRAM、ROM、PROM、EPROM、EEPROM、CDROM、Flash Memory。

主存,又称内存,是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。 内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。 内存是由内存芯片、电路板、金手指等部分组成的。

辅存狭义上是我们平时讲的硬盘。科学地说是外部存储器(需要通过I/O系统与之交换数据,又称为辅助存储器)。存储容量大、成本低、存取速度慢,以及可以永久地脱机保存信息。主要包括磁表面存储器、软盘存储器、磁带存储设备、光盘存储设备。

cache 高速缓冲存储器 一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。存储器的高速缓冲存储器存储了频繁访问的 RAM 位置的内容及这些数据项的存储地址。当处理器引用存储器中的某地址时,高速缓冲存储器便检查是否存有该地址。如果存有该地址,则将数据返回处理器;如果没有保存该地址,则进行常规的存储器访问。因为高速缓冲存储器总是比主RAM 存储器速度快,所以当 RAM 的访问速度低于微处理器的速度时,常使用高速缓冲存储器。

RAM(Random Access Memory)随机存取存储器 主要用于存储计算机运行时的程序和数据,需要执行的程序或者需要处理的数据都必须先装入RAM内,是指既可以从该设备读取数据,也可以往里面写数据。RAM的特点是:计算机通电状态下RAM中的数据可以反复使用,只有向其中写入新数据时才被更新;断电后RAM中的数据随之消失。

SRAM是英文Static RAM的缩写,它是一种具有静止存取功能的内存,不需要刷新电路即能保存它内部存储的数据。SRAM不需要刷新电路即能保存它内部存储的数据。
而DRAM(Dynamic Random Access Memory)每隔一段时间,要刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,且功耗较大。所以在主板上SRAM存储器要占用一部分面积。

ROM(Read Only Memory)只读存储器,是指只能从该设备中读取数据而不能往里面写数据的存储器。Rom中的数据是由设计者和制造商事先编好固化在里面的一些程序,使用者不能随意更改。ROM主要用于检查计算机系统的配置情况并提供最基本的输入输出(I/O)程序,如存储BIOS参数的CMOS芯片。Rom的特点是计算机断电后存储器中的数据仍然存在。

PROM (Programmable Read-Only Memory)-可编程只读存储器,也叫One-Time Programmable (OTP)ROM“一次可编程只读存储器”,是一种可以用程序操作的只读内存。最主要特征是只允许数据写入一次,如果数据烧入错误只能报废。

EPROM(Erasable Programmable ROM,可擦除可编程ROM)芯片可重复擦除和写入,解决了PROM芯片只能写入一次的弊端。EPROM芯片有一个很明显的特征,在其正面的陶瓷封装上,开有一个玻璃窗口,透过该窗口,可以看到其内部的集成电路,紫外线透过该孔照射内部芯片就可以擦除其内的数据,完成芯片擦除的操作要用到EPROM擦除器。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12—24V,随不同的芯片型号而定)。EPROM的型号是以27开头的,如27C020(8*256K)是一片2M Bits容量的EPROM芯片。EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。 EPROM芯片在空白状态时(用紫外光线擦除后),内部的每一个存储单元的数据都为1(高电平)。

EEPROM (Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器--一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。EEPROM(电可擦写可编程只读存储器)是可用户更改的只读存储器(ROM),其可通过高于普通 EEPROM
电压的作用来擦除和重编程(重写)。不像EPROM芯片,EEPROM不需从计算机中取出即可修改。在一个EEPROM中,当计算机在使用的时候是可频繁地重编程的,EEPROM的寿命是一个很重要的设计考虑参数。EEPROM的一种特殊形式是闪存,其应用通常是个人电脑中的电压来擦写和重编程。 EEPROM,一般用于即插即用(Plug & Play)。 常用在接口卡中,用来存放硬件设置数据。 也常用在防止软件非法拷贝的"硬件锁"上面。

CD-ROM(Compact Disc Read-Only Memory)即只读光盘,是一种在电脑上使用的光盘。这种光盘只能写入数据一次,信息将永久保存在光盘上,使用时通过光盘驱动器读出信息。CD的格式最初是为音乐的存储和回放设计的,1985年,由SONY和飞利浦制定的黄皮书标准使得这种格式能够适应各种二进制数据。有些CD-ROM既存储音乐,又存储计算机数据,这种CD-ROM的音乐能够被CD播放器播放,计算机数据只能被计算机处理。

Flash Memory,也称闪存(Flash Memory)是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器,数据删除不是以单个的字节为单位而是以固定的区块为单位(注意:NOR Flash 为字节存储。),区块大小一般为256KB到20MB。闪存是电子可擦除只读存储器(EEPROM)的变种,EEPROM与闪存不同的是,它能在字节水平上进行删除和重写而不是整个芯片擦写,这样闪存就比EEPROM的更新速度快。由于其断电时仍能保存数据,闪存通常被用来保存设置信息,如在电脑的BIOS(基本输入输出程序)、PDA(个人数字助理)、数码相机中保存资料等

⑻ jvm中线程本地内存是真实存在的,还是一个抽象概念

jvm内存模型:Java代码是运行在Java虚拟机之上的,由Java虚拟机通过解释执行(解释器)或编译执行(即时编译器)来完成,故Java内存模型,也就是指Java虚拟机的运行时内存模型。

运行时内存模型,分为线程私有和共享数据区两大类,其中线程私有的数据区包含程序计数器、虚拟机栈、本地方法区,所有线程共享的数据区包含Java堆、方法区,在方法区内有一个常量池。java运行时的内存模型图,如下:

从图中,可知内存分为线程私有和共享两大类:

(1)线程私有区,包含以下3类:
程序计数器,记录正在执行的虚拟机字节码的地址;
虚拟机栈:方法执行的内存区,每个方法执行时会在虚拟机栈中创建栈帧;
本地方法栈:虚拟机的Native方法执行的内存区;

(2)线程共享区,包含以下2类
Java堆:对象分配内存的区域;
方法区:存放类信息、常量、静态变量、编译器编译后的代码等数据;
常量池:存放编译器生成的各种字面量和符号引用,是方法区的一部分。

楼主提到的Java栈,一般而言是指图中的虚拟机栈,在代码中的方法调用过程中,往往需要从一个方法跳转到另一个方法,执行完再返回,那么在跳转之前需要在当前方法的基本信息压入栈中保存再跳转。

三、关于寄存器的问题

对于java最常用的虚拟机,sun公司提供的hotspot虚拟机,是基于栈的虚拟机;而对于android的虚拟机,则采用google提供的dalvik,art两种虚拟机,在android 5.0以后便默认采用art虚拟机,这是基于寄存器的虚拟机。 楼主问的是jvm(即java vm),这是基于栈的虚拟机。那么关于虚拟机栈,这块内存的内容,我们再进一步详细分析,如下图:

可以看到,在虚拟机栈有一帧帧的 栈帧组成,而栈帧包含局部变量表,操作栈等子项,那么线程在运行的时候,代码在运行时,是通过程序计数器不断执行下一条指令。真正指令运算等操作时通过控制操作栈的操作数入栈和出栈,将操作数在局部变量表和操作栈之间转移。

⑼ 玉溪电脑培训学校告诉你Java内存模型原理

这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关实现规则,环环相扣,希望读者看完这篇文章后能对Java内存模型体系产生一个相对清晰的理解,知其然知其所以然。

内存模型产生背景

在介绍Java内存模型之前,java课程http://www.kmbdqn.cn/认为应该先了解一下物理计算机中的并发问题,理解这些问题可以搞清楚内存模型产生的背景。

物理机遇到的并发问题与虚拟机中的情况有不少相似之处,物理机的解决方案对虚拟机的实现有相当的参考意义。

物理机的并发问题

硬件的效率问题

计算机处理器处理绝大多数运行任务都不可能只靠处理器“计算”就能完成,处理器至少需要与内存交互,如读取运算数据、存储运算结果,这个I/O操作很难消除(无法仅靠寄存器完成所有运算任务)。

由于计算机的存储设备与处理器的运算速度有几个数量级的差距,为了避免处理器等待缓慢的内存完成读写操作,现代计算机系统通过加入一层读写速度尽可能接近处理器运算速度的高速缓存。

缓存作为内存和处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步回内存之中。

缓存一致性问题

基于高速缓存的存储系统交互很好的解决了处理器与内存速度的矛盾,但是也为计算机系统带来更高的复杂度,因为引入了一个新问题:缓存一致性。

在多处理器的系统中(或者单处理器多核的系统),每个处理器(每个核)都有自己的高速缓存,而它们有共享同一主内存(MainMemory)。

当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。

为此,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议进行操作,来维护缓存的一致性。


⑽ C中指针变量何时需要初始化malloc

首先你要明白什么是指针,指针是用来操作内存的。那么指针又如何操作内存呢?在C语言里可以定义指针变量,这个指针变量里可以存储内存的地址,一个32位的无符号整型值。它就像普通的int, double型变量一样。以下面为例说明:
int iMax = 1;
int * pMax = NULL;
我们定义了一个int型的变量iMax 和一个int型的指针变量pMax,并对他们进行了初始化。这里iMax的值为1; pMax的值为NULL,也就是一个无符号整形0。注意NULL是一个宏,代表0。现在pMax的值为NULL,一般来讲0也是一块内存地址, 我们也可以操作。注意这个NULL你现在认为他表示无效即可。用来给指针进行初始化。其它的先不用管。
现在我们来使用pMax, 如果要使用pMax, 那么就要对pMax赋值,使它指向一块内存。我们这里定义指针的类型为一个指向一个int型值的指针变量。所以可以将iMax的地址赋值给pMax。注意,不管什么样的类型的指针变量,其值都是一个unsigned int型的值,表示内存的地址。理解这一点很重要。为什么需要定义指针所指向的类型呢。如 char *, int * , double *型的指针,原因是我们使用指针是为了操作存储在内存中的特定类型的值。如果没有定义指针的类型,那我们在操作内存时,只能一个字节, 一个字节使用。这样的指针没有什么意义,也许你还不太理解。但多应用就能明白这一点。
现在我们来给pMax赋值,然后操作它.
pMax = &iMax;
好了,可以使用pMax了。就像使用iMax一样用它。不过你得在它前面加个指针运算符'*';
*pMax = *pMax + 2;
现在的pMax指针变量中存储的是iMax变量地址的值。对*pMax操作, 就是对iMax操作。现在*pMax = 3 , iMax = 3;
如果说我临时需要一块内存,这块内存用来存储n个int的变量。我就需要使用malloc为pMax分配一块内存。可以这样做:
pMax = malloc(sizeof(int) * n);
if (pMax == NULL) // 错误处理
{ TODO...}
这样我们就为pMax分配了一块内存大小为sizeof(int) *n 字节的内存。这里malloc返回一个指向这块内存的首地址并将它赋给了int型指针变量pMax.
好了,pMax已经可以使用了。我们需要对它进行初始化。这个可以使用memset函数
memset(pMax, 0 , sizeof(int) * n);
现在就可以像数组一样操作这块int型的内存了。
pMax[0] = iMax;
pMax[1] = iMax + 1;
pMax[2] = pMax[0];
...
总的来说,指针非常灵活。因为它可以直接操作内存。这就会使指针这个东西很不容易控制。
像你说的p = temp ;将数组的首地址赋值给p , 这样只是为了更容易操作字符串。temp也表示字符串的首地址, 但他是一个不可改变的量,即不能对temp赋值,它是只读的。指针p就不同了,他可以进行一些数学运算。
对一个程序来讲,如果你临时需要一块内存来存储数据,你可以使用malloc, 但记得要free。否则容易造成内存泄露。
就这些吧。 希望对你有用。写这么多也不容易,给点分吧。^_^