JVM小结

JVM之前就有接触过,但一直都没有做一个总结,很多概念没有一个系统性的认识,借这篇对JVM的一些知识点做一个总结与归纳。

1.JVM体系结构图

每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

                    JVM体系结构图(英文)

                                                                 JVM体系结构图(中文)

上面有两张图,由于编辑器的问题,两张图挨在一起。左侧是JVM的完整组成结构,右边是对JVM运行时数据区(JV内存模型)的中文翻译结构图,相当于左图的中间和下面部分,上部分的类加载器没有画出来。

2.描述JVM体系结构

(1)类加载器:JVM启动时或者类运行时将需要的class加载到JVM中。每个被装载的类的类型对应一个Class实例,唯一表示该类,存于堆中。

(2)执行引擎:负责执行JVM的字节码指令(CPU)。执行引擎是JVM的核心部分,作用是解析字节码指令,得到执行结果(实现方式:直接执行,JIT(just in time)即时编译转成本地代码执行,寄存器芯片模式执行,基于栈执行)。本质上就是一个个方法串起来的流程。每个Java线程就是一个执行引擎的实例,一个JVM实例中会有多个执行引擎在工作,有的执行用户程序,有的执行JVM内部程序(GC).

(3)内存区:模拟物理机的存储、记录和调度等功能模块,如寄存器或者PC指针记录器。存储执行引擎执行时所需要存储的数据。

(4)本地方法接口:调用操作系统本地方法返回结果。

3.内存区的详细结构

堆和方法区是线程共享的,虚拟机栈,本地方法栈,程序计数器是线程私有的。

1)程序计数器

程序计数器就是记录当前线程执行程序的位置,改变计数器的值来确定执行的下一条指令,比如循环、分支、方法跳转、异常处理,线程恢复都是依赖程序计数器来完成。
    Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。
    如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2)JAVA栈区

java虚拟机栈是线程私有,生命周期与线程相同。创建线程的时候就会创建一个java虚拟机栈。
    虚拟机执行java程序的时候,每个方法都会创建一个栈帧,栈帧存放在java虚拟机栈中,通过压栈出栈的方式进行方法调用。
    栈帧又分为一下几个区域:局部变量表、操作数栈、动态连接、方法出口等。

3)本地方法栈

本地方法栈 为虚拟机使用到本地方法服务(native)。本地方法栈为线程私有,功能和虚拟机栈非常类似。线程在调用本地方法时,来存储本地方法的局部变量表,本地方法的操作数栈等等信息。

4)堆

堆是被所有线程共享的区域,实在虚拟机启动时创建的。堆里面存放的都是对象的实例(new 出来的对象都存在堆中)。
    我们平常所说的垃圾回收,主要回收的就是堆区。为了提升垃圾回收的性能,又把堆分成两块区新生代(young)年老代(old),更细一点划分新生代又可划分为Eden区和2个Survivor区(From Survivor和To Survivor)。

5)方法区

方法区是被所有线程共享区域,用于存放已被虚拟机加载类信息,常量,静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代(permanment generation)。

4.垃圾回收

基础的回收算法

1)引用计数法

引用计数法是唯一没有使用根集的垃圾回收,该算法使用引用计数器来区分存活对象和不再使用的对象。

2)标记-清除(Mark-Sweep)算法

正如其名称所暗示的那样,标记-清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向——用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器(Tracing Collector)。

此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时会产生内存碎片。

尽管最初版本的标记-清除算法在今天看来还存在效率不高(标记和清除是两个相当耗时的过程)等诸多缺陷,但是,几乎所有现代垃圾收集算法都是标记-清除思想的延续。

3)复制(Copying)算法

为了解决标记-清除算法在垃圾收集效率方面的缺陷,产生了复制算法。复制算法别出心裁地将堆空间一分为二,并使用简单的复制操作来完成垃圾收集工作。

分区、复制的思路不仅大幅提高了垃圾收集的效率,而且也将原本繁纷复杂的内存分配算法变得前所未有地简明和扼要(既然每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了)!不过,任何奇迹的出现都有一定的代价,在垃圾收集技术中,复制算法提高效率的代价是人为地将可用内存缩小了一半。

整合回收算法

 1)标记-整理(Mark-Compact)算法(或 标记 - 压缩)

标记-整理算法是标记-清除算法和复制算法的有机结合。把标记-清除算法在内存占用上的优点和复制算法在执行效率上的特长综合起来,这是所有人都希望看到的结果。不过,两种垃圾收集算法的整合并不像 1 加 1 等于 2 那样简单,必须引入一些全新的思路。

实验表明,标记-整理算法的总体执行效率高于标记-清除算法,又不像复制算法那样需要牺牲一半的存储空间,这显然是一种非常理想的结果。在许多现代的垃圾收集器中,都使用了标记-整理算法或其改进版本。

2)分代回收

分代收集算法通常将堆中的内存块按寿命分为两类,年老的和年轻的。垃圾收集器使用不同的收集算法或收集策略,分别处理这两类内存块,并特别地把主要工作时间花在处理年轻的内存块上。

根据对象的生命周期分类,JVM的堆空间分成年轻代(Young Gen)、年老代(Old Gen)和持久代(Perm Gen)。Sun JVM对不同代将使用不同的垃圾回收算法。

堆区的分代回收

Young:新生代又分为Eden 和两片生存空间(survivor spaces)。所有新建对象都放在eden,两片生存区中保证有一片空间在任何时间是空的,当垃圾收集时,假如suvivor1是空的,survivor2里面是原先有的对象,则先把 Eden 中的活对象复制到survivor1,再把survivor2复制到survivor1,然后清理eden和survior2。直到到达最大门限值(老化),然后复制到旧生代(old)。可以用-Xmn参数规定其大小。

新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少。Young适合使用复制算法

在GC前To 幸存区(survivor)保持清空。对象保存在 Eden 和 From 幸存区(survivor)中,GC运行时,Eden中的幸存对象被复制到 To 幸存区(survivor)。针对 From 幸存区(survivor)中的幸存对象,会考虑对象年龄,如果年龄没达到阀值(tenuring threshold),对象会被复制到To 幸存区(survivor)。如果达到阀值对象被复制到老年代。复制阶段完成后,Eden 和From 幸存区中只保存死对象,可以视为清空。如果在复制过程中To 幸存区被填满了,剩余的对象会被复制到老年代中。最后 From 幸存区和 To幸存区会调换下名字,在下次GC时,To 幸存区会成为From 幸存区。

Old:young总是不断膨胀的,其中有些对象在young中发生的几次收集中始终都在survivor中存活,终于survivor的空间也撑不住了,于是这些对象被搬到了old区(也有可能是young区的对象被收集了几次后被搬到old区,这个几次由参数-XX:MaxTenuringThreshold=参数来设置)。对象在年老代的存活时间比较长,如果进行使用复制算法来进行GC,需要移动大量的对象,导致效率很低。所以,Old适合使用标记-清除算法(或者标记-清除-整理),需要被GC的对象很少,那么标记的对象就很少,在对标记的对象进行回收,效率就会很高。

方法区的回收

Permanent:持久代。装载Class信息等基础数据,默认64M,如果是类很多很多的服务程序,需要加大其设置-XX:MaxPermSize=,否则它满了之后会引起full GC或者Out of Memory。像Spring,Hibernate这类经常动态生成类的框架需要更多的持久代内存。

参考资料:https://www.jianshu.com/p/a60d6ef0771b

http://www.cnblogs.com/ywl925/p/3925637.html

https://www.cnblogs.com/xingele0917/p/4270188.html

猜你喜欢

转载自blog.csdn.net/w450093854/article/details/83614792
JVM