一起学习虚拟机

Jvm虚拟机
1.JVM架构图

(1) 程序计数器:当前线程所执行的字节码的行号指示器,内存空间小,线程私有。
内存溢出情况:唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
(2) 堆:存放对象实例和数组,几乎所有的对象实例都在这里分配内存。
内存溢出情况:OutOfMemoryError
(3)本地方法栈:本地方法栈是为虚拟机使用到的Native方法服务。在Hotspot虚拟机中本地方法栈与虚拟机栈
中合二为一
内存溢出情况: StackOverflowError 和 OutOfMemoryError 异常。
(4) 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(5) 运行时常量池:常量(final)、字符串
2. 类加载机制
概念:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,叫做类加载机制。

类加载的时机:加载(Loading)–>验证(Verification)–>准备(Preparation)–>解析(Resolution)–>初始化(Initialization)–>使用(Using)–>卸载(Unloading)7个阶段,这7个阶段是一个生命周期。其中(验证,准备,解析)被称为连接。

类加载需要注意的地方:
1.加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加
载过程必须按照这种顺序按部就班地开始。

2.解析阶段不需要一定按照顺序,他在某些情况下可以在初始化后在开始,
这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。

3.初始化阶段,虚拟机规范则是严格规定了有且只有5种情况必须立即对
类进行“初始化”(而加载、验证、准备自然需要在此之前开始):

3.1遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

3.2使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3.3当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
3.4当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
3.5当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
类加载的过程:
1、加载
“加载”是“类加载”(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、验证
验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
2.1文件格式验证
验证class文件格式规范。
例如: class文件是否已魔术0xCAFEBABE开头 , 主、次版本号是否在当前虚拟机处理范围之内等
2.2元数据验证
这个阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范要求。
例如:这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)、这个类是否继承了不允许被继承的类(被final修饰的)、如果这个类的父类是抽象类,是否实现了起父类或接口中要求实现的所有方法。
2.3字节码验证
进行数据流和控制流分析,这个阶段对类的方法体进行校验分析,这个阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。
保证访法体中的类型转换有效,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但不能把一个父类对象赋值给子类数据类型、保证跳转命令不会跳转到方法体以外的字节码命令上。
2.4符号引用验证
3、准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
准备阶段进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
准备阶段的初始值“通常情况”下是数据类型的零值。
例如:假设一个类变量定义为:public static int value = 12;
那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为12的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为12的动作将在初始化阶段才会被执行。
上面所说的“通常情况”下初始值是零值,那相对于一些特殊的情况,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,建设上面类变量value定义为:
public static final int value = 12;
编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value设置为12。

4、解析
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
符号引用:
符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
直接引用:
直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
虚拟机规范并没有规定解析阶段发生的具体时间,只要求了在执行anewarry、checkcast、getfield、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic这13个用于操作符号引用的字节码指令之前,先对它们使用的符号引用进行解析,所以虚拟机实现会根据需要来判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。
解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。分别对应编译后常量池内的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四种常量类型。
5、初始化
类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
从另外一个角度来表达:初始化阶段是执行类构造器< clinit >()方法的过程。在以下四种情况下初始化过程会被触发执行:
1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用类的静态方法的时候。
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
4.jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类

类加载器:

1、类与类加载器

对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

2、双亲委派模型 :类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。

2.1从虚拟机的角度说只有两种不同的类加载器:1.启动类加在器 2.所有其他类加载器
2.2从java开发角度来看有三种:
2.2.1启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。

2.2.2扩展类加载器(Extension ClassLoader):该加载器主要是负责加载JAVA_HOME\lib\,该加载器可以被开发者直接使用。

2.2.3应用程序类加载器(Application ClassLoader):该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
我们的应用程序都是由这三类加载器互相配合进行加载的。
另外还有自定义类加载器。自定义类加载器(必须继承 ClassLoader)。
3.字节码执行机制
执行引擎是Java虚拟机最核心的组成部分之一,虚拟机 是一个相对于 物理机 的概念 这两种机器都有代码执行能力。

两者的区别:
物理机的执行引擎是直接建立在处理器 硬件 指令集和操作系统层面上的。
虚拟机的执行引擎则是由自己实现的 可以自行制定指令集与执行引擎的结构体系 并且能够执行那些不被硬件直接支持的指令集格式。

行时栈帧结构:
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构 它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素 栈帧存储了方法的局部变量表 操作数栈 动态连接和方法返回地址等信息 每一个方法从调用开始至执行完成的过程 都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

1.局部变量表:存放基本数据类型变量、引用类型的变量、returnAddress类型的变量。
局部变量表(Local Variable Table)是一组变量值存储空间 用于存放方法参数和方法内部定义的局部变量 在Java程序编译为Class文件时 就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。

操作数栈:
操作数栈(Operand Stack)也常称为操作栈 它是一个后入先出(Last In First Out LIFO)栈 同局部变量表一样 操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中 操作数栈的每一个元素可以是任意的Java数据类型 包括long和double 32位数据类型所占的栈容量为1 64位数据类型所占的栈容量为2 在方法执行的任何时候 操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。

动态连接
每个栈帧都包含一个指向运行时常量池[1]中该栈帧所属方法的引用 持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking) 我们知道Class文件的常量池中存有大量的符号引用 字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数 这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用 这种转化称为静态解析 另外一部分将在每一次运行期间转化为直接引用 这部分称为动态连接
方法返回地址
当一个方法开始执行后 有两种方式可以退出这个方法
第一种方式是执行引擎遇到任意一个方法返回的字节码指令 这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者) 是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定 这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)
第二种方式是 在方法执行过程中遇到了异常 并且这个异常没有在方法体内得到处理 无论是Java虚拟机内部产生的异常 还是代码中使用athrow字节码指令产生的异常 只要在本方法的异常表中没有搜索到匹配的异常处理器 就会导致方法退出 这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion) 一个方法使用异常完成出口的方式退出 是不会给它的上层调用者产生任何返回值的

附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中 例如与调试相关的信息 这部分信息完全取决于具体的虚拟机实现 在实际开发中 一般会把动态连接 方法返回地址与其他附加信息全部归为一类 称为栈帧信息

栈的字节码解释执行引擎
Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。

栈的解释器执行过程
虚拟机最终会对执行过程做一些优化来提高性能 实际的运作过程不一定完全符合概念模型的描述……更准确地说 实际情况会和概念模型差距非常大 这种差距产生的原因是虚拟机中解析器和即时编译器都会对输入的字节码进行优化
4.JVM内存模型
Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是:
1.程序计数器
1.1. 什么是程序计数器?
程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器。也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。
注:但是,如果当前线程正在执行的是一个本地方法,那么此时程序计数器为空。
1.2. 程序计数器的作用
程序计数器有两个作用:
1.字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
1.3. 程序计数器的特点
1.是一块较小的存储空间
2.线程私有。每条线程都有一个程序计数器。
3.是唯一一个不会出现OutOfMemoryError的内存区域。
4.生命周期随着线程的创建而创建,随着线程的结束而死亡。

  1. Java虚拟机栈
    2.1. 什么是Java虚拟机栈?
    Java虚拟机栈是描述Java方法运行过程的内存模型。
    Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做“栈帧”的区域,这块区域用于存储该方法在运行过程中所需要的一些信息,这些信息包括:局部变量表,操作数栈,动态链接,方法出口信息等
    2.2. Java虚拟机栈的特点
    1.局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。
    2.Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError。
    a) StackOverFlowError:
    若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
    b) OutOfMemoryError:
    若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
    3.Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
    注:StackOverFlowError和OutOfMemoryError的异同?
    StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。
    而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。

  2. 本地方法栈
    本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。
    本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
    方法执行完毕后相应的栈帧也会出栈并释放内存空间。
    也会抛出StackOverFlowError和OutOfMemoryError异常。


  3. 4.1. 什么是堆?
    堆是用来存放对象的内存空间。
    几乎所有的对象都存储在堆中。
    4.2. 堆的特点
    1.线程共享
    整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
    2.在虚拟机启动时创建
    3.垃圾回收的主要场所。
    4.可以进一步细分为:新生代、老年代。
    新生代又可被分为:Eden、From Survior、To Survior。
    不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更高效。
    5.堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。

  4. 方法区。
    5.1. 什么是方法区?
    Java虚拟机规范中定义方法区是堆的一个逻辑部分。
    方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
    5.2. 方法区的特点
    1.线程共享
    方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。
    2.永久代
    方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。
    3.内存回收效率低
    方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。
    对方法区的内存回收的主要目标是:对常量池的回收 和 对类型的卸载。
    4.Java虚拟机规范对方法区的要求比较宽松。
    和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。
    5.3. 什么是运行时常量池?
    方法区中存放三种数据:类信息、常量、静态变量、即时编译器编译后的代码。其中常量存储在运行时常量池中。
    我们一般在一个类中通过public static final来声明一个常量。这个类被编译后便生成Class文件,这个类的所有信息都存储在这个class文件中。
    当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。
    当运行时常量池中的某些常量没有被对象引用,同时也没有被变量引用,那么就需要垃圾收集器回收。

  5. 直接内存
    直接内存是除Java虚拟机之外的内存,但也有可能被Java使用。
    在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。
    直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。
    5.GC垃圾回收
    1.为什么需要垃圾回收
    如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分配内存空间而不进行回收。除非内存无限大,我们可以任性的分配而不回收,但是事实并非如此。所以,垃圾回收是必须的。
    2.垃圾回收的方式
    1、 引用计数法
    这个算法的实现是,给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种算法使用场景很多,但是,Java中却没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。
    2、可达性分析法
    通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
    在Java语言中,可以作为GCRoots的对象包括下面几种:
    (1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
    (2). 方法区中的类静态属性引用的对象。
    (3). 方法区中常量引用的对象。
    (4). 本地方法栈中JNI(Native方法)引用的对象。
    3.四种引用状态
    1、强引用
    代码中普遍存在的类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
    2、软引用
    描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。Java中的类SoftReference表示软引用。
    3、弱引用
    描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。
    4、虚引用
    这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系
    4.方法区的垃圾回收:1. 废弃常量。2. 无用的类。
    如何判断废弃常量呢?
    以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

如何判断无用的类呢?需要满足以下三个条件

  1. 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

5.垃圾回收算法:

1、标记-清除(Mark-Sweep)算法
标记-清除(Mark-Sweep)算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。

2、复制(Copying)算法
复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。

3、标记-整理(Mark-Compact)算法
  让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。

4、分代收集算法
现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法是根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。
6.垃圾收集器
1、Serial收集器
采用复制算法的单线程的收集器,单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着它进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。
Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器,
2、ParNew收集器
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。
3、Parallel Scavenge收集器
Parallel Scavenge收集器也是一个新生代收集器,也是用复制算法的收集器,也是并行的多线程收集器
4、Serial Old收集器
Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”
5、Parallel Old收集器
Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
6、CMS收集器
CMS(Conrrurent Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器。使用标记 - 清除算法,收集过程分为如下四步:
(1). 初始标记,标记GCRoots能直接关联到的对象,时间很短。
(2). 并发标记,进行GCRoots Tracing(可达性分析)过程,时间很长。
(3). 重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长。
(4). 并发清除,回收内存空间,时间很长
7、G1收集器特点
(1). 并行和并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。
(2). 分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。
(3). 空间整合。基于标记 - 整理算法,无内存碎片产生。
(4). 可预测的停顿。能简历可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

6.JVM性能监控与故障定位
jdk中自带了很多免费且非常实用的JVM性能分析和故障监控小工具,下面就分别介绍几个常用的小工具:

NO1:jps:显示虚拟机进程
作用:用于查看虚拟机运行了那些进程,并输出这些进程LVMID,即进程id,它是使用最频繁的一个命令,因为其它工具需要依赖jps。首先需要jps输出jvm正在运行的的进程id;然后其它工具根据进程id进行监控对应的进程运行情况。
jps 命令格式: jps [options] [hostid]

-q 只输出LVMID,省略主类名
-m 输出虚拟机进程启动时传递给主类main()函数的参数
-l 输出主类全类名,如果进程执行的是jar包,输出jar路径
-v 输出虚拟机进程的JVM启动参数

NO2:jma java内存映射
语法:jmap [option] pid
示例:jmap -heap 2241
作用:显示java堆信息使用状况

NO3:jstat 统计虚拟机信息
语法:jstat [option vmid [interval [s|ms] [count] ] ]
示例:jstat 2241 500 10
作用:每隔500ms查询一次进程2241垃圾回收状况,共查询10次

NO4:jinfo :java信息配置
语法:jinfo [option] pid
示例:jinfo -flag CMSInitiatingOccupancyFraction 2241
作用:查询CMSInitiatingOccupancyFraction参数值
jinfo pid 显示全部配置信息的值

NO5:jhat 堆转存快照分析
语法:jhat [ options ] heap-dump-file
作用:用于分析jmap生成的内存快照
jhat -J-Xmx1024M 221.bin 会启动一个http端口(默认7000),在浏览器ip:port可以查看分析

NO6:jstack:java堆栈跟踪
语法:jstack [ options ] vmid
示例:jstack -l 4005 > jstack.log
作用:用于生成虚拟机当前时刻的线程快照,定位线程长时间停顿的原因。
JDK的可视化工具JConsole和VisualVM
JVM调优

  1. 堆大小设置
    JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
    例子:典型设置
    java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0

-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxPermSize=16m:设置持久代大小为16m。

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

2.回收器选择:JVM给了三种选择:串行收集器、并行收集器、并发收集器
2.1吞吐量优先的并行收集器
例子:典型配置
1.java -Xmx3800m -Xms3800m -Xmn2g -Xss128k
-XX:+UseParallelGC
-XX:ParallelGCThreads=20

-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

2.java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseParallelGC
-XX:ParallelGCThreads=20
-XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

3.java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseParallelGC
-XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

4.java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseParallelGC
-XX:MaxGCPauseMillis=100
-XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

2.2响应时间优先的并发收集器
例子:典型配置:
1.java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn
设置。
-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。

2.Java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:+UseConcMarkSweepGC
-XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值