java虚拟机详解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
本地方法栈
与虚拟机栈类似,只不过虚拟机栈为虚拟机执行JAVA方法服 务,而本地方法栈则是为了虚拟机使用到的Native方法服务。
Java堆
Java堆是JAVA虚拟机所管理的内存中最大的一块,是被所有线 程共享的区域,在虚拟机启动时创建。此内存区域的唯一目的就是 存放对象实例,几乎所有的对象都在这里分配。
finalize()方法是对象死里逃生的最后一次机会,稍后GC将对FQueue中的对象进行第二次小规模的标记,如果对象在finalize()中成功 解救自己(只要重新与引用链上的任何一个对象建立关联即可,如把自 己this赋给某个类变量或对象的成员变量),那么在第二次标记时它将 被移出“及时回收”的集合;如果对象这个时候还没有逃脱,那么就真的 离死不远了。
新生代GC(Minor GC): 指发生在新生代的垃圾收集动作,因为java 对象大多具有朝生夕死的特性,所有Minor GC非常频繁,一般回收 速度也比较快。
老年代GC(Major GC或者Full GC): 指发生在老年代的GC,出现 了Major GC 经常会伴随至少一次的Minor GC(但非绝对的,在 ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略 选择过程)。Major GC 的速度一般会比Minor GC 慢10倍以上。
无法在任何地方反射访问该类的方法。 虚拟机可以对满足上述3个条件的无用类进行回收,这里说得仅仅 是“可以”,而不是和对象一样,不用了就必然会回收。可以通过 HotSpot的-Xnoclassgc进行控制。在大量使用反射,动态代理, CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自 定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永 久代不会溢出。
Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一 样可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
相对而言,垃圾收集行为在这个区域是比较少出现的。这个主要是 由于方法区存储的内容有关,比如类信息,静态常量一般不需要垃 圾回收。 但并非数据进入了方法区就如永久代的名字一样永远存 在了,这个区域的内存回收目标主要是针对常量池的回收和对类型 的卸载,只不过回收条件要比JAVA堆中的苛刻的多,效果也难以令 人满意。
Object4
Object7
Object6
Object5
Object3
Object1 Gc Roots Object2
在根搜索算法中不可达的对象,也并非是“非死不可”的,这时候 它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次 标记过程:如果对象在进行根搜索算法后发现没有与Gc Roots相连接的 引用链,那么将会被第一次标记并进行一次筛选,筛选的条件是此对象 是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者 finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有 必要执行”。在第二次标记时此对象就被判了“死刑”。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的 类。
回收废弃常量
与回收Java堆中的对象非常相似。假如一个字符串“abc”已经进入了 常量池,但当前系统没有任何一个String对象是叫做“abc”的,也没 有其他地方引用了这个常量,如果这时候发生内存回收,而且必要 的话,这个“abc”常量将会被系统”请”出常量池。常量池中德其他 类,接口,方法,字段的符号引用也与此类似。
1. JAVA虚拟机对它管理的内存 划分
Java虚拟机在执行JAVA程序的过程中,会把它所管理的内存划分为 若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的 时间。有的区域随着虚拟机进程的启动而存在,有的区域是依赖用户线 程的启动和结束而建立和销毁。
本地库接口
执行引擎
程序计数器
本地方法栈
引用
判定对象是否存活和“引用”有关,JDK1.2之前,Java对引用的 定义只有被引用和没被引用两种状态。很难描述像这样的场景:当 内存空间还足够时,则保留在内存中;如果内存在进行垃圾回收之 后还是非常紧张,则可以抛弃这些对象。
JDK1.2之后,对引用的概念进行了扩充,强度依次减弱:
强引用(Strong Reference):类似“Object obj = new
程序计数器(Native方法除外,此时计数器值为空)记录的是正在执 行的虚拟机字节码指令的地址。此区域是唯一一个在JAVA虚拟机规 范中没有规定任何OutOfMemeoryError的区域。
JAVA虚拟机栈
与程序计数器一样,也是线程私有的。虚拟机栈描述的是JAVA 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈 帧,用于存储 局部变量表,操作数表,动态链接,方法出口等信 息。每个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机 中从入栈到出栈的过程。
3. 方法区的回收
前面说过垃圾回收主要回收JAVA堆和方法区。很多人认为方法 区(HotSpot虚拟机中的永久代,注意:永久代不是老年代,老年 代在JAVA堆中)是没有垃圾收集的,Java虚拟机规范中确实说过可 以不收集方法区,而且在方法区中垃圾收集“性价比”比较低:一般 堆中,尤其是新生代中,垃圾收集一般可回收70%--95%的空间, 而永久代则远低于此。
由于JAVA虚拟机的多线程是通过线程轮流切换并分配处理器时 间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行 一条线程中的指令。因此,为了线程切换后能恢复到正确的位置, 每条线程都需要有一个独立的程序计数器,各条线程之间的程序计 数器互不影响,独立存储,所以上面图的程序计数器区域 属于线 程私有的。
回收无用的类
判定一个常量是否为“废弃常量”比较简单,但判定一个类是否无用 的条件则相当苛刻。需要类同时满足下面三个条件:
1, 该类所有的实例都已经被回收,也就是Java堆中不存在该 类的任何实例
2, 加载该类的ClassLoader已经被回收 3, 该类对应的ng.Class对象没有在任何地方被引用,
这种算法实现简单,Java并没有选用它,原因是:它很难解决 对象之间相互循环引用的问题。
根搜索算法 通过一系列名为Gc Roots的对象作为起始点,从这些节点开始向下 搜索,搜索所走过的路径称为引用链(Reference Chain),当一个 对象到Gc Roots没有任何引用链相连,就证明此对象是不可用的: 如下图:object5,object6,object7虽然互相有关联,但它们到Gc Roots是不可达的,所以它们被判定为死亡对象,可以被回收。
所以在设计Java堆内存时,如果存在直接内存,要确保二者的 和不能超过机器的物理内存限制。
2. 判定一个对象已经死亡
垃圾回收主要针对JAVA堆和方法区,回收死亡(不可能再被任 何途径使用的对象)的对象,先看看怎么判定一个对象已经死亡。
引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数 器就加1;当引用失效时,计数器就减1;任何时刻计数还存在,垃圾回收器永远不会回 收掉被引用的对象。
软引用(Soft Reference):描述一些还有用,但并非必须的对 象。对于软引用关联的对象,在系统将要发生内存溢出之前,将把 这些对象列入回收范围并进行第二次回收。如果这次回收还没有足 够的内存,才会抛出内存溢出异常。
本文主要是基于 周志明的《深入理解JAVA虚拟机》一书,感兴趣 的朋友可以去看一看。鉴于篇幅关系,本文只涉及JAVA内存管理,垃 圾回收。
垃圾回收是一把双刃剑,在C++中,程序员要负责每一个对象的内 存回收。Java的垃圾回收器 可以帮我们解决这个问题,但正是由于 JAVA程序员把内存控制的权利交给JAVA虚拟机,一旦出现内存泄露和 内存溢出方面的问题,如果不了解虚拟机怎样使用内存,排查错误将会 异常艰难。
弱引用(Weak Reference):弱引用也是用来描述非必须对象 的,但它的强度比软引用更弱一些,被弱引用关联的对象只能生存 到下一次垃圾收集之前。当垃圾回收器工作时,无论当前内存是否 足够,都会回收掉只被弱引用关联的对象。
虚引用(Phantom Reference):虚引用也成为幽灵引用或幻影 引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在, 完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对 象的实例。为一个对象设置虚引用关联的唯一目的:就是希望能在 这个对象被垃圾回收时收到一个系统通知。
空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
在JAVA虚拟机中,对这个区域规定了两种异常状况:如果线程 所请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部 分虚拟机都支持),当扩展是无法申请到足够的内存时会抛出 OutOfMemoryError异常。
虚拟机栈
方法区

运行时数据区
本地方法库
图 1 : Java虚拟机运行时数据区 (颜色部分为所有线程共享的数据 区)
程序计数器
程序计数器是一块较小的内存区域,它的作用可以看成是当前 线程所执行字节码的行号指示器。字节码解释器工作时就是通过改 变这个计数器的值来选取下一条需要执行的字节码指令。分支,循 环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器 来完成。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象 将会被设置在一个名为F-Queue的队列之中,并在稍后有一条虚拟机自 动建立的,低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚 拟机会触发这个方法,但并不承诺会等待它结束。这样做得原因是,如 果一个对象在一个finalize()方法中执行缓慢,或者发生死循环,将很有 可能会导致F-Queue中其他对象永久处于等待状态,甚至导致整个内存 回收系统崩溃。
在方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚 拟机规范中定义的内存区域。
在JDK1.4中新加入了NIO(New Input/Output)类,引入了一 种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以使 用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里 面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在 一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制 数据。
4. 垃圾回收算法
一般将Java堆分为老年代和年轻代,这样划分是为了根据对象 的特点选择不同的垃圾回收器以针对性的回收。比如-Xmx 20M,Xms 20M, -Xmn12M,JAVA堆总共20M内存,给年轻代分了12M,那 么老年代就是8M。由此得出Minor GC 和Major GC/Full GC的概念
局部变量表存放了编译期可知的各种基本数据类型 (boolean,byte,char,short,int,float,double),对象引用和return Address类型。其中64位的double和long会占用2个局部变量空间, 其余的数据类型只占一个。局部表所需的内存空间在编译期完成分 配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量
因此,Java堆就是垃圾回收器管理的主要区域,也被称为“GC 堆”。
目前主流的虚拟机都支持堆扩展,可以通过-Xmx和-Xms来控 制。如果在堆中没有内存来完成实例分配,将会抛出 OutOfMemoryError异常。
方法区
方法区也是被线程共享的,它用于存储已经被虚拟机加载的类 信息,常量,静态常量,即时编译期编译后的代码等数据。
很多人愿意把方法区称为永久代,本质上二者并不等价。仅仅 是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法 区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如 BEA Jrockit,IBM J9)来说是不存在永久代的概念的。即使HotSpot 虚拟机本身,现在也有放弃永久代并“搬家”到Native Memory 来实 现方法区的规划了。
相关文档
最新文档