java虚拟机

http://blog.csdn.net/zq602316498/article/details/38052137


http://blog.csdn.net/zq602316498/article/details/38052137


http://blog.csdn.net/longeremmy/article/details/8834999

Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区。这些区域有不同的特性,起不同的作用。它们有各自的创建时间,销毁时间。有的区域随着进程的启动而创建,随着进程结束而销毁,有的则始终贯穿虚拟机整个生命周期。

Java虚拟机运行时内存区域主要分为七部分,分别是:程序计数器Java虚拟机栈,本地方法栈,方法区,Java堆,运行时常量池,直接内存。

如上图所示(图片来源于网络):

蓝色区域包裹的部分为运行时几个数据区域:

    白色的部分为线程私有的,既随着线程的启动而创建。每个线程都拥有各自的一份内存区域。它们是: JAVA(JAVA STACK),本地方法栈(NATIVE METHOD STACK),程序计数器(PROGRAM COUNTER REGISTER)。

    黄色部分是线程共享的,所有的线程共享该区域的内容。他们是:方法区(METHOD AREA),(HEAP)。

我们分别来介绍这些区域。

(1)程序计数器

程序计数器(program counter register

     学过计算机组成原理的都知道计算机处理器中的程序计数器。当处理器执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

处理器的程序计数器是指寄存器,而java程序计数器是指一小块内存空间。java代码编译字节码之后,虚拟机会一行一行的解释字节码,并翻印成本地代码。这个程序计数器盛放的就是当前线程所执行字节码的行号的指示器。在虚拟机概念模型中,字节码解释器工作室就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理等都依赖于它。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,因此为了线程切换后还能恢复执行位置,每条线程都需要一个独立的程序计数器

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Java Native方法,这个计数器值为空。

而且程序计数器是Java虚拟机中没有规定任何OutOfMemoryError的区域。

 

(2)虚拟机栈

     Java虚拟机栈(VM Stack)也是线程私有的,因此它的生命周期也和线程相同。它存放的是Java方法执行时的数据,既描述的是Java方法执行的内存模型:每个方法开始执行的时候,都会创建一个栈帧(Stack Frame)用于储存局部变量表栈操作数动态链接方法出口等信息。每个方法从调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。经常有人把Java内存分为堆内存和栈内存,这种是比较粗糙的分法,很大原因是大多数程序‘猿’最关注的,与对象内存分配最密切的区域就是堆和栈。局部变量表存放的是编译器可知的各种基本数据类型(boolean byteintlongcharshortfloatdouble)、对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)。其中64bit长度的longdouble会占用两个局部变量空间(Slot),其余的数据类型只占用一个。局部变量表所需的内存空间是在编译时期确定的,在方法运行期间不会改变局部变量表的大小。在Java虚拟机规范中,对这部分区域规定了两种异常: 1、当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常; 2、如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。

 (3)本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是十分相似的,他们之间的区别不过是虚拟机栈为Java方法字节码服务,而本地方法栈则为Native方法服务。在虚拟机规范中对本地方法使用的语言和使用方法与数据结构没有强制规定,因此具体的虚拟机可以自由实现它。Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。和虚拟机栈一样,本地方法栈也会抛出OutOfMemoryError 和 StackOverflowError异常。

接下来我们介绍的都是所有线程共享的区域了。

 (4)堆

堆(heap)是虚拟机中最大的一块内存区域了,被所有线程共享,在虚拟机启动时创建。它的目的便是存放对象实例

堆是垃圾收集器管理的主要区域,因此 很多时候也被成为‘GC’堆(Garbage Collected Heap)。

从垃圾回收的角度来讲,现在的收集器包括HotSpot都采用分代收集算法,所以堆又可以分为:新生代(Young和老年代(Tenured),再细致一点,新生代又可分为EdenFrom Survivor空间和To Survivor空间。

从内存分配的角度来讲,又可以分为若干个线程私有的分配缓冲区(Thread Local Allocation Buffer ,TLAB)。

当堆空间不足切无法扩展,会抛出OutOfMemoryError异常。

 

(5)方法区

方法去(Method Area)与Java堆一样,是各个线程共享的内存区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

它有个别名叫做非堆Non-Heap。对于HotSpot开发者来说,很多人称它为“永久代”(Permanent Generation),但是两者并不等价,仅仅是因为HotSpot虚拟机设计团队把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以向管理堆一样管理这部分内存。但是因为永久代有“-XXMaxPermSize的上限,使其更容易内存溢出。因此在JDK1.7HotSpot中,已经把原本放在永久代的字符串常量池移出去了。

当方法区无法满足内存分配需求的时候,会抛出OutOfMemoryError异常。

(6)常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中出了类的版本、字段、方法、接口等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后进入方法区存放。

运行时常量池相对于Class文件常量池的另外一个重要特征是具有动态性,运行期间也可能有新的常量池放入持重,比如String.intern()方法。

运行时常量池属于方法区一部分,自然会抛出OutOfMemoryError异常

 (7)直接内存

直接内存(Direct Memory)不属于虚拟机中定义的内存区域,而是堆外内存

JDK1.4 中新加入了NIO(new Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过Java堆中的DirectByteBuffer对象作为这快内存的引用进行操作。这样能在一些场景中显著提高新能性能。如果直接内存不足时,会抛出OutOfMemoryError异常。


GC在什么时候对什么做了什么?

        要回答这个问题,先了解下GC的发展史、jvm运行时数据区的划分、jvm内存分配策略、jvm垃圾收集算法等知识。

        先说下jvm运行时数据的划分,粗暴的分可以分为堆区(Heap)和栈区(Stack),但jvm的分法实际上比这复杂得多,大概分为下面几块:

        1、程序计数器(Program Conuter Register)

        程序计数器是一块较小的内存空间,它是当前线程执行字节码的行号指示器,字节码解释工作器就是通过改变这个计数器的值来选取下一条需要执行的指令。它是线程私有的内存,也是唯一一个没有OOM异常的区域。

        2、Java虚拟机栈区(Java Virtual Machine Stacks)

        也就是通常所说的栈区,它描述的是Java方法执行的内存模型,每个方法被执行的时候都创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法被调用到完成,相当于一个栈帧在虚拟机栈中从入栈到出栈的过程。此区域也是线程私有的内存,可能抛出两种异常:如果线程请求的栈深度大于虚拟机允许的深度将抛出StackOverflowError;如果虚拟机栈可以动态的扩展,扩展到无法动态的申请到足够的内存时会抛出OOM异常。

        3、本地方法栈(Native Method Stacks)

        本地方法栈与虚拟机栈发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行Java方法,本地方法栈则是为虚拟机使用到的Native方法服务。

        4、堆区(Heap)

        所有对象实例和数组都在堆区上分配,堆区是GC主要管理的区域。堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。此块内存为所有线程共享区域,当堆中没有足够内存完成实例分配时会抛出OOM异常。

        5、方法区(Method Area)

       方法区也是所有线程共享区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。GC在这个区域很少出现,这个区域内存回收的目标主要是对常量池的回收和类型的卸载,回收的内存比较少,所以也有称这个区域为永久代(Permanent Generation)的。当方法区无法满足内存分配时抛出OOM异常。

        6、运行时常量池(Runtime Constant Pool)

        运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

 

        垃圾收集(Garbage Collection)并不是Java独有的,最早是出现在Lisp语言中,它做的事就是自动管理内存,也就是下面三个问题:

1、什么时候回收

2、哪些内存需要回收

3、如何回收

 

 1、什么时候回收?

         上面说到GC经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。

        1.1 对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;

        1.2 Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;

        1.3 发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。

 

 2、哪些内存需要回收

        jvm对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到GC Roots没有任何引用相连接,用图论的来说就是从GC Roots到这个对象不可达,则证明此对象是不可用的,说明此对象可以被GC。对于这些不可达对象,也不是一下子就被GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与GC Roots相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行finalize()方法,对于没有必要执行finalize()方法的将会被GC,对于有必要有必要执行的,对象在finalize()方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。

 

 3、如何回收

        选择不同的垃圾收集器,所使用的收集算法也不同。

在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一个Survivor区,当回收时将Eden区和Survivor还存活着的对象一次性的拷贝到另一个Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区,Eden和Survivor的默认比例是8:1,可以使用-XX:SurvivorRatio来设置该比例。

       而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值