• JMV的学习


    一.JVM学习
    1.1JVM运行机制的最重要的三点:加载(类加载器,classloader) 、内存管理(包含GC)、执行。
    如果再加上JDK所作的把java文件编译为二进制class文件的步骤,就组成了Java代码的执行机制三部曲:
    编译–>加载–>执行
    2.1 Java编译机制
    Java编译机制不属于JVM,但是JVM运行class文件,首先需要JDK把java源码编译成class文件。
    JVM规范规定了class文件的格式,但并未规定java源码如何编译成class文件,以及如何执行class文件。
    各厂商的JDK编译器各不相同,比如Sun JDK中的编译器是javac.exe。
    Java 源码编译由以下三个过程组成:
    • 分析和输入到符号表
    • 注解处理
    • 语义分析和生成class文件
    2.1.1 分析和输入到符号表(Parse and Enter):
    Parse过程主要做词法分析和语法分析。
    Enter过程是将符号输入到符号表。
      2.1.2 注解处理(Annotation Processing):
    根据注解产生一些新的代码,或进行一些特殊检查。
    2.1.3 语义分析和生成class文件(Analyse and Generate):
    语义分析包括声明检查、类型检查、语句到达检查、exception检查、变量赋值检查、解除语法、泛型转换等。
    采用后续遍历语法树生成class文件。
    class文件包含了如下信息:
    结构信息:包括格式版本号及各部分的数量与大小的信息。
    元数据:主要是声明和常量的信息。
    方法信息:主要是语句和表达式的信息。
    2.2 类加载机制
    类加载机制是指class文件加载到JVM,并形成class对象的机制。类的加载又分为三个步骤:
    装载–>链接–>初始化
    2.2.1 装载(Load)
    装载过程负责找到二进制代码并加载到JVM中。
    对于接口或非数组型的类,由ClassLoader直接加载;
    对于数组型的类,数组类由JVM直接创建,而数组中的元素类型还是由ClassLoader加载。
    2.2.2 链接(Link)
    链接过程又分为:校验,准备,和解析。
    校验格式遵循JVM规范。
    准备过程会初始化类中的静态变量,并将其值赋为默认值。(特别注意这一点:静态变量初始化是在Initialize阶段之前完成的)
    最后对类中所有的属性、方法进行验证。
    装载和链接过程完成后,二进制字节码就转换成了class对象。此时还未初始化。
    2.2.3 初始化(Initialize)
    初始化即执行类的静态初始化代码、静态属性初始化、实例初始块代码、构造方法等。(但静态初始化和实例/构造方法初始化不是同时进行的,一个是类加载完后进行,一个是实例化的时候进行)
    初始化是在初次主动使用对象前执行,在以下4种情况下初始化过程会被触发执行:
    1) 调用了new;
    2) 反射执行newInstance()方法;
    3) 子类调用了初始化;
    4) JVM启动过程中指定的初始化类。
    关于静态变量、静态初始化块、实例初始化块、构造方法的初始化顺序,见另一文《Java类和实例初始化顺序》
    JVM对类的加载,是通过ClassLoader及其子类来完成的,分为:
    Bootstrap ClassLoader, Extension ClassLoader, Application ClassLoader, Custom ClassLoader。
    1) Bootstrap ClassLoader:
    Sun JDK采用C++实现此类,启动时会初始化此ClassLoader,并由它完成jre/lib/rt.jar包里所有class文件的加载。
    如果用java代码去打印此类,只会显示null。
    2) Extension ClassLoader:
    用来加载扩展包。Sun JDK中,继承自java.lang.ClassLoader,全限定名为:sun.misc.Launcher.ExtClassLoader
    3) Application ClassLoader:
    用来加载classpath指定的jar包。Sun JDK中,继承自java.lang.ClassLoader,全限定名为:sun.misc.Launcher.ExtClassLoader
    (注意:ExtClassLoader和AppClassLoader是属于同一个package的,而不是加载关系中的父子关系)
    4) Custom ClassLoader:
    根据用户的需要定制自己的类加载过程,在运行期进行指定类的动态实时加载。
    在代码中可以通过类名.class.getClassLoader()或类名.class.getClassLoader().getParent()或继续.getParent()来得到类名信息。
    Sun JDK中各ClassLoader的继承关系如下图:
    其实IBM的JDK中用到的ClassLoader基本都是Sun的这一套,并在此基础上再实现了一些ClassLoader类。
    加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。
    而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
    这就是所谓的双亲委托模式(英文名为parent delegation,其实是单亲,双亲这个词容易让人误会),这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。比如java.lang.String在系统启动的时候由Bootstrap ClassLoader加载了,所以用户就不能再试图自己写一个java.lang.String来代替原有的String类了。
    命名空间:
    Java的命名空间其实说法并不统一。
    一说Java的命名空间就是package,如同C#里的namespace。
    一说Java的命名空间是由类装载器实例所装载的类组成。每个类装载器实例有自己的命名空间,命名空间由所有以此装载器实例为创始类装载器的类组成。
    不管怎么说,不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。
    隐式加载和显式加载
    隐式加载:程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。
    显式加载:通过class.forname(),class.loadClass()等方法,显式加载需要的类。
    两个异常ClassNotFoundException和NoClassDefFoundError的区别:
    ClassNotFoundException: 当前ClassLoader加载类时未找到类文件。
    NoClassDefFoundError: 主要原因是加载的类中引用到得其他类找不到。
    2.3 类执行机制
    前面说了,JVM规范并没有规定如何执行class文件。计算机不能理解高级语言(如Java,C),只能理解机器语言。Java编译器是把Java文件编译为二进制字节码。这是一种中间代码,计算机仍然不懂。这就需要JVM把它翻译成机器码。翻译有两种方式:解释和编译。那么在JVM执行类的时候,就有两种方式了:解释执行和编译执行
    解释执行:
    在运行程序的时候才翻译,且每次运行都需要翻译,所以效率低。
    JVM采用了invokestatic、invokevirtual、invokeinterface、invokespecial四个指令来执行不同的方法调用,基本上可以从名字看出来各自的用途,其中invokespecial是用来调用private方法和实例初始化方法。
    编译执行:
    将字节码编译成机器码执行。这里需要特别指出的是,Sun JVM支持在运行时编译(Just In Time, JIT编译)。Sun JVM在执行过程中对执行频率高的代码(Hotspot,热点)进行编译,对执行不频繁的代码则继续采用解释的方式,所以Sun JVM又称为Hotspot VM。
    Sun JVM提供了两种编译模式: client compiler(-client)和server compiler(-server)。
    • client comiler: 又称为C1,轻量级,只做少量优化,占用内存少。主要优化有:
    • 方法内联
    • 去虚拟化
    • 冗余削除
    • server compiler: 又称为C2,重量级,采用大量优化,占用内存多。逃逸分析是C2进行很多优化的基础。
    主要优化有:
    • 变量替换
    • 栈上分配
    • 同步削除
    逃逸分析(Escape Analysis):是指根据运行状况来判断方法中的便利是否会被外部读取。如会被读取,则此变量是逃逸的。比如在方法中,给外部全局变量赋值,或方法有返回值,或对象引用传递等。如果没有发生逃逸,则可以对代码进行上述优化
    几种情况下:
    32位windows机器始终都是client模式。
    当机器配置CPU超过2核且内存超过2G,默认为server模式。
    可以通过增加-client或-server来强制指定。
    查看本机默认编译方式:
    在命令行执行”java –version”
    如:
    java version “1.7.0_01″
    Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
    Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)
    最后一行显示了其JVM默认为server模式编译。mixed mode表示解释和编译混合执行,区别于单纯的解释执行和编译执行。
    ==================================================================
    3. JVM内存管理
    3.1 内存空间划分
    JVM在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,各区域有各自不同的用途和生存期。按规范,JVM所管理的内存包括以下几个运行时数据区域:
    • 程序计数器(Program Counter Register):
    程序计数器是一块比较小的内存空间,它是线程私有的内存,指向当前线程所执行的字节码的位置。
    程序计数器是唯一一块不会抛出OutOfMemoryError的区域。 
    • 虚拟机栈(VM Stacks):
    即栈,或称方法栈。也是线程私有的。基本操作为压栈和出栈,单位为栈帧,顺序为先进后出。
    栈帧由三部分组成:局部变量区、操作数栈、栈帧数据区。(动态链接,方法出口)
    局部变量表存放了编译器可知的基本数据类型、对象引用和方法返回值等。
    栈是运行时的单位。
    StackOverFlowError: 当线程请求的栈深度大于虚拟机所允许的深度时,抛出StackOverFlowError。换句话说,当需要存储的数据超过了分配的栈空间时,就会抛这个错误信息。比如死循环、递归次数过多等。
    OutOfMemoryError: 虚拟机栈动态扩展到无法申请到足够的内存时,就会抛OutOfMemoryError。但基本很少会碰到这个Error。
    • 本地方法栈(Native Method Stacks):
    本地方法栈是为虚拟机使用的本地方法服务的。而虚拟机栈则是为虚拟机执行Java方法服务的。两者很相似,HotSpot虚拟机是直接把两者合二为一的。
    当然也是线程私有的。 
    • 堆(Heap):
    一个JVM只有一个堆,所有线程共享。堆用来存放类实例和数组。这块区域是GC管理的主要区域。
    堆是存储的单位。
    当堆中没有足够内存来分配对象实例,且堆无法再扩展时,就会OOM。
    • 方法区(Method Area):
    又称为非堆(Non-Heap),也是所有线程共享的。
    用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放编译期生成的常量和符号引用。
    HotSpot虚拟机使用Perm代(永久代)来实现方法区,所以在一定概念和时间范围内可以相互通用。但以后HotSpot也将采用Native Memory来实现方法区。
    3.2 对象访问
    我们知道,一个对象的引用reference是分配在栈空间的局部变量表里的,指向对象的引用。但JVM规范并没有规定对象的访问方式。主流的访问方式有两种:使用句柄和直接访问。
    使用句柄:Java堆中划分一块区域作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。如图:
    直接访问: reference中直接存储堆中的对象地址。如图:
    使用句柄的好处:当发生GC时,对象经常会发生移动,而reference中存储的句柄地址就不需要发生变化,变化的只是句柄中的对象指针。
    直接访问的好处:速度!
    HotSpot采用直接访问的方式。
    3.3 GC
    3.3.1 垃圾回收算法:
    引用计数(Reference Counting)
    标记-清除(Mark-Sweep)
    复制(Copying)
    标记-整理(Mark-Compact)
    增量收集(Incremental Collecting)
    分代(Generational Collecting):
    3.3.2 垃圾收集器:
    HotSpot JVM有如下几种垃圾收集器,这里不展开了:
    Serial收集器
    ParNew收集器
    Parallel Scavenge收集器
    Serial Old收集器
    Scavenge Old收集器
    CMS收集器
    G1收集器
     3.3.3 分代回收:
    不同的对象的生命周期是不一样的,因此对不同生命周期的对象,可以采取不同的回收方式,以提高回收效率。所以分代收集算法是以其他算法为基础的。
    在HotSpot JVM中将分配到的内存堆(Heap)分为两个物理区域,一个是年轻代(Young Generation),一个是年老代(Tenured Generation),另外再加上持久代(Permanent Generation),就组成了分代回收GC中的三个内存区域。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。
    Young(年轻代): 一般来说年轻代又分为一个Eden区和两个Survivor区。(也可分多个Survivor区) Survivor区无先后顺序,彼此对等。大部分对象在Eden区生成。
    当Eden区满的时候,存活的对象就被复制到其中一个Survivor区中,我们权且称其为主Survivor区;
    当此Survivor区的数据又满的时候,其中的对象就被复制到另一个Survivor区,原Survivor区被清空。那么现在,第二个Survivor区就成了主区,所以它同时又接收Eden区复制转移过来的对象。
    当此Survivor区再次满的时候,复制转移了两次还存活的对象,就被复制到年老代中去了。(转移几次才复制到老年代中,可通过参数配置)
    Tenured(老年代):一般来说,年老代存放的都是存活期较长的对象。
    Permanent(持久代): 用于存放静态数据,如类、方法等。注意,持久代仅仅是HotSpot虚拟机目前对方法区的实现方式,其他虚拟机是没有持久代的,所以持久代不等同于方法区。
    分代垃圾回收分为两种:Scaverage GC和Full GC
    Scaverage GC:
    当新产生对象,写入Eden区,而Eden区满了的时候,就会触发Scaverage GC。这时GC就会清理Eden区,清除非存活对象,把存活的对象写入Survivor区, 同时清理两个Survivor区。Eden区内存分配不大,且大部分对象都是在Eden区产生的,所以Scaverage GC会比较频繁。
    Full GC:
    Full GC会对整个堆进行整理,包括年轻代、年老代、持久代。Full GC会消耗很多内存,频繁的Full GC更会严重影响性能,所以要避免频繁的Full GC。会导致Full GC的可能原因有:
    • Tenured Generation写满了。
    • Perm Generation写满了。
    • 显式调用System.gc()
    3.3.4 常见配置汇总
      1. 堆设置
    • -Xms:初始堆大小
    • -Xmx:最大堆大小
    • -XX:NewSize=n:设置年轻代大小
    • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
    • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
    • -XX:MaxPermSize=n:设置持久代大小
      1. 收集器设置
    • -XX:+UseSerialGC:设置串行收集器
    • -XX:+UseParallelGC:设置并行收集器
    • -XX:+UseParalledlOldGC:设置并行年老代收集器
    • -XX:+UseConcMarkSweepGC:设置并发收集器
      1. 垃圾回收统计信息
    • -XX:+PrintGC
    • -XX:+Printetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:filename
      1. 并行收集器设置
    • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
    • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
    • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
      1. 并发收集器设置
    • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
    • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
  • 相关阅读:
    React 进修之路(1)
    requireJS的配置心得
    DOM的内部插入和外部插入
    h5移动端设备像素比dpr介绍
    原生js--事件类型
    React 进修之路(3)
    javaScript
    html&css
    MyBatis
    Maven基础
  • 原文地址:https://www.cnblogs.com/llaq/p/9451777.html
Copyright © 2020-2023  润新知