JVM是可运行 Java代码的假想计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。JVM运行在操做系统之上,他与硬件没有直接交互。java
Java源文件经过编译器,可以产生相应的.Class文件,也就是字节码文件,而字节码文件又经过Java虚拟机中的解释器,编译成特定机器上的机器码。 过程以下:算法
Java源文件->编译器->字节码文件->JVM->机器码
复制代码
每一种平台的解释器是不一样的,可是实现的虚拟机是相同的,这也就是Java可以跨平台的缘由了,当一个程序从开始运行,这时候虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者 关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。数组
JVM容许一个应用并发执行多个线程。HotspotJVM中的Java线程与原生操做系统线程有直接的映射关系。当线程本地存储、缓 冲区分配、同步对象、栈、程序计数器等准备好之后,就会建立一个操做系统原生线程。 Java 线程结束,原生线程随之被回收。操做系统负责调度全部线程,并把它们分配到任何可 用的 CPU 上。当原生线程初始化完毕,就会调用Java线程的run()方法。当线程结束时,会释放原生线程和 Java 线程的全部资源。数据结构
一块较小的内存空间,是当前线程所执行的字节码的行号执行器,每条线程都要有个一独立的程序计数器,这类内存也称为“线程私有”的内存。多线程
正在执行Java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。若是仍是Native方法,则为空。并发
这个内存区域是惟一一个在虚拟机中没有规定任何OutOfMemoryError状况的区域。jvm
是描述Java方法执行的内存模型,每一个方法在执行的同时都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息。每个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。spa
栈帧是用来存储数据和部分结果的数据结构,同时也被用来处理动态连接、方法返回值和异常分派。栈帧随着方法的调用而建立,随着方法的结束而销毁-不管方法是正常完成仍是异常完成(抛出了方法内未捕获的异常)都算做方法结束。操作系统
本地方法区和java stack做用相似,区别是虚拟机栈为执行Java方法服务,而本地方法栈则为native方法服务,若是一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。线程
建立的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的区域。 因为如今jvm采用分代收集算法,所以Java堆从GC的角度还能够细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代
永久代用来存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据, HotSpot VM 把 GC 分代收集扩展至方法区,即便用Java堆的永久代来实现方法区,这样HotSpot的垃圾收集器就能够像管理 Java 堆同样管理这部份内存,而没必要为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 所以收益通常很小)。 运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池。用于存放编译器生成的各类字面量和符号引用,这部份内容将在类加载后存放到方法区的运行时常量池中。Java虚拟机对Class文件对每一部分的格式都有严格的规定,每个字节用于存储哪一种数据结构都必须符合规范上的要求,这样才会被虚拟机承认、装载和执行。
Java堆从GC的角度还能够细分为:新生代(Eden区、From survivor区和To survivor区)和老年代
用来存放新生的对象。通常占据1/3的空间。因为频繁建立对象,因此新生代会频繁触发MinorGC进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
Java新对象的出生地(若是新建立的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。
上一次GC的幸存者,做为这一次的GC的被扫描者
保留了一次MinorGC过程当中的幸存者
MinorGC采用复制算法 一、Eden、From Survivor复制到 To Survivor,年龄+1 首先把Eden和From Survivor区域中存活的对象复制到To Survivor区域若是有对象的年 龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1,(若是 ServicorTo 不 够位置了就放到老年区); 二、清空Eden、From Survivor 而后,清空Eden、From Survivor中的对象 三、From Survivor和To Survivor互换 From Survivor和To Survivor互换,原To Survivor成为下一次GC时的From Survivor区。
主要存放应用程序中生命周期长的内存对象 老年代的对象比较稳定,因此MinorGC不会频繁执行。在进行MajorGC前通常都先执行了一次MinorGC,使得有新生代的对象晋升到老年代,致使空间不够用时才触发。当没法找到足 够大的连续空间分配给新建立的较大对象时也会提早触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC采用标记清除算法,首先扫描一次全部老年代,标记处存活的对象,而后回收没有标记的对象。MajorGC会产生内存碎片,为了减小内存损耗,咱们通常须要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被 放入永久区域,它和和存放实例的区域不一样,GC 不会在主程序运行期对永久区域进行清理。因此这 也致使了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
在 Java8 中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代相似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。所以,默认状况下,元空间的大小仅受本地内存限制。类的元数据放入native memory, 字符串池和类的静态变量放入java 堆中,这样能够加载多少类的元数据就再也不由 MaxPermSize控制, 而由系统的实际可用空间来控制。
在 Java 中,引用和对象是有关联的。若是要操做对象则必须用引用进行。所以,很显然一个简单 的办法是经过引用计数来判断一个对象是否能够回收。简单说,即一个对象若是没有任何与之关 联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收 对象。
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。经过一系列的“GC roots” 对象做为起点搜索。若是在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。 要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要通过两次标记 过程。两次标记后仍然是可回收对象,则将面临回收。
最基础的垃圾回收算法,分为两阶段,标注和清除。标记阶段标记出全部须要回收的对象,清除阶段回收被标记的对象所占用的空间。如图
从图中咱们能够发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象找不到可利用的空间的问题。
为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另外一块上去,把已使用 的内存清掉,如图:
标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端,而后清除端边界外的对象,如图:
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不一样生命周期将内存 划分为不一样的域,通常状况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特色是每次垃圾回收时只有少许对象须要被回收,新生代的特色是每次垃 圾回收时都有大量垃圾须要被回收,所以能够根据不一样区域选择不一样的算法。
目前大部分 JVM 的 GC 对于新生代都采起 Copying 算法,由于新生代中每次垃圾回收都要 回收大部分对象,即要复制的操做比较少,但一般并非按照 1:1 来划分新生代。通常将新生代 划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用 Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另 一块 Survivor 空间中。
老年代由于每次回收少许对象,于是采用标记整理算法
一、Java虚拟机提到过的方法区的永生代,他用来存储class类,常量,方法描述等。对永久代的回收主要包括废弃常量和无用的类。
二、对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目 前存放对象的那一块),少数状况会直接分配到老生代。
三、当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和 From Space 区的存活对象会被挪到 To Space,而后将 Eden Space 和 From Space 进行清理。
四、若是 To Space 没法足够存储某个对象,则将这个对象存储到老生代。
五、在进行 GC 后,使用的即是 Eden Space 和 To Space 了,如此反复循环。
六、当对象的Survivor区躲过一次GC后,其年龄就会+1。默认状况下年龄到达15的对象会被移到老生代中。
在 Java 中最多见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即 使该对象之后永远都不会被用到 JVM 也不会回收。所以强引用是形成 Java 内存泄漏的主要缘由之 一。
软引用须要用 SoftReference 类来实现,对于只有软引用的对象来讲,当系统内存足够时它 不会被回收,当系统内存空间不足时它会被回收。软引用一般用在对内存敏感的程序中。
弱引用须要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象 来讲,只要垃圾回收机制一运行,无论 JVM 的内存空间是否足够,总会回收该对象占用的内存。
虚引用须要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚 引用的主要做用是跟踪对象被垃圾回收的状态。
当前主流 VM 垃圾收集都采用”分代收集”(Generational Collection)算法, 这种算法会根据对象存活周期的不一样将内存划分为几块, 如JVM 中的 新生代、老年代、永久代,这样就能够根据 各年代特色分别采用最适当的 GC 算法
每次垃圾收集都能发现大批对象已死, 只有少许存活. 所以选用复制算法, 只须要付出少许 存活对象的复制成本就能够完成收集.
由于对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标 记—整理”算法来进行回收, 没必要进行内存复制, 且直接腾出空闲内存.
分区算法则将整个堆空间划分为连续的不一样小区间,每一个小区间独立使用,独立回收.这样作的好处是可控制一次回收多少个小区间, 根据目标停顿时间,每次合理地回收若干个小区间(而不是整个堆),从而减小一次 GC 所产生的停顿。
Java堆内存被划分为新生代和老年代两部分,新生代主要使用复制和标记-清除垃圾回收算法;老年代主要使用标记-整理垃圾回收算法,jdk1.6中虚拟机的垃圾收集器以下:
Serial是最基本的垃圾收集器,使用复制算法.serial是一个单线程的收集器,只会使用一个CPU或一条线程去完成垃圾收集工做,而且在进行垃圾收集的同时,必须暂停其余全部的工做线程,知道垃圾收集结束. serial垃圾收集器虽然在收集垃圾过程当中须要暂停其余的工做线程,可是它简单高效,对于限定单个cpu环境来讲,没有线程交互的开销,能够得到最高的单线程垃圾收集效率,所以serial垃圾收集器依然是Java虚拟机运行在 Client 模式下默认的新生代垃圾收集器
ParNew垃圾收集器实际上是Serial收集器的多线程版,也是使用复制算法除了使用多线程进行垃 圾收集以外,其他的行为和 Serial 收集器彻底同样,ParNew 垃圾收集器在垃圾收集过程当中一样也 要暂停全部其余的工做线程。ParNew收集器默认开启和CPU数目相同的线程数,能够经过-XX:ParallelGCThreads参数来限制垃圾收集器的线程数.ParNew 虽然是除了多线程外和 Serial 收集器几乎彻底同样,可是 ParNew 垃圾收集器是不少 java 虚拟机运行在 Server 模式下新生代的默认垃圾收集器。
Parallel Scavenge收集器是一个新生代垃圾收集器,一样使用复制算法,也是一个多线程的垃圾收集器,他重点关注的是程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间), 高吞吐量能够最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而 不须要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个 重要区别。
Serial Old 是 Serial 垃圾收集器年老代版本,它一样是个单线程的收集器,使用标记-整理算法, 这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。 在 Server 模式下,主要有两个用途:
Parallel Old 收集器是 Parallel Scavenge 的年老代版本,使用多线程的标记-整理算法,在 JDK1.6 才开始提供。 在 JDK1.6 以前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保证新生代的吞吐量优先,没法保证总体的吞吐量,Parallel Old 正是为了在年老代一样提供吞 吐量优先的垃圾收集器,若是系统对吞吐量要求比较高,能够优先考虑新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。 新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图:
只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然须要暂停全部的工做线程。 Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾 回收停顿时间,和其余年老代使用标记-整理算法不一样,它使用多线程的标记-清除算法。 最短的垃圾收集停顿时间能够为交互比较高的程序提升用户体验。 CMS 工做机制相比其余的垃圾收集器来讲更复杂,整个过程分为如下 4 个阶段: 13/04/2018 Page 33 of 283
进行 GC Roots 跟踪的过程,和用户线程一块儿工做,不须要暂停工做线程。
为了修正在并发标记期间,因用户程序继续运行而致使标记产生变更的那一部分对象的标记 记录,仍然须要暂停全部的工做线程。 清除 GC Roots 不可达对象,和用户线程一块儿工做,不须要暂停工做线程。因为耗时最长的并 发标记和并发清除过程当中,垃圾收集线程能够和用户如今一块儿并发工做,因此整体上来看 CMS 收集器的内存回收和用户线程是一块儿并发地执行。 CMS 收集器工做过程:
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收 集器两个最突出的改进是: