标签:收集 原理 linux 父类 方法区 final compact info 隔离
JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,它屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码,ByteCode), 就可以在多种平台上不加修改地运行。这背后其实就是JVM把字节码翻译成具体平台上的机器指令,从而实现“一次编写,到处运行(Write Once, Run Anywhere)”。
Java为什么能够跨平台?
Java引入了字节码的概念,jvm 只能认识字节码,并将它们解释到系统的API调用。针对不同的系统有不同的jvm实现,有 Linux 版本的 jvm 实现,也有 Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。在不同的系统平台上运行是通过JAVA解释器将字节码解释为不同平台的机器码,在不同的 jvm 实现上会映射到不同系统的 API 调用,从而实现代码的不加修改即可跨平台运行。
三者的关系是:JDK>JRE>JVM
按照阶段分为两个阶段:
编译阶段:当我们将一个.java的文件进行编译,编译程序会生成一个相同名字而后缀为.class的文件。
运行阶段主要分为以下步骤:
验证
验证、准备、解析这三步可以看做是一个连接的过程,将类的字节码连接到JVM的运行状态之中
验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会威胁到jvm的安全,主要包括以下几个方面的验证:
java public static int a=7
解析
解析是将常量池内的符号引用转为直接引用(如物理内存地址指针)
初始化
到了初始化阶段,jvm才真正开始执行类中定义的java代码
1)初始化阶段是执行类构造器
2)当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先触发其父类的初始化。
3)虚拟机会保证一个类的
程序计数器
程序计数器(Progarm Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码行号指示器。在JVM中,通过程序计数器来记录某个线程的字节码执行位置,或者说记录下一条要运行的指令。程序计数器是具备线程隔离的特性,也就是说,每个线程工作时都有属于自己的独立计数器,互不影响,是一块线程私有的内存空间。
如果当前正在执行的是一个java方法,程序计数器会记录正在执行的java字节码地址;如果正在执行的是native方法,则程序计数器为空。
Java虚拟机栈
java虚拟机栈是线程私有的内存空间,它用来保存方法的局部变量、部分结果,并参与方法的调用和返回。
虚拟机栈在运营师采用栈帧来保存数据,栈帧中主要有局部变量表、操作数栈、动态链接地址、返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作,相应的,方法的返回则对应着栈帧的出战操作。
和java栈相关的两个异常:在线程的计算过程中,如果请求的栈的深度大于最大可用的栈深度,则抛出改异常。
如果java的栈可以扩展,在程序运行过程中,没有足够的内存来支撑程序的扩展,则抛出该异常。
本地方法栈
本地方法栈和java虚拟机栈功能类似,本地方法栈主要管理本地方法栈的调用,一般是指有C实现的。和java虚拟机栈一样会抛出StackOverFlowError和OutOfMemoryError异常
方法区
方法区是java内存区域中比较重要的一部分,主要保存的信息是元数据。其中最为重要的是类的类型信息、常量池、域信息、方法信息。
Java堆
Java堆可以说是Java运行时内存中最为重要的一部分,几乎所有的对象和数据都是在堆中分配空间的。Java堆分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象,如果对象一直没有被回收,生存的足够长,老年对象就会被移入老年代。
新生代又可以细分为eden、surivor space0(s0或者from space)和surivor space1(s1或者To space)。eden存放刚刚创建的对象,s0和s1存放的对象至少经历了一次垃圾回收,等幸存下来。如果幸存去的对象到了指定年龄仍未被回收,就会进入老年代。
持久代:Permanent Generation。在Sun的JVM中就是方法区的意思,尽管有些JVM大多没有这一代。主要存放常量及类的一些信息默认最小值为16MB,最大值为64MB
Mark-Compact(标记-整理)算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,
分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块并采用不用的垃圾收集算法。
一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
Serial Old收集器
cms(concurrent mark sweep)收集器
老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见后面),当用户线程内存不足时,采用备用方案Serial Old收集。
初始标记阶段仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创建新对象,这阶段需要停顿线程,但耗时很短。
并发标记阶段是从 GC Root 开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中,这阶段需要停顿线程,但是可并行执行。
最后在筛选回收阶段首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划,从Sun公司透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。通过下图可以比较清楚地看到G1收集器的运作步骤中并发和需要停顿的阶段。
标签:收集 原理 linux 父类 方法区 final compact info 隔离
原文地址:https://www.cnblogs.com/cambodia/p/12149892.html