JVM 基础

Jvm定义:

  JVM:Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

  JVM运行时数据区组成部分:

    JVM分为五大部分:方法区、虚拟机栈、堆、本地方法栈、程序计数器。

    程序计数器:是当前线程所执行的字节码行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现,而且每个线程都有一个独立的程序计数器,互不影响。(线程私有)。如果线程正在执行的是java方法,那么计数器记录的是字节码指令的地址;如果是指定的native方法,那么这个计数器值为空(undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何的OutOfMemoryError情况的区域。

    Java虚拟机栈:描述的是java方法执行的内存模型,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作帧、动态链接、方法出口等信息。(线程私有)每个方法被调用到完成的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。注:规定:线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常;如果虚拟机动态扩展时,无法申请到足够的内存时会抛出OutOfMemoryError异常。

    本地方法栈:与java虚拟机相似,唯一不同的是为Native方法服务,也会抛出StackOverFlowError异常和OutOfMemoryError异常。

    Java堆:Java堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块区域,在虚拟机启动的时候创建,存放的是java对象实例。(共享)所有的对象实例和数组几乎都是在堆上分配内存,随着技术(JIT)和逃逸分析技术的发展,绝对的栈上分配,也逐渐地减少。Java堆是垃圾收集器管理的主要区域,有时候也称该区域为“GC堆”。目前的收集器采用的是分带收集算法,即Java堆分为新生代和老年代;再将新生代细分为Eden空间、From Survivor  空间、To Survivor空间等。

    新生代  :存放新生的对象,占据堆的1/3空间,MinorGC(采用复制清除算法)进行垃圾回收。 

      新生代分 Eden、ServivorFrom、ServivorTo三个区。 
      Eden:新对象的出生地。当Eden区内存不足的时候,虚拟机将进行一次MinorGC。 
      ServivorTo:保留MinorGC过程中的幸存者。 
      ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。 
      MinorGC的过程:MinorGC采用复制算法。首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域,同时把这些对象的年龄+1(默认情况下15岁就直接送到老年代了);然后,清空Eden和ServicorFrom中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。

    1.Eden区: Eden区位于Java堆的年轻代,是新对象分配内存的地方,由于堆是所有线程共享的,因此在堆上分配内存需要加锁。而Sun JDK为提升效率,会为每个新建的线程在Eden上分配一块独立的空间由该线程独享,这块空间称为TLAB(Thread Local Allocation Buffer)。在TLAB上分配内存不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配。如果对象过大或TLAB用完,则仍然在堆上进行分配。如果Eden区内存也用完了,则会进行一次Minor GC(young GC)。

    2.Survival-from/to :   Survival区与Eden区相同都在Java堆的年轻代。Survival区有两块,一块称为from区,另一块为to区,这两个区是相对的,在发生一次Minor GC后,from区就会和to区互换。在发生Minor GC时,Eden区和Survivalfrom区会把一些仍然存活的对象复制进Survival to区,并清除内存。Survival to区会把一些存活得足够旧的对象移至年老代。

    3.年老代:   年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。注:Java堆可以处于物理上不连续的内存空间中,在逻辑上是连续的即可。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

    方法区:是各个线程共享的区域,存放的是已被虚拟机加载的类信息、常量、静态变量、类型数据、即时编译器编译后的代码等数据。(共享)垃圾收集器在这个区域是比较少出现的,这个区域的内存回收目标主要是针对常量池的回收对类型的卸载。注:方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

   运行时常量池:是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,在类加载后存放到方法区的运行时常量池中。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。运行时常量池还具备动态性,在运行期间也可能将新的常量放入池中,eg:就是String类的intern()方法。注:常量池无法再申请到内存时会抛出OutOfMemoryError异常。

   注:直接内存,它并不是虚拟机数据区的一部分,也不是虚拟机中定义的内存区域。在jdk1.4及其以后新加入了NIO(New Input/Output)类,引入了一种基于通道(channel)和缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的的引用进行操作。本机直接内存的分配不受到Java堆大小的限制,各个内存区域的综合大雨物理内存限制的话,会导致动态扩展时出现OutOfMemoryError异常。

     访问对象:引用类型在虚拟机中只规定了一个纸箱对象的引用,并没有定义这个引用应该通过哪种方式定位,主流访问方式分为两种:使用句柄和直接指针。

         (1)使用句柄访问方式:Java堆中将机会划分一块内存来作为句柄池,引用中存储的就是这个对象的句柄地址,而句柄中包含了对象实例数据(实例池)和类型数据(方法区)各自的具体地址信息。实例池和句柄池均在Java堆中。

         (2)使用直接指针访问方式:Java堆对象的布局中考虑如何放置访问类型数据的相关信息,引用中直接存储的就是对象地址。

          两种访问方式优势:句柄访问方式中引用存储的是问鼎的句柄地址,在对象呗移动时只是改变了句柄中的实例数据至深圳,而引用本身不需要修改。直接指针访问方式是速度更快,节省了一次指针定位的时间开销。

垃圾收集器:

        引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用时。计数器值就增加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。注:很难解决java问题中的对象之间的相互循环引用问题。

         根搜索算法: 通过一系列的名为“”GC Roots”的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何的引用链相连时,则证明此对象是不可用的。注:GC Roots对象分为:(1)虚拟机栈(栈帧中的本地变量表)中的引用的对象。(2)方法区中的静态属性引用的对象。(3)方法区中常量引用的对象。(4)本地方法栈中JNI(native方法)的引用的对象。

         引用:引用数据类型的值存储的是一块内存的起始地址,则代表着一种引用。

           Java中将引用分为:强引用、软引用、弱引用、虚引用。

           强引用:只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

           软引用:系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。

           弱引用:被弱引用关联的对象只能生存到下一次垃圾收集器发生之前。

           虚引用:一个对象是否有虚引用的存在,完全不影响其生存时间,也无法通过虚引用来取得一个对象实例。

     方法区回收(永久代回收):废弃常量和无用的类。

                  废弃常量:当前系统没有任何地方引用这个常量,如果发生内存回收,有必要的话,会被系统清除常量池。

                  无用的类:(1)该类所有的实例都已经被回收,Java堆中不存在该类的任何实例。(2)加载该类的ClassLoader已经被回收。(3)该类的Java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法。

  垃圾收集算法: (1)标记-清除算法(最基础):分为标记和清除两个阶段,首先是标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象。缺点:(1)效率不高;(2)空间问题:标记清除后,会产生大量不连续的内存碎片,可能会导致以后的程序需要分配较大对象时,无法找到足够的连续内存,而触发垃圾收集动作。

           (2)复制算法:它将可用内存按容量分为了大小相等的两块,每次只使用其中的一块。当这一块的内存用完后,就将还存活着的对象复制到另一块上去,然后再把已使用的内存空间一次清理掉。

           (3)标记-整理算法:标记过程与标记-清除算法中的标记一样,然后让所有存活对象都向一端移动,最后直接清理端边界以外的内存。

           (4)分代收集算法:当代商业虚拟机都采用 “分代收集” 算法,根据对象的存活周期的不同将内存划分为几块。将java堆分为新生代和老年代,然后就可以根据各个年代的特点采用最适当的收集算法。在新生代中,有大批的对象死亡,只有少数存活,采用复制算法; 在老年代中,对象存活率较高,故采用标记-清除或标记-整理算法。

posted @ 2018-09-06 15:28  沉默有时是最好的诉说  阅读(150)  评论(0编辑  收藏  举报