谁能说说java内存机制管理机制吗

本帖子已过去太久远了,不再提供回复功能。谈谈Java内存管理 - 博客频道 - CSDN.NET
__技术分享__部落格
一个不善于写文章的程序员。A programmer's self-reflection
分类:技术分享
对于一个Java程序员来说,大多数情况下的确是无需对内存的分配、释放做太多考虑,对Jvm也无需有多么深的理解的。但是在写程序的过程中却也往往因为这样而造成了一些不容易察觉到的内存问题,并且在内存问题出现的时候,也不能很快的定位并解决。因此,了解并掌握Java的内存管理是一个合格的Java程序员必需的技能,也只有这样才能写出更好的程序,更好地优化程序的性能。
一. 背景知识
根据网络可以找到的资料以及笔者能够打听到的消息,目前国内外著名的几个大型互联网公司的语言选型概括如下:
Google: C/C++ Go Python Java JavaScript,不得不提的是Google贡献给java社区的guava包质量非常高,非常值得学习和使用。Youtube、豆瓣: PythonFackbook、Yahoo、Flickr、新浪:php(优化过的php vm)网易、阿里、搜狐: Java、PHP、Node.jsTwitter: Ruby-&Java,之所以如此就在于与Jvm相比,Ruby的runtime是非常慢的。并且Ruby的应用比起Java还是比较小众的。不过最近twitter有往scala上迁移的趋势。
可见,虽然最近这些年很多言论都号称java已死或者不久即死,但是Java的语言应用占有率一直居高不下。与高性能的C/C++相比,Java具有gc机制,并且没有那让人望而生畏的指针,上手门槛相对较低;而与上手成本更低的PHP、Ruby等脚本语言来说,又比这些脚本语言有性能上的优势(这里暂时忽略FB自己开发的HHVM)。
对于Java来说,最终是要依靠字节码运行在jvm上的。目前,常见的jvm有以下几种:
Sun HotSpotBEA JrockitIBM J9Dalvik(Android)
其中以HotSpot应用最广泛。目前sun jdk的最新版本已经到了8,但鉴于新版的jdk使用并未普及,因此本文仅仅针对HotSpot虚拟机的jdk6来讲。
二. Jvm虚拟机内存简介
2.1 Java运行时内存区
Java的运行时内存组成如下图所示:
其中,对于这各个部分有一些是线程私有的,其他则是线程共享的。
线程私有的如下:
程序计数器
当前线程所执行的字节码的行号指示器
Java虚拟机栈
Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。
每个线程都有自己独立的栈空间线程栈只存基本类型和对象地址方法中局部变量在线程空间中
本地方法栈
Native方法服务。在HotSpot虚拟机中和Java虚拟机栈合二为一。
线程共享的如下:
存放对象实例,几乎所有的对象实例以及其属性都在这里分配内存。
存储已经被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据。
运行时常量池
方法区的一部分。用于存放编译期生成的各种字面量和符号引用。
NIO、Native函数直接分配的堆外内存。DirectBuffer引用也会使用此部分内存。
2.2 对象访问
Java是面向对象的一种编程语言,那么如何通过引用来访问对象呢?一般有两种方式:
通过句柄访问
此种方式也是HotSpot虚拟机采用的方式。
2.3 内存溢出
在JVM申请内存的过程中,会遇到无法申请到足够内存,从而导致内存溢出的情况。一般有以下几种情况:
虚拟机栈和本地方法栈溢出
StackOverflowError: 线程请求的栈深度大于虚拟机所允许的最大深度(循环递归)OutOfMemoryError: 虚拟机在扩展栈是无法申请到足够的内存空间,一般可以通过不停地创建线程引起此种情况
Java堆溢出: 当创建大量对象并且对象生命周期都很长的情况下,会引发OutOfMemoryError运行时常量区溢出:OutOfMemoryError:PermGen space,这里一个典型的例子就是String的intern方法,当大量字符串使用intern时,会触发此内存溢出方法区溢出:方法区存放Class等元数据信息,如果产生大量的类(使用cglib),那么就会引发此内存溢出,OutOfMemoryError:PermGen space,在使用Hibernate等框架时会容易引起此种情况。
三. 垃圾收集
3.1 理论基础
在通常情况下,我们掌握java的内存管理就是为了应对网站/服务访问慢,慢的原因一般有以下几点:
内存:垃圾收集占用cpu;放入了太多数据,造成内存泄露(java也是有这种问题的^_^)线程死锁I/O速度太慢依赖的其他服务响应太慢复杂的业务逻辑或者算法造成响应的缓慢
其中,垃圾收集对性能的影响一般有以下几个:
内存泄露程序暂停程序吞吐量显著下降响应时间变慢
垃圾收集的一些基本概念
Concurrent Collector:收集的同时可运行其他的工作进程Parallel Collector: 使用多CPU进行垃圾收集Stop-the-word(STW):收集时必须暂停其他所有的工作进程Sticky-reference-count:对于使用“引用计数”(reference count)算法的GC,如果对象的计数器溢出,则起不到标记某个对象是垃圾的作用了,这种错误称为sticky-reference-count problem,通常可以增加计数器的bit数来减少出现这个问题的几率,但是那样会占用更多空间。一般如果GC算法能迅速清理完对象,也不容易出现这个问题。Mutator:mutate的中文是变异,在GC中即是指一种JVM程序,专门更新对象的状态的,也就是让对象“变异”成为另一种类型,比如变为垃圾。On-the-fly:用来描述某个GC的类型:on-the-fly reference count garbage collector。此GC不用标记而是通过引用计数来识别垃圾。Generational gc:这是一种相对于传统的“标记-清理”技术来说,比较先进的gc,特点是把对象分成不同的generation,即分成几代人,有年轻的,有年老的。这类gc主要是利用计算机程序的一个特点,即“越年轻的对象越容易死亡”,也就是存活的越久的对象越有机会存活下去(姜是老的辣)。
吞吐量与响应时间
牵扯到垃圾收集,还需要搞清楚吞吐量与响应时间的含义
吞吐量是对单位时间内完成的工作量的量度。如:每分钟的 Web 服务器请求数量响应时间是提交请求和返回该请求的响应之间使用的时间。如:访问Web页面花费的时间
吞吐量与访问时间的关系很复杂,有时可能以响应时间为代价而得到较高的吞吐量,而有时候又要以吞吐量为代价得到较好的响应时间。而在其他情况下,一个单独的更改可能对两者都有提高。通常,平均响应时间越短,系统吞吐量越大;平均响应时间越长,系统吞吐量越小; 但是,系统吞吐量越大, 未必平均响应时间越短;因为在某些情况(例如,不增加任何硬件配置)吞吐量的增大,有时会把平均响应时间作为牺牲,来换取一段时间处理更多的请求。
针对于Java的垃圾回收来说,不同的垃圾回收器会不同程度地影响这两个指标。例如:并行的垃圾收集器,其保证的是吞吐量,会在一定程度上牺牲响应时间。而并发的收集器,则主要保证的是请求的响应时间。
找出堆中活着的对象释放死对象占用的资源定期调整活对象的位置
Mark-Sweep 标记-清除Mark-Sweep-Compact 标记-整理
Copying Collector 复制算法
从”GC roots”开始扫描(这里的roots包括线程栈、静态常量等),给能够沿着roots到达的对象标记为”live”,最终所有能够到达的对象都被标记为”live”,而无法到达的对象则为”dead”。效率和存活对象的数量是线性相关的。
Sweep-清除
扫描堆,定位到所有”dead”对象,并清理掉。效率和堆的大小是线性相关的。
Compact-压缩
对于对象的清除,会产生一些内存碎片,这时候就需要对这些内存进行压缩、整理。包括:relocate(将存货的对象移动到一起,从而释放出连续的可用内存)、remap(收集所有的对象引用指向新的对象地址)。效率和存活对象的数量是线性相关的。
将内存分为”from”和”to”两个区域,垃圾回收时,将from区域的存活对象整体复制到to区域中。效率和存活对象的数量是线性相关的。
其中,Copy对比Mark-sweep
内存消耗:copy需要两倍的最大live set内存;mark-sweep则只需要一倍。效率上:copy与live set成线性相关,效率高;mark-sweep则与堆大小线性相关,效率较低。
分代收集是目前比较先进的垃圾回收方案。有以下几个相关理论
分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对年青代对象的收集,而且年青代通常只占整个空间的一小部分。把年青代里活的很长的对象移动到老年代。只有当老年代满了才去收集。收集效率明显比不分代高。
HotSpot虚拟机的分代收集,分为一个Eden区、两个Survivor去以及Old Generation/Tenured区,其中Eden以及Survivor共同组成New Generatiton/Young space。通常将对New Generation进行的回收称为Minor GC;对Old Generation进行的回收称为Major GC,但由于Major GC除并发GC外均需对整个堆以及Permanent Generation进行扫描和回收,因此又称为Full GC。
Eden区是分配对象的区域。Survivor是minor/younger gc后存储存活对象的区域。Tenured区域存储长时间存活的对象。
分代收集中典型的垃圾收集算法组合描述如下:
年青代通常使用Copy算法收集,会stop the world老年代收集一般采用Mark-sweep-compact, 有可能会stop the world,也可以是concurrent或者部分concurrent。
那么何时进行Minor GC、何时进行Major GC? 一般的过程如下:
对象在Eden Space完成内存分配当Eden Space满了,再创建对象,会因为申请不到空间,触发Minor GC,进行New(Eden + S0 或 Eden S1) Generation进行垃圾回收Minor GC时,Eden Space不能被回收的对象被放入到空的Survivor(S0或S1,Eden肯定会被清空),另一个Survivor里不能被GC回收的对象也会被放入这个Survivor,始终保证一个Survivor是空的在Step3时,如果发现Survivor区满了,则这些对象被copy到old区,或者Survivor并没有满,但是有些对象已经足够Old,也被放入Old Space。当Old Space被放满之后,进行Full GC
但这个具体还要看JVM是采用的哪种GC方案。
New Generation的GC有以下三种:
SerialParallelScavengeParNew
对于上述三种GC方案均是在Eden Space分配不下时,触发GC。
Old Generation的GC有以下四种:
Serial OldParallelCMS
对于Serial Old, Parallel Old而言触发机制为
Old Generation空间不足Permanent Generation空间不足Minor GC时的悲观策略Minor GC后在Eden上分配内存仍然失败执行Heap Dump时外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止,。这里需要注意的是禁用System.gc()会引起使用NIO时的OOM,所以此选项慎重使用。具体可见:。
对于CMS而言触发机制为:
当Old Generation空间使用到一定比率时触发,HopSpot V1.6中默认是92%,可通过PrintCMSInitiationStatistics(此参数在V1.5中不能用)来查看这个值到底是多少,通过CMSInitiatingOccupancyFaction来强制指定。默认值是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerRatio* MinHeapFreeRatio) / 100.0)/ 100.0,MinHeapFreeRatio默认值为40,CMSTriggerRatio默认值为80。当Permanent Generation采用CMS收集且空间使用到一定比率触发,Permanent Generation采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled。 Hotspot V1.6中默认为92%,可通过CMSInitiatingPermOccupancyFraction来强制指定。同样,它是根据如下公式计算出来的:((100 -MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio)
/ 100.0)/ 100.0,MinHeapFreeRatio默认值为40,CMSTriggerPermRatio默认值为80。Hotspot根据成本计算决定是否需要执行CMS GC,可通过-XX:+UseCmsInitiatingOccupancyOnly来去掉这个动态执行的策略。外部调用System.gc,且设置了ExplicitGCIInvokesConcurrent或者ExplicitGCInvokesConcurrentAndUnloadsClasses。
3.2 HotSpot垃圾收集器
上图即为HotSpot虚拟机的垃圾收集器组成。
Serial收集器
-XX:+UserSerialGC参数打开此收集器Client模式下新生代默认的收集器。较长的stop the world时间简单而高效
此收集器的一个工作流程如下如所示:
ParNew收集器
-XX:+UserParNewGC+UseConcuMarkSweepGC时默认开启Serial收集器的多线程版本默认线程数与CPU数目相同-XX:ParrallelGCThreads指定线程数目
对比Serial收集器如下图所示:
Parallel Scavenge收集器
新生代并行收集器采用Copy算法主要关注的是达到可控制的吞吐量,“吞吐量优先”-XX:MaxGCPauseMillis -XX:GCTimeRAtion两个参数精确控制吞吐量-XX:UseAdaptiveSizePolicy GC自适应调节策略Server模式的默认新生代收集器
Serial Old收集器
Serial的老年代版本Client模式的默认老年代收集器CMS收集器的后备预案,Concurrent Mode Failure时使用-XX:+UseSerialGC开启此收集器
Parallel Old收集器
-XX:+UseParallelGC -XX:+UseParallelOldGC启用此收集器Server模式的默认老年代收集器Parallel Scavenge的老年代版本,使用多线程和”mark-sweep”算法关注点在吞吐量以及CPU资源敏感的场合使用一般使用Parallel Scavenge + Parallel Old可以达到最大吞吐量保证
并发低停顿收集器
-XX:UseConcMarkSweepGC 开启CMS收集器,(默认使用ParNew作为年轻代收集器,SerialOld作为收集失败的垃圾收集器)以获取最短回收停顿时间为目标的收集器,重视响应速度,希望系统停顿时间最短,会和互联网应用。
四个步骤:
初始标记 Stop the world: 只标记GC roots能直接关联到的对象,速度很快。并发标记:进行GC roots tracing,与用户线程并发进行重新标记 Stop the world:修正并发标记期间因程序继续运行导致变动的标记记录并发清除
对比serial old收集器如下图所示:
CMS有以下的缺点:
CMS是唯一不进行compact的垃圾收集器,当cms释放了垃圾对象占用的内存后,它不会把活动对象移动到老年代的一端对CPU资源非常敏感。不会导致线程停顿,但会导致程序变慢,总吞吐量降低。CPU核越多越不明显无法处理浮动垃圾。可能出现“concurrent Mode Failure”失败, 导致另一次full GC ,可以通过调整-XX:CMSInitiatingOccupancyFraction来控制内存占用达到多少时触发gc大量空间碎片。这个可以通过设置-XX:UseCMSCompacAtFullCollection(是否在full gc时开启compact)以及-XX:CMSFullGCsBeforeCompaction(在进行compact前full gc的次数)
G1算法在Java6中还是试验性质的,在Java7中正式引入,但还未被广泛运用到生产环境中。它的特点如下:
使用标记-清理算法不会产生碎片可预测的停顿时间化整为零:将整个Java堆划分为多个大小相等的独立区域-XX:+UseG1GC可以打开此垃圾回收器-XX:MaxGCPauseMillis=200可以设置最大GC停顿时间,当然JVM并不保证一定能够达到,只是尽力。
3.3 调优经验
需要打开gc日志并读懂gc日志:-XX:PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamp -Xloggc:$CATALINA_BASE/logs/gc.log垃圾回收的最佳状态是只有young gc,也就是避免生命周期很长的对象的存在。从young gc开始,尽量给年青代大点的内存,避免full gc注意Survivor大小注意内存墙:4G~5G
GC日志简介
: [GC [PSYoungGen: 1375104K-&16K)] 4145665K-&82400K), 0.0174410 secs] [Times: user=0.27 sys=0.00, real=0.02 secs]
:发生的时间点,JVM运行的时间长度,以度为单位,也可以格式化成固定的时间格式(使用-XX:+PrintGCDateStamps)PSYoungGen:发生了何种类型的GC,此处代表发生了年轻代的GC1375104K:回收前的大小11376K:回收后的大小1386176K:YOUNG代的大小4145665 K:回收前总的占用大小2782002K:回收后的占用大小4182400K:总占用大小0.0174410:垃圾收集停顿时间0.27和0.00:代表在用户态(user)和系统状(sys)的CPU运行时间0.02 secs:代表实际的GC的运行时间
注:上面实际GC的运行时间小于用户态和系统态的时间总和,是由于前者仅指CPU的运行时间,包括等待或IO阻塞的时间,而现在的GC是采用多线程收集的,同时机器也是多个CPU,因此,大部分是二者之和要比前面的值大。如果是采用串形化收集器的话,二者时间几乎相差不多。
老年代使用建议
Parallel GC(-XX:+UseParallel[Old]GC)
Parallel GC的minor GC时间是最快的, CMS的young gc要比parallel慢, 因为内存碎片可以保证最大的吞吐量
确实有必要才改成CMS或G1(for old gen collections)
小对象allocate的代价很小,通常10个CPU指令;收集掉新对象也非常廉价;不用担心活的很短的小对象大对象分配的代价以及初始化的代价很大;不同大小的大对象可能导致java堆碎片,尤其是CMS, ParallelGC 或 G1还好;尽量避免分配大对象避免改变数据结构大小,如避免改变数组或array backed collections / containers的大小;对象构建(初始化)时最好显式批量定数组大小;改变大小导致不必要的对象分配,可能导致java堆碎片对象池可能潜在的问题
增加了活对象的数量,可能增加GC时间访问(多线程)对象池需要锁,可能带来可扩展性的问题小心过于频繁的对象池访问
GC的庞氏骗局
虽然GC在大多数情况下还是正常的,但有时候JVM也会发生欺骗你的场景, JVM不停的在垃圾回收,可是每次回收完后堆却还是满的,很明显程序内存被使用完了,已经无法正常工作了,但JVM就是不抛出OutOfMemoryError(OOM)这个异常来告诉程序员内部发出了什么,只是不停的做老好人尝试帮我们做垃圾回收,把服务器的资源耗光了。
出现这种现象的一种典型情况就是GC的GCTimeLimit和GCHeapFreeLimit参数设置不合适。GCTimeLimit的默认值是98%,也就是说如果大于等于98%的时间都用花在GC上,则会抛出OutOfMemoryError。GCHeapFreeLimit是回收后可用堆的大小,默认值是2%,也就是说只要有多余2%的内存可用就认为此次gc是成功的。如果GCTimeLimit设置过大或者GCHeapFreeLimit设置过小那么就会造成GC的庞式骗局,不停地进行垃圾回收。
四. Java7、8带来的一些变化
Java7带来的内存方面的一个很大的改变就是String常量池从Perm区移动到了Heap中。调用String的intern方法时,如果存在堆中的对象,则会直接保存对象的引用,而不会重新创建对象。Java7正式引入G1垃圾收集器用于替换CMS。Java8中,取消掉了方法区(永久代),使用“元空间”替代,元空间只与系统内存相关。Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。可以使用-XX:+UseStringDedup
排名:千里之外
(1)(4)(56)(3)(14)(8)1.实例变量和类变量的内存分配;类变量:使用static修饰的成员变量是类变量,;实例变量:没有使用static修饰的成员变量是实;由于同一个JVM内每个累只对应一个Class对象;对于实例变量而言,该类没创建一次实例,就需要为实;2.类变量的初始化时机总是出于实例变量的初始化之;我们先看下下面三段代码:;1)因为两个实例变量都是在创建变量的时候才开始分;1
1.实例变量和类变量的内存分配
类变量 :使用static修饰的成员变量是类变量,属于该类本身
实例变量:没有使用static修饰的成员变量是实例变量,属于该类的实例
由于同一个JVM内每个累只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间。
对于实例变量而言,该类没创建一次实例,就需要为实例变量分配一块内存空间,所以,程序中有几个实例,实例变量就需要几块内存空间。
2.类变量的初始化时机总是出于实例变量的初始化之前
我们先看下下面三段代码:
1)因为两个实例变量都是在创建变量的时候才开始分配空间,此时num2还没有分配,所以前向引用就会出现编译错误。
1 int num = num2 + 3;
//非法前向引用,会报错
2 int num2 = 2
2)因为两个类变量在JVM加载类的时候分配空间,此时num2还没有分配,所以前向引用就出现变异错误。
1 static int num = num2 + 3;
//非法前向引用,会报错
2 tatic int num2 = 2
3)因为类变量num2在JVM加载类的时候空间已经分配好,而num在创建实例的时候踩分配空间,此时num2已经分配成功了,所以num前向引用成功。
1 int num = num2 + 3;
//正确使用
2 static int num2 = 2;
由上面三段代码块就可以验证得:类变量的初始化时机总是出于实例变量的初始化之前
3.Java对象的初始化方式及其执行顺序
Java对象的初始化方式有三种:1)构造器
2)初始化块
3)定义变量时指定初始化值 如果这三种初始化方式同时出现,也要注意,他们也有一个执行顺序的规定:
1)静态初始化块只在类第一次创建对象的时候运行一次,后面就不会再运行,而类在每次创建对象时,非静态初始化块总是会运行一次。
9 public class Test{
System.out.println(&执行---静态初始化代码块.&);
System.out.println(&执行---非静态初始化代码块.&);
public static void main(String[] args) {
for (int i = 1; i &= 2; i++) {
System.out.println(&创建第 & + i + & 个对象&);
new Test();
System.out.println();
运行结果:
2)构造器每次创建对象时,构造器必然有执行的机会
,此时,非静态初始化块必定也将获得机会并且运行在构造器之前
9 public class Test{
System.out.println(&执行---非静态初始化代码块.&);
public Test() {
System.out.println(&执行---构造器.&);
public static void main(String[] args) {
for (int i = 1; i &= 2; i++) {
System.out.println(&创建第 & + i + & 个对象&);
new Test();
System.out.println();
3)定义变量时指定的初始化值和初始化块中指定的初始值的执行顺序与他们在源程序中的排列顺序相同。
验证代码一:
9 public class Test{
String i = &定义变量时指定的初始化值&;
i = &初始化块中指定的初始值&;
public static void main(String[] args) {
for (int i = 1; i &= 2; i++) {
System.out.println(&创建第 & + i + & 个对象&);
System.out.println(new Test().i);
System.out.println();
验证代码二 :
14 public class Test{
i = &初始化块中指定的初始值&;
String i = &定义变量时指定的初始化值&;
public static void main(String[] args) {
for (int i = 1; i &= 2; i++) {
System.out.println(&创建第 & + i + & 个对象&);
System.out.println(new Test().i);
System.out.println();
运行结果:
4.关于父子实例的内存控制
(一般情况下是不用内部类来验证的,但是都是一样的啦,我偷懒下,所以使用了内部类,大家原谅哈)
1)当子类重写父类方法后,父类表面上只是调用属于自己的被子类重写的方法。
9 public class Test{
class Base {
public void info() {
System.out.println(&Base&);
public void getInfo() {
public class Child extends Base{
public void info() {
System.out.println(&Child&);
public static void main(String[] args) {
Test test = new Test();
Base base = test.new Child();
base.getInfo();
三亿文库包含各类专业文献、高等教育、幼儿教育、小学教育、中学教育、各类资格考试、外语学习资料、行业资料、文学作品欣赏、Java对象与内存管理机制68等内容。 
 java 是如何管理内存的 Java 的内存管理就是对象的分配和释放问题。(两部分) ...释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作...  详细介绍Java的内存管理与内存泄露_计算机软件及应用_IT/计算机_专业资料。详细...Java 的内存垃圾回收机制是从程序的主要运行对象开始检查引用链, 当遍历 一遍后...  从JVM内存管理的角度谈谈静态方法和静态属性 和 java对象引用与JVM自动内存管理_...内存管理器的工作机制 下面将首先介绍未嵌入引用对象时内存管理器的工作机制,然后...  JVM原理以及JVM内存管理机制_计算机软件及应用_IT/计算机_专业资料。JVM简介以及...Java 堆,由于每个线程分配到的计算栈容量有限,对于可能会 占据大量内存的对象,...  内存管理器的工作机制 下面将首先介绍未嵌入引用对象时内存管理器的工作机制, 然后讨论引用对 象加入之后 Java 堆发生的变化。 内存管理器的作用就是识别程序中不...  java内存管理机制 5页 2财富值 Java 内存管理原理 4页 5财富值 深入Java内存...对象&abc&, 3) 引用 str2 指向堆中的新建的 String 对象&abc&, 4) str2...  java内存管理机制 5页 1下载券 深入Java内存管理 33页 1下载券 JAVA 内存管理...Java 的内存管 理就是对象的分配 和释放问题。 Java 中, 在 程序员需要通过...  d.Java 的 堆是一个运行时数据区,类的(对象从中分配空间。 这些对象通过 ...Java内存管理机制与jvm参... 28页 1下载券 java内存管理讲义 31页 1下载券喜欢...Java自动内存管理机制
第二章:内存区域
java在执行java程序的过程中会把它所管理的内存划分成很多个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。Java虚拟机规范中把java虚拟机所管理的内存划分为以下几个区域。
一、程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,它的作用是当前所执行的字节码的行号指示器。它是线程私有的,即各个线程都有独立的程序计数器。
如果线程正在 执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,则这个计数器是空(undefined)。
此内存区域是唯一的一个不会抛出OutOfMemoryError异常的区域。
二、java虚拟机栈(Java Virtual Machine Stacks)
我们可能经常听到说java内存分为堆内存和栈内存,其实这个说法中的栈内存是指java虚拟机栈中的局部变量表部分。
Java虚拟机栈描述的是java方法执行的内存模型:每个方法被执行时都会同时创建一个栈帧(Stack Frame),用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。Java虚拟机栈也是线程私有的,生命周期也线程相同。
局部变量表存放的类型包括以下三种:
1、 编译期可知的基本数据类型:boolean、byte、char、short、int、float、long、double共8 种类型;
2、 对象引用:即reference类型,它存放的是一个指向堆中对象起始地址的引用指针,或一个代表对象的句柄或者其他与此对象相关的位置,根据虚拟机的不同实现而不同;
3、 returnAddress类型:存放指向一条字节码指令的地址;
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量表空间是完全确定了的,方法在运行期间不会改变局部变量表的大小。
Java虚拟机栈会抛出两种异常:
1、 OutOfMemoryError异常:如果虚拟本可以动态扩展,当扩展时无法申请到足够的内存时抛出;
2、 StackOverflowError异常:如果线程请求的栈深度大于虚拟机所允许的深度时抛出;
三、本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈的作用相似,它是为虚拟机在执行native方法时服务,而虚拟机栈是为虚拟机执行java方法服务。此内存区域也会抛出OutOfMemoryError异常和StackOverflowError异常。
四、java堆(Java Heap)
java堆是用于存放对象实例和数组。它是java虚拟机管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,也是垃圾收集器管理的要区域,几乎所有的对象实例都在这里分配内存。
如果垃圾收集器采用的是分代收集它还,它还可以细分为新生代和老年代,再细致一点的有Enden空间、From Survivor空间、To Survivor空间等。
java堆可以处理物理上不连续的内存空间中,只要逻辑上连续即可。在实现时既可以实现丰固定大小的,也可以是可扩展的。如果是可扩展的,可以通过-Xms和-Xmx来指定最小和最大值,如果-Xms和-Xmx的值相等,则相当于不可扩展了。如果堆中没有内存可完成实例分配,此内存区域会抛出OutOfMemoryError异常。
五、方法区
方法区用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它是堆的一个逻辑部分,是各个线程共享的内存区域。如果是sun的HotSpot虚拟机,它也叫做永久代(Permanent Generation),如果方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
方法区中的运行时常量池(RuntimeConstant Pool),用于存放编译期生成的各种字面量和符号引用,它在类加载后存放到运行时常量池中。
运行时常量池具有动态性,即常量不一定只在编译期产生,在运行期间也可能将新的常量存入池中,比如String类的intern()方法。
六、直接内存(DirectMemory)
它不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。比如在JDK1.4中新加入的NIO类,有一种基于通道与缓冲区的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。所以直接内存不会受到java堆大小的限制,但会受到本机总内存的大小及处理器寻址空间的限制。它也会抛出OutOfMemoryError异常。}

我要回帖

更多关于 java内存机制 的文章

更多推荐

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

点击添加站长微信