JVM是Java虚拟机。它是由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域组成。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行,由于不同系统都有对应相同版本的Java虚拟机,各自系统的虚拟机都可以识别并编译自己的字节码文件,这也就实现了Java的跨平台这一大特性。
JRE称为Java运行环境。这就表明了,所有的Java程序都要在有JRE的情况下才能运行。
JDK称为Java开发环境。是程序开发者用来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
JVM是JRE的一部分。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
Jvm是Java的核心和基础,在Java编译器和Os平台之间的虚拟处理器。它是利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行Java的字节码程序。
由于方法区和堆是线程共享的,并不像虚拟机栈,程序计数器和本地方法栈是线程私有的。所以方法区和堆可以和GC回收扯上关系。
由于在堆中存在在所有的对象,所以在Gc回收之前都要考虑,哪些对象还活着,哪些对象已经死了,活着就让他继续活着,死了就让回收他。这时候就存在了某些算法来通过判断对象的存活状态从而判断是否该被回收。
给对象中添加一个引用计数器,每当一个地方应用了对象,计数器就加一;当引用失效的时候,计数器就减一;通过这样的加加减减,当计数器变为0的时候就表示该对象已死,可以进行回收操作。
缺点:很难解决两个对象之间相互循环引用的情况通过一系列称为“GC Roots"的对象作为起点,从这些节点开始向下搜索,搜索所走过的路劲称为引用链,当一个对象到 GC Roots没有与任何引用链相连(既对象到GC Roots不可达),则可以表示对象已死亡,可以收尸准备后事了。
Java中可以作为GC Roots的对象包括:虚拟机中引用的对象,本地方法栈中本地方法引用的对象,方法区静态属性引用的对象,方法区常量引用的对象
优点:在主流的商用程序语言的实现中,大部分都是通过可达性分析算法来判定对象的死活状态”垃圾不回收,留着回家过年?“,是个垃圾都是要被回收的,当然Java里没用的垃圾也是要被回收的,正在写博客的我不好好学习也是没用的垃圾也要被回收的呜呜呜。
重点介绍:GC垃圾回收
GC的基本原理:将内存中不再使用的对象进行回收,GC中用于回收的方法称为收集器。由于,Gc需要消耗一些资源和时间,所以Java在对象的生命周期特征进行分析后,按照新生代,旧生代的方式来对对象进行收集,以尽可能的缩短Gc对应用造成的暂停
对于不同的对象引用类型,Gc会采用不同的方法进行回收。所以Jvm对象的引用类型被分为了四种类型,对于不同的对象采用不同的引用类型
强引用:默认情况对象下采用的都是强引用,例如new对象的时候,只要强引用还存在,就永远不会被Gc清理。软引用:这种情况一般适用于缓存场景的应用,就比如是一些还有用但是不一定非要存在的对象。这种引用只有当Jvm内存不足的时候才会被Gc回收,如果被回收后,还是没有足够的空间,就会抛出内存溢出异常弱引用:这种就是更弱一点的软引用,但是他在被Gc回收的时候一定会被Gc回收,只能生存到下一次Gc前虚引用:这种引用是最弱的引用关系,一个对象是否有虚引用的存 在,完全不会对生存时间构成影响。为一个对象设置虚引用关联的唯一目的就是希望能 在这个对象被GC回收时收到一个系统通知。方法:最基础的算法,分标记和清除两个阶段:首先标记所需要回收的对象,待标记完成后统一回收被标记的对象
缺点:
由于标记和清除的过程效率都不咋高,所以效率较低被标记的对象位置可能会乱七八糟,所以使用这种方法回收对象会产生大量的不连续的内存碎片。碎片太多就会导致需要分配大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收的动作,这就是一这种算法的空间问题。方法:将一块内存分为两块,每次只利用一块,当这块内存用完后,将还存活的对象复制到另一块上面,最后把第一块用完的内存空间清理掉。
优点:解决了标记清楚算法的内存碎片问题缺点:每次都将内存分为两块,这就导致了内存会缩小一半。方法:标记整理和标记整理算法类似,都是靠标记即将要回收的对象进行回收的。只不过标记整理在标记后会进行清除对象,而标记整理不会直接清除,他会让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存(并更新对象的指针)。
优点:解决前两种的内存碎片和内存缩小的问题缺点:在标记-清除算法的基础上还要进行移动对象,这样会使成本相对增高。这种算法是目前大部分商用虚拟机都在用的垃圾收集算法
核心思想:根据对象的存活周期将 堆 分为:新生代和老年代和永久代。另一个和Gc回收扯上关系的方法区被称为永久代(在最新的船新版本中,已经舍弃了永久代,新增了元空间的概念。要记住永久代使用的是Jvm内存,而元空间使用的则是物理内存)
新生代对象一般存活率较低,每次Gc时都会有大量对象死去,所以采用复制算法。新生代也就可以不使用50%的内存作为空闲,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。新产生的对象优先进去Eden区,当Eden区满了之后再使用Survivor from,当Survivor from 也满了之后就进行新生代GC,一旦发生GC,将10%的活动区间与另外80%中存活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部释放。
老年代中的对象因为对象存活率高、没有额外空间进行分配担保,就使用标记-清除或标记-整理算法。
大对象会直接进入老年代,目的是为了避免在Eden和Survivor区中发生大量的内存复制
长期存活的对象也会直接进入老年代,JVM给每个对象定义一个对象年龄计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳,将被移入Survivor并且年龄设定为1。没熬过一次Minor GC,年龄就加1,当他的年龄到一定程度(默认为15岁,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。但是JVM并不是永远要求年龄必须达到最大年龄才会晋升老年代,如果Survivor 空间中相同年龄(如年龄为x)所有对象大小的总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。
垃圾收集算法是垃圾回收的理论基础,而垃圾收集器则是具体实现
垃圾收集器是具体实现。JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里我们采取的是 HotSpot虚拟机,其内置了 7种类型的垃圾收集器实现。下面就让我们逐个进行学习,分析。
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。Hotspot实现了如此多的收集器,正是因为目前并无完美的收集器出现,只是选择对具体应用最适合的收集器。
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。Serial收集器是最基本、历史最久的收集器。
曾是新生代手机的唯一选择。他是单线程的,只会使用一个CPU或一条收集线程去完成垃圾收集工作,并且它在收集的时候,必须暂停其他所有的工作线程,直到它结束,即“Stop the World”。停掉所有的用户线程,对很多应用来说难以接受。比如你在做一件事情,被别人强制停掉,你心里奔腾而过的“羊驼”还数的过来吗?
尽管如此,它仍然是虚拟机运行在client模式下的默认新生代收集器:简单而高效(与其他收集器的单个线程相比,因为没有线程切换的开销等)。
ParNew收集器是Serial收集器的多线程版本,其它行为和Servial一模一样,包括源码也有一大部分相同。
是许多运行在Servial模式下的Jvm中首选新生代收集器,其中一个很重要的原因就是除了Servial之外,只有他能和老年代CMS收集器配合工作(看第一张连线图)。
Parallel Scavenge收集器也是一个并行的多线程新生代收集器,它使用复制算法。
Parallel Scavenge 大多数特性与 ParNew 相似。
而Parallel Scavenge的关注点与CMS等其他收集器不同:
其它收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量,所谓吞吐量就是 CPU 用于运行用户代码的时间和 CPU 总消耗时间的比值,即 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。高吞吐量意味着高效率地利用 CPU 时间,尽快完成程序计算任务,适合在后台运算而不需要太多交互的任务。Serial Old是Servial收集器的老年代版本,它同样是一个单线程收集器,使用标记整理算法
在次收集器的主要意义也是给Client模式下的虚拟机使用。如果在Server模式下,他还有两大用途:
在JDK1.5以及之前版本中与Parallel Scavenge收集器搭配使用作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用是Parallel Scavenge的老年代版本,是多线程和标记整理算法适合和Parallel Scavenge一起组成一个吞吐量优先的收集组合
JDK1.6以后才存在的
对于较注重吞吐量以及CPU敏感的场合,可以优先考虑Parallel Scavenge加Parallel Old收集器
CMS收集器设计理念:获取最短回收停顿时间。
适用于那些非常重视服务的响应速度的集中在互联网站或者B/S系统的服务端上的Java应用上
基于标记清除算法的基础上实现的(4个步骤)
初始标记:仅仅标记一下GC Roots能直接关联到的对象,速度很快,但是需要”Stop The Word“并发标记:进行GC Roots Tracing的过程,在整个过程中耗时最长重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,当然也是需要”stop the word“并发清除由于整个过程中耗时最长的是并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:
并发收集,停顿时间短。缺点:
在整个GC过程中第四步的并发收集导致会产生浮动垃圾,由于在执行并发清除的过程中,用户线程还在执行,这样就必然会产生新的垃圾,但这些垃圾有恰巧错过了这一次标记,只能等到下一次回收了。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。
毕竟是基于标记清除算法实现的收集器,所以必然会和标记清除算法一样产生大量的空间碎片,大量的看见碎片会给大对象的分配带来麻烦。
CMS是一个针对并发模型的收集器,所以在并发阶段就必然会占用CPU时间,这就会导致应用程序变得很慢,总吞吐量变低,所以CMS对CPU资源是非常的敏感。
G1收集器是当前收集器技术发展最前沿的成果之一,是一款面向服务端应用的垃圾收集器。
在G1之前的其他收集器进行收集的范围都是整个新生代或者老生代。
而G1不再是这样,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合。
出生使命:干掉CMS收集器。
最强王者的理由:
并行与并发,G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短”Stop the Word“停顿时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行分代收集,分代收集在G1中得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象来获取更好的收集效果。空间整合 G1从整体来看是基于标记-整理算法实现的收集器,但是从局部(两个Region之间)上来看是基于复制算法实现的。这意味着G1运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。此特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。可预测的停顿 ,这是G1相对CMS的一大优势,降低停顿时间是G1和CMS共同的关注点。在程序运行过程中,G1 会维护一个 优先级列表,里面记录着各个 Region 的垃圾堆积价值(回收所获得大小和回收所需时间的比值),这样在每次回收的时候,根据 允许的回收时间,优先回收价值最大的 Region,通过这样,我们的 GC 时间就是可预测的。避免全堆扫描——Remembered Set
听着将堆划分为多个 Region 很简单,在实际情况下,我们该如何保证 Region 以及其中对象的独立性呢?
实际上,Region 不可能是独立的,因为一个对象分配到这个 Region 上,但其可能会被堆上的任一个引用所关联。这样在进行可达性分析的时候,难道意味着要进行 全堆扫描 吗?如果这样的话,性能又该如何保证呢?
因此 G1 使用了 Remembered Set 来避免这一现象。在程序对 Reference 类型数据进行写操作时,其会产生一个 Write Barrier 暂时中断写操作,来检查 Reference 引用对象是否处于不同的 Region 中,如果是,则通过 CardTable 将相关引用信息记录到对象所属 Region 的 Remembered Set 中,这样在进行内存回收,可达性分析的时候通过 Remembered Set 则可以保证不对全堆扫描也不会有遗漏。
如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤:
初始标记 仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程,但耗时很短。
并发标记 从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
最终标记 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
筛选回收 首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。