jvm_jvm- - CSDN
jvm 订阅
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。 展开全文
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
信息
软件语言
Java
简    称
JVM
应用学科
计算机软件
中文名
Java虚拟机
外文名
Java Virtual Machine
JVM概述
Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。Java虚拟机不仅是一种跨平台的软件,而且是一种新的网络计算平台。该平台包括许多相关的技术,如符合开放接口标准的各种API、优化技术等。Java技术使同一种应用可以运行在不同的平台上。Java平台可分为两部分,即Java虚拟机(Java virtual machine,JVM)和Java API类库。 [1] 
收起全文
  • 本篇文章通过我和三妹对话的形式来谈一谈“究竟什么是 JVM”。 教妹学 Java,没见过这么有趣的标题吧?“语不惊人死不休”,没错,本篇文章的标题就是这么酷炫,接受不了的同学就别点进来看了,所谓好奇心害死猫;...

    大家好,我是沉默王二,一个和黄家驹一样身高,和刘德华一样颜值的程序员。本篇文章通过我和三妹对话的形式来谈一谈“究竟什么是 JVM”。

    教妹学 Java,没见过这么有趣的标题吧?“语不惊人死不休”,没错,本篇文章的标题就是这么酷炫,接受不了的同学就别点进来看了,所谓好奇心害死猫;能够接受的同学我只能说你赚到了,你不仅能在阅读的过程中感受到思维的乐趣,还真的能学习到知识。下面就由我来介绍一下故事的背景吧。

    我有一个漂亮如花的妹妹(见上图),她叫什么呢?我想聪明的读者朋友们都能猜得出:沉默王三,没错,年方三六。父母正考虑让她向我学习,做一名正儿八经的 Java 程序员。我期初是想反抗的,因为程序员这行业容易掉头发。但家命难为啊,与其反抗,不如做点更积极的事情,写点有趣的文章,教妹妹如何更快地掌握 Java 这门编程语言。毕竟程序员还算得上高薪(都是拿命换的啊)。

    (铺垫结束,正文开始)

    “二哥,最近疫情闹得人心惶惶,无心学习了,怎么办?”

    “三妹啊,你要知道,历史上经历过无数次的动荡与不安,但最后,都挺过去了。有人破坏,有人修复。安安心心学习,疫情过后肯定能够派上大用场,到时候经济复苏,人才亟需,懂吗?”

    “二哥,说话就是不一样,一句话就安抚了我不安的心情。”

    “那还不开始今天的主题?”

    “二哥,上一篇文章中你给我解释了什么是 JDK,JRE 和 JVM,但我想知道 JVM 究竟是什么,它能干什么事。”

    “三妹啊,搬个凳子坐我旁边,听二哥来给你慢慢说啊。”

    01、什么是 JVM

    再来回顾一下。JVM(Java Virtual Machine)俗称 Java 虚拟机。之所以称为虚拟机,是因为它实际上并不存在。它提供了一种运行环境,可供 Java 字节码在上面运行。

    02、JVM 能做什么

    JVM 提供了以下操作:

    • 加载字节码
    • 验证字节码
    • 执行字节码
    • 提供运行时环境

    JVM 定义了以下内容:

    • 存储区
    • 类文件格式
    • 寄存器组
    • 垃圾回收堆
    • 致命错误报告等

    03、JVM 的内部结构

    我们来尝试理解一下 JVM 的内部结构,它包含了类加载器(Class Loader)、运行时数据区(Runtime Data Areas)和执行引擎(Excution Engine)。

    1)类加载器

    类加载器是 JVM 的一个子系统,用于加载类文件。每当我们运行一个 Java 程序,它都会由类加载器首先加载。Java 中有三个内置的类加载器:

    • 启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar(包含了 Java 标准库下的所有类文件,比如说 java.lang 包下的类,java.net 包下的类,java.util 包下的类,java.io 包下的类,java.sql 包下的类)。

    • 扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。

    • 应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。

    一般来说,Java 程序员并不需要直接同类加载器进行交互。JVM 默认的行为就已经足够满足大多数情况的需求了。不过,如果遇到了需要和类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就不得不花大量的时间去调试
    ClassNotFoundExceptionNoClassDefFoundError 等异常。

    对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

    三妹啊,是不是有点晕,来来来,通过一段简单的代码了解下。

    public class Test {
    
    	public static void main(String[] args) {
    		ClassLoader loader = Test.class.getClassLoader();
    		while (loader != null) {
    			System.out.println(loader.toString());
    			loader = loader.getParent();
    		}
    	}
    
    }
    

    每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 类名.class.getClassLoader() 可以获取到此引用;然后通过 loader.getParent() 可以获取类加载器的上层类加载器。

    上面这段代码的输出结果如下:

    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@4617c264
    

    第一行输出为 Test 的类加载器,即应用类加载器,它是 sun.misc.Launcher$AppClassLoader 类的实例;第二行输出为扩展类加载器,是 sun.misc.Launcher$ExtClassLoader 类的实例。那启动类加载器呢?

    按理说,扩展类加载器的上层类加载器是启动类加载器,但在我这个版本的 JDK 中, 扩展类加载器的 getParent() 返回 null。所以没有输出。

    2)运行时数据区

    运行时数据区又包含以下内容。

    • PC寄存器(PC Register),也叫程序计数器(Program Counter Register),是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器。

    • JVM 栈(Java Virtual Machine Stack),与 PC 寄存器一样,JVM 栈也是线程私有的。每一个 JVM 线程都有自己的 JVM 栈,这个栈与线程同时创建,它的生命周期与线程相同。

    • 本地方法栈(Native Method Stack),JVM 可能会使用到传统的栈来支持 Native 方法(使用 Java 语言以外的其它语言[C语言]编写的方法)的执行,这个栈就是本地方法栈。

    • 堆(Heap),在 JVM 中,堆是可供各条线程共享的运行时内存区域,也是供所有类实例和数据对象分配内存的区域。

    • 方法区(Method area),在 JVM 中,被加载类型的信息都保存在方法区中。包括类型信息(Type Information)和方法列表(Method Tables)。方法区是所有线程共享的,所以访问方法区信息的方法必须是线程安全的。

    • 运行时常量池(Runtime Constant Pool),运行时常量池是每一个类或接口的常量池在运行时的表现形式,它包括了编译器可知的数值字面量,以及运行期解析后才能获得的方法或字段的引用。简而言之,当一个方法或者变量被引用时,JVM 通过运行时常量区来查找方法或者变量在内存里的实际地址。

    3)执行引擎

    执行引擎包含了:

    • 解释器:读取字节码流,然后执行指令。因为它一条一条地解释和执行指令,所以它可以很快地解释字节码,但是执行起来会比较慢。

    • 即时(Just-In-Time,JIT)编译器:即时编译器用来弥补解释器的缺点,提高性能。执行引擎首先按照解释执行的方式来执行,然后在合适的时候,即时编译器把整段字节码编译成本地代码。然后,执行引擎就没有必要再去解释执行方法了,它可以直接通过本地代码去执行。执行本地代码比一条一条进行解释执行的速度快很多。编译后的代码可以执行的很快,因为本地代码是保存在缓存里的。

    04、鸣谢

    本篇文章为《教妹学Java》专栏的第七篇文章,是不是有趣得很?我相信你能感受的到,这可是全网独一份,我看到已经有人在模仿了。现在定价只需 9.9 元,9.9 元你连一杯奶茶都买不到,但却能买下二哥精心制作的专栏,据说 CSDN 已经考虑涨价了,毕竟已经卖出一百多份了。

    我知道,购买专栏的同学都是冲着二哥的名声来的,毕竟二哥是 CSDN 的明星博主,哈哈。为表谢意,我再附送上个人微信(qing_gee),你有什么问题都可以来咨询。

    上一篇回顾:教妹学Java(六):JDK,JRE和JVM之间有什么区别?

    PS:本篇文章中的示例代码已经同步到 GitHub,地址为 itwanger.JavaPoint,欢迎大家 star 和 issue。

    原创不易,喜欢就点个赞,因为你一个小小的举动,就会让这个世界多一份美好。

    展开全文
  • JVM原理详解

    2019-07-18 11:05:39
    1,通俗讲解JVM运行原理,各组成部分的作用,包括栈、堆等 2,使用visio视图,直观介绍JVM组成 3,介绍线程安全产生的原因 4,面试题中存在的些许JVM题目讲解
  • JVM架构和GC垃圾回收机制详解 JVM架构图分析 下图:参考网络+书籍,如有侵权请见谅 (想了解Hadoop内存溢出请看: Hadoop内存溢出(OOM)分类、参数调优化) JVM被分为三个主要的子系统 (1)类加载器子系统(2...

    JVM架构和GC垃圾回收机制详解

    JVM架构图分析

    下图:参考网络+书籍,如有侵权请见谅 (想了解Hadoop内存溢出请看: Hadoop内存溢出(OOM)分类、参数调优化

    JVM被分为三个主要的子系统

    (1)类加载器子系统(2)运行时数据区(3)执行引擎

    1. 类加载器子系统

    Java的动态类加载功能是由类加载器子系统处理。当它在运行时(不是编译时)首次引用一个类时,它加载、链接并初始化该类文件。

    1.1 加载

    类由此组件加载。启动类加载器 (BootStrap class Loader)、扩展类加载器(Extension class Loader)和应用程序类加载器(Application class Loader) 这三种类加载器帮助完成类的加载。

    1.  启动类加载器 – 负责从启动类路径中加载类,无非就是rt.jar。这个加载器会被赋予最高优先级。

    2.  扩展类加载器 – 负责加载ext 目录(jre\lib)内的类.

    3.  应用程序类加载器 – 负责加载应用程序级别类路径,涉及到路径的环境变量等etc.

    上述的类加载器会遵循委托层次算法(Delegation Hierarchy Algorithm)加载类文件

    1.2 链接

    1.  校验 – 字节码校验器会校验生成的字节码是否正确,如果校验失败,我们会得到校验错误

    2.  准备 – 分配内存并初始化默认值给所有的静态变量。

    3.  解析 – 所有符号内存引用方法区(Method Area)原始引用所替代。

    1.3 初始化

    这是类加载的最后阶段,这里所有的静态变量会被赋初始值, 并且静态块将被执行。

    2. 运行时数据区(Runtime Data Area)

    The 运行时数据区域被划分为5个主要组件:

    2.1 方法区(Method Area)

    所有类级别数据将被存储在这里,包括静态变量。每个JVM只有一个方法区,它是一个共享的资源。

    2.2 堆区(Heap Area)

    所有的对象和它们相应的实例变量以及数组将被存储在这里。每个JVM同样只有一个堆区。由于方法区堆区的内存由多个线程共享,所以存储的数据不是线程安全的

    2.3 栈区(Stack Area)

    对每个线程会单独创建一个运行时栈。对每个函数呼叫会在栈内存生成一个栈帧(Stack Frame)。所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源。栈帧被分为三个子实体:

    a 局部变量数组 – 包含多少个与方法相关的局部变量并且相应的值将被存储在这里。

    b 操作数栈 – 如果需要执行任何中间操作,操作数栈作为运行时工作区去执行指令。

    c 帧数据 – 方法的所有符号都保存在这里。在任意异常的情况下,catch块的信息将会被保存在帧数据里面。

    如上是JVM三大核心区域

    2.4 PC寄存器

    每个线程都有一个单独的PC寄存器来保存当前执行指令的地址,一旦该指令被执行,pc寄存器会被更新至下条指令的地址。

    2.5 本地方法栈

    本地方法栈保存本地方法信息。对每一个线程,将创建一个单独的本地方法栈。

    3. 执行引擎

    分配给运行时数据区的字节码将由执行引擎执行。执行引擎读取字节码并逐段执行。

    3.1  解释器:

     解释器能快速的解释字节码,但执行却很慢。 解释器的缺点就是,当一个方法被调用多次,每次都需要重新解释。

    编译器

    JIT编译器消除了解释器的缺点。执行引擎利用解释器转换字节码,但如果是重复的代码则使用JIT编译器将全部字节码编译成本机代码。本机代码将直接用于重复的方法调用,这提高了系统的性能。

    a. 中间代码生成器 – 生成中间代码

    b. 代码优化器 – 负责优化上面生成的中间代码

    c. 目标代码生成器 – 负责生成机器代码或本机代码

    d.  探测器(Profiler) – 一个特殊的组件,负责寻找被多次调用的方法。

    3.3  垃圾回收器:

    收集并删除未引用的对象。可以通过调用"System.gc()"来触发垃圾回收,但并不保证会确实进行垃圾回收。JVM的垃圾回收只收集哪些由new关键字创建的对象。所以,如果不是用new创建的对象,你可以使用finalize函数来执行清理。

    Java本地接口 (JNI)JNI 会与本地方法库进行交互并提供执行引擎所需的本地库。

    本地方法库:它是一个执行引擎所需的本地库的集合。

    通过一个小程序认识JVM

    package com.spark.jvm;
    /**
     * 从JVM调用的角度分析java程序堆内存空间的使用:
     * 当JVM进程启动的时候,会从类加载路径中找到包含main方法的入口类HelloJVM
     * 找到HelloJVM会直接读取该文件中的二进制数据,并且把该类的信息放到运行时的Method内存区域中。
     * 然后会定位到HelloJVM中的main方法的字节码中,并开始执行Main方法中的指令
     * 此时会创建Student实例对象,并且使用student来引用该对象(或者说给该对象命名),其内幕如下:
     * 第一步:JVM会直接到Method区域中去查找Student类的信息,此时发现没有Student类,就通过类加载器加载该Student类文件;
     * 第二步:在JVM的Method区域中加载并找到了Student类之后会在Heap区域中为Student实例对象分配内存,
     * 并且在Student的实例对象中持有指向方法区域中的Student类的引用(内存地址);
     * 第三步:JVM实例化完成后会在当前线程中为Stack中的reference建立实际的应用关系,此时会赋值给student
     * 接下来就是调用方法
     * 在JVM中方法的调用一定是属于线程的行为,也就是说方法调用本身会发生在线程的方法调用栈:
     * 线程的方法调用栈(Method Stack Frames),每一个方法的调用就是方法调用栈中的一个Frame,
     * 该Frame包含了方法的参数,局部变量,临时数据等 student.sayHello();
     */
    public class HelloJVM {
    	//在JVM运行的时候会通过反射的方式到Method区域找到入口方法main
    	public static void main(String[] args) {//main方法也是放在Method方法区域中的
    		/**
    		 * student(小写的)是放在主线程中的Stack区域中的
    		 * Student对象实例是放在所有线程共享的Heap区域中的
    		 */
    		Student student = new Student("spark");
    		/**
    		 * 首先会通过student指针(或句柄)(指针就直接指向堆中的对象,句柄表明有一个中间的,student指向句柄,句柄指向对象)
    		 * 找Student对象,当找到该对象后会通过对象内部指向方法区域中的指针来调用具体的方法去执行任务
    		 */
    		student.sayHello();
    	}
    }
    
    class Student {
    	// name本身作为成员是放在stack区域的但是name指向的String对象是放在Heap中
    	private String name;
    	public Student(String name) {
    		this.name = name;
    	}
    	//sayHello这个方法是放在方法区中的
    	public void sayHello() {
    	System.out.println("Hello, this is " + this.name);
    	}
    }

    JVM三大性能调优参数:-Xms –Xmx –Xss

    -Xms –Xmx是对堆的性能调优参数,一般两个设置是一样的,如果不一样,当Heap不够用,会发生内存抖动。一般都调大这两个参数,并且两个大小一样。

    -Xss是对每一个线程栈的性能调优参数,影响堆栈调用的深度

    实战演示从OOM推导出JVM GC时候基于的内存结构:Young Generation(Eden、From、To)、OldGeneration、Permanent Generation

    JVMHeap区域(年轻代、老年代)和方法区(永久代)结构图:

    从Java GC的角度解读代码:程序20行new的Person对象会首先会进入年轻代Eden中(如果对象太大可能直接进入年老代)。在GC之前对象是存在Eden和from中的,进行GC的时候Eden中的对象被拷贝到To这样一个survive空间(survive幸存)空间:包括from和to,他们的空间大小是一样的,又叫s1和s2)中(有一个拷贝算法),From中的对象(算法会考虑经过GC幸存的次数)到一定次数(阈值(如果说每次GC之后这个对象依旧在Survive中存在,GC一次他的Age就会加1,默认15就会放到OldGeneration。但是实际情况比较复杂,有可能没有到阈值就从Survive区域直接到Old Generation区域。在进行GC的时候会对Survive中的对象进行判断,Survive空间中有一些对象Age是一样的,也就是经过的GC次数一样,年龄相同的这样一批对象的总和大于等于Survive空间一半的话,这组对象就会进入old Generation中,(是一种动态的调整))),会被复制到OldGeneration,如果没到次数From中的对象会被复制到To中,复制完成后To中保存的是有效的对象,Eden和From中剩下的都是无效的对象,这个时候就把Eden和From中所有的对象清空。在复制的时候Eden中的对象进入To中,To可能已经满了,这个时候Eden中的对象就会被直接复制到Old Generation中,From中的对象也会直接进入Old Generation中。就是存在这样一种情况,To比较小,第一次复制的时候空间就满了,直接进入old Generation中。复制完成后,To和From的名字会对调一下,因为Eden和From都是空的,对调后Eden和To都是空的,下次分配就会分配到Eden。一直循环这个流程。好处:使用对象最多和效率最高的就是在Young Generation中,通过From to就避免过于频繁的产生FullGC(Old Generation满了一般都会产生FullGC)

    虚拟机在进行MinorGC(新生代的GC)的时候,会判断要进入OldGeneration区域对象的大小,是否大于Old Generation剩余空间大小,如果大于就会发生Full GC

    刚分配对象在Eden中,如果空间不足尝试进行GC,回收空间,如果进行了MinorGC空间依旧不够就放入Old Generation,如果OldGeneration空间还不够就OOM了。

    比较大的对象,数组等,大于某值(可配置)就直接分配到老年代,(避免频繁内存拷贝)

    年轻代和年老代属于Heap空间的

    Permanent Generation(永久代)可以理解成方法区,(它属于方法区)也有可能发生GC,例如类的实例对象全部被GC了,同时它的类加载器也被GC掉了,这个时候就会触发永久代中对象的GC。

    如果OldGeneration满了就会产生FullGC

    满原因:1,from survive中对象的生命周期到一定阈值

    2,分配的对象直接是大对象

    3、由于To 空间不够,进行GC直接把对象拷贝到年老代(年老代GC时候采用不同的算法)

    如果Young Generation大小分配不合理或空间比较小,这个时候导致对象很容易进入Old Generation中,而Old Generation中回收具体对象的时候速度是远远低于Young Generation回收速度。

    因此实际分配要考虑年老代和新生代的比例,考虑Eden和survives的比例

    Permanent Generation中发生GC的时候也对性能影响非常大,也是Full GC

    JVM GC时候核心参数:

    -XX:NewRatio –XX:SurvivorRatio –XX:NewSize –XX:MaxNewSize

    –XX:NewSize–XX:MaxNewSize指定新生代初始大小和最大大小。

    1,-XX:NewRatio    是年老代 新生代相对的比例,比如NewRatio=2,表明年老代是新生代的2倍。老年代占了heap的2/3,新生代占了1/3

    2,-XX:SurvivorRatio 配置的是在新生代里面Eden和一个Servive的比例

    如果指定NewRatio还可以指定NewSizeMaxNewSize,如果同时指定了会如何???

    NewRatio=2,这个时候新生代会尝试分配整个Heap大小的1/3的大小,但是分配的空间不会小于-XX:NewSize也不会大于 –XX:MaxNewSize

    3,-XX:NewSize –XX:MaxNewSize

    实际设置比例还是设置固定大小,固定大小理论上速度更高。

    -XX:NewSize –XX:MaxNewSize理论越大越好,但是整个Heap大小是有限的,一般年轻代的设置大小不要超过年老代。

    -XX:SurvivorRatio新生代里面Eden和一个Servive的比例,如果SurvivorRatio是5的话,也就是Eden区域是SurviveTo区域的5倍。Survive由From和To构成。结果就是整个Eden占用了新生代5/7,From和To分别占用了1/7,如果分配不合理,Eden太大,这样产生对象很顺利,但是进行GC有一部分对象幸存下来,拷贝到To,空间小,就没有足够的空间,对象会被放在old Generation中。如果Survive空间大,会有足够的空间容纳GC后存活的对象,但是Eden区域小,会被很快消耗完,这就增加了GC的次数。

    JVM的GC日志解读:

    一、 JVM YoungGeneration下MinorGC日志详解

    [GC (Allocation Failure) [PSYoungGen:2336K->288K(2560K)] 8274K->6418K(9728K), 0.0112926 secs] [Times:user=0.06 sys=0.00, real=0.01 secs]

    PSYoungGen(是新生代类型,新生代日志收集器),2336K表示使用新生代GC前,占用的内存,->288K表示GC后占用的内存,(2560K)代表整个新生代总共大小

    8274K(GC前整个JVM Heap对内存的占用)->6418K(MinorGC后内存占用总量)(9728K)(整个堆的大小)0.0112926 secs(Minor GC消耗的时间)] [Times: user=0.06 sys=0.00, real=0.01 secs] 用户空间,内核空间时间的消耗,real整个的消耗

    二、 JVM的GC日志Full GC日志每个字段彻底详解

    [Full GC (Ergonomics) [PSYoungGen: 984K->425K(2048K)] [ParOldGen:7129K->7129K(7168K)] 8114K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1022588 secs] [Times: user=0.56 sys=0.02,real=0.10 secs]

    [Full GC (Allocation Failure) [PSYoungGen: 425K->425K(2048K)][ParOldGen: 7129K->7129K(7168K)] 7555K->7555K(9216K), [Metaspace:2613K->2613K(1056768K)], 0.1003696 secs] [Times: user=0.64 sys=0.03,real=0.10 secs]

    [Full GC(表明是Full GC) (Ergonomics) [PSYoungGen:FullGC会导致新生代Minor GC产生]984K->425K(2048K)][ParOldGen:(老年代GC)7129K(GC前多大)->7129K(GC后,并没有降低内存占用,因为写的程序不断循环一直有引用)(7168K) (老年代总容量)] 8114K(GC前占用整个Heap空间大小)->7555K (GC后占用整个Heap空间大小) (9216K) (整个Heap大小,JVM堆的大小), [Metaspace: (java6 7是permanentspace,java8改成Metaspace,类相关的一些信息) 2613K->2613K(1056768K) (GC前后基本没变,空间很大)], 0.1022588 secs(GC的耗时,秒为单位)] [Times: user=0.56 sys=0.02, real=0.10 secs](用户空间耗时,内核空间耗时,真正的耗时时间)

    三、 Java8中的JVM的MetaSpace

    Metaspace的使用C语言实现的,使用的是OS的空间,Native Memory Space可动态的伸缩,可以根据类加载的信息的情况,在进行GC的时候进行调整自身的大小,来延缓下一次GC的到来。

    可以设置Metaspace的大小,如果超过最大大小就会OOM,不设置如果把整个操作系统的内存耗尽了出现OOM,一般会设置一个足够大的初始值,安全其间会设置最大值。

    永久代发生GC有两种情况,类的所有的实例被GC掉,且class load不存。

    对于元数据空间 简化了GC, class load不存在了就需要进行GC。

    三种基本的GC算法基石

    一、 标记/清除算法

    内存中的对象构成一棵树,当有效的内存被耗尽的时候,程序就会停止,做两件事,第一:标记,标记从树根可达的对象(途中水红色),第二:清除(清楚不可达的对象)。标记清除的时候有停止程序运行,如果不停止,此时如果存在新产生的对象,这个对象是树根可达的,但是没有被标记(标记已经完成了),会清除掉。

    缺点:递归效率低性能低;释放空间不连续容易导致内存碎片;会停止整个程序运行;

    二、 复制算法

    把内存分成两块区域:空闲区域和活动区域,第一还是标记(标记谁是可达的对象),标记之后把可达的对象复制到空闲区,将空闲区变成活动区,同时把以前活动区对象1,4清除掉,变成空闲区。

    速度快但耗费空间,假定活动区域全部是活动对象,这个时候进行交换的时候就相当于多占用了一倍空间,但是没啥用。

    三、 标记整理算法

    平衡点

    标记谁是活跃对象,整理,会把内存对象整理成一课树一个连续的空间,

    JVM垃圾回收分代收集算法

    综合了上述算法优略

    1, 分代GC在新生代的算法:采用了GC的复制算法,速度快,因为新生代一般是新对象,都是瞬态的用了可能很快被释放的对象。

    2, 分代GC在年老代的算法 标记/整理算法,GC后会执行压缩,整理到一个连续的空间,这样就维护着下一次分配对象的指针,下一次对象分配就可以采用碰撞指针技术,将新对象分配在第一个空闲的区域。

    JVM垃圾回收器串行、并行、并发垃圾回收器概述

    1, JVM中不同的垃圾回收器

    2, 串行,并行,并发垃圾回收器(和JVM历史有关系,刚开始串行)

    Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。

    JVM中Serial收集器、ParNew收集器、Parallel收集器解析

    Serial收集器 单线程方式(没有线程切换开销,如果受限物理机器单线程可采用)串行且采用stop the world在工作的时候程序会停止

    Serial和serial old

    ParNew收集器:多线程(多CPU和多Core的环境中高效),生产环境对低延时要求高的话,就采用ParNew和CMS组合来进行server端的垃圾回收

    Parallel 收集器:多线程,并行, 它可以控制JVM吞吐量的大小,吞吐量优先的收集器,一般设置1%,可设置程序暂停的时间,会通过把新生代空间变小,来完成回收,频繁的小规模垃圾回收,会影响程序吞吐量大小

    JVM中CMS收集器解密

    低延迟进行垃圾回收,在线服务和处理速度要求高的情况下很重要

    配置:XX:UseConcMarkSweepGC

    concurrence(并发) Mark(标记)Sweep(清理)

    低延时

    把垃圾回收分成四个阶段

    CMS-initial-mark初始标记阶段会stop the world,短暂的暂停程序根据跟对象标记的对象所连接的对象是否可达来标记出哪些可到达

    CMS-concurrent-mark并发标记,根据上一次标记的结果确定哪些不可到达,线程并发或交替之行,基本不会出现程序暂停。

    CMS-remark再次标记,会出现程序暂停,所有内存那一时刻静止,确保被全部标记,有可能第二阶段之前有可能被标记为垃圾的对象有可能被引用,在此标记确认。

    CMS-concurrent-sweep并发清理垃圾,把标记的垃圾清理掉了,没有压缩,有可能产生内存碎片,不连续的内存块,这时候就不能更好的使用内存,可以通过一个参数配置,根据内存的情况执行压缩。

    JVM中G1收集器

    可以像CMS收集器一样,GC操作与应用的现场一起并发执行

    紧凑的空闲内存区域且没有很长的GC停顿时间

    需要可预测的GC暂停耗时

    不想牺牲太多吞吐量性能

    启动后不需要请求更大的Java堆

    通过案例瞬间理解JVM中PSYoungGen、ParOldGen、MetaSpace

    Heap
     PSYoungGen      total 2560K, used 321K[0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
      eden space 2048K, 15% used[0x00000007bfd00000,0x00000007bfd50568,0x00000007bff00000)
      from space 512K, 0% used[0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
      to   space 512K, 0% used[0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
     ParOldGen       total 7168K, used 7097K[0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
     object space 7168K, 99%used [0x00000007bf600000,0x00000007bfcee7b8,0x00000007bfd00000)
     Metaspace       used 2647K, capacity 4486K, committed4864K, reserved 1056768K
     class space    used 289K, capacity 386K, committed 512K,reserved 1048576K

    PSYoungGen是eden + from

    使用MAT对Dump文件进行分析实战

    导出Dump文件

     

    MapReduce过程详解及其性能优化

    展开全文
  • JVM大汇总

    2016-07-10 11:10:39
    JVM是虚拟机,也是一种规范 , 他遵循着冯·诺依曼体系结构的设计原理。 冯·诺依曼体系结构中,指出计算机处理的数据和指令都是二进制数,采用存储程序方式不加区分的存储在同一个存储器里,并且顺序执行,指令...
    原文  http://blog.csdn.net/mshootingstar/article/details/44783227

    JVM是虚拟机,也是一种规范  他遵循着冯·诺依曼体系结构的设计原理。 冯·诺依曼体系结构中,指出计算机处理的数据和指令都是二进制数,采用存储程序方式不加区分的存储在同一个存储器里,并且顺序执行,指令由操作码和地址码 组成,操作码决定了操作类型和所操作的数的数字类型,地址码则指出地址码和操作数。从dos到window8,从unix到ubuntu和CentOS, 还有MAC OS等等,不同的操作系统指令集以及数据结构都有着差异,而JVM通过在操作系统上建立虚拟机,自己定义出来的一套统一的数据结构和操作指令,把同一套语 言翻译给各大主流的操作系统,实现了跨平台运行,可以说 JVM是java的核心,是java可以一次编译到处运行的本质所在 。

    我研究学习了JVM的组成和运行原理,JVM的统一数据格式规范、字节码文件结构,JVM关于内存的管理

    一、JVM的组成和运行原理 

         JVM的毕竟是个虚拟机  是一种规范,虽说符合冯诺依曼的计算机设计理念,但是他并不是实体计算机,所以他的组成也不是什么存储器,控制器  运算器  输入输出设备。在我看来,JVM放在运行在真实的操作系统中表现的更像应用或者说是进程  他的组成可以理解为JVM这个进程有哪些功能模块,而这些功能模块的运作可以看做是JVM的运行原理。JVM有多种实现,例如Oracle的JVM,HP的JVM和IBM的JVM等,而在本文中研究学习的则是使用最广泛的Oracle的HotSpot JVM

         1.JVM在JDK中的位置 

         JDK是java开发的必备工具箱,JDK其中有一部分是JRE,JRE是JAVA运行环境,JVM则是JRE最核心的部分。我从oracle.com截取了一张关于JDK Standard Edtion的组成图,

    JVM 相关知识整理和学习

         从最底层的位置可以看出来JVM有多重要,而实际项目中JAVA应用的性能优化,OOM等异常的处理最终都得从JVM这儿来解决。 HotSpot是Oracle关于JVM的商标,区别于IBM,HP等厂商开发的JVM。Java HotSpot Client VM和Java HotSpot Server VM是JDK关于JVM的两种不同的实现,前者可以减少启动时间和内存占用,而后者则提供更加优秀的程序运行速度(参考自:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/index.html  该文档有关于各个版本的JVM的介绍 )。在命令行,通过java -version可以查看关于当前机器JVM的信息,下面是我在Win8系统上执行命令的截图,

    JVM 相关知识整理和学习

         可以看出我装的是build 20.13-b02版本,HotSpot 类型Server模式的JVM。

         2.JVM的组成

         JVM由4大部分组成:ClassLoader,Runtime Data Area,Execution Engine,Native Interface。

         我从CSDN找了一张描述JVM大致结构的图:

    JVM 相关知识整理和学习

         2.1. ClassLoader 是负责加载class文件  class文件在文件开头有特定的文件标示  并且ClassLoader只负责class文件的加载  至于它是否可以运行,则由Execution Engine决定

         2.2.Native Interface 是负责调用本地接口的。他的作用是调用不同语言的接口给JAVA用  他会在Native Method Stack中记录对应的本地方法  然后调用该方法时就通过Execution Engine加载对应的本地lib。原本多于用一些专业领域  如JAVA驱动  地图制作引擎等  现在关于这种本地方法接口的调用已经被类似于Socket通信  WebService等方式取代。

         2.3.Execution Engine 是执行引擎  也叫Interpreter。Class文件被加载后  会把指令和数据信息放入内存中,Execution Engine则负责把这些命令解释给操作系统。

         2.4.Runtime Data Area 则是存放数据的, 分为五部分:Stack,Heap,Method Area,PC Register,Native Method Stack。几乎所有的关于java内存方面的问题  都是集中在这块。下图是javapapers.com上关于Run-time Data Areas的描述:

    JVM 相关知识整理和学习

         可以看出它把Method Area化为了Heap的一部分,javapapers.com中认为Method Area是Heap的逻辑区域,但这取决于JVM的实现者,而HotSpot JVM中把Method Area划分为非堆内存,显然是不包含在Heap中的。下图是javacodegeeks.com中,2014年9月刊出的一片博文中关于Runtime Data Area的划分,其中指出,NonHeap包含PermGen和Code Cache,PermGen包含Method Area,而且PermGen在JAVA SE 8中已经不再用了。查阅资料(https://abhirockzz.wordpress.com/2014/03/18/java-se-8-is-knocking-are-you-there/ )得知,java8中PermGen已经从JVM中移除并被MetaSpace取代,java8中也不会见到OOM:PermGen Space的异常。目前Runtime Data Area可以用下图描述它的组成:

    JVM 相关知识整理和学习

         2.4.1. Stack 是java栈内存,它等价于C语言中的栈, 栈的内存地址是不连续的 每个线程都拥有自己的栈。 栈 里面存储着的是StackFrame,在《JVM Specification》中文版中被译作java虚拟机框架,也叫做栈帧。StackFrame包含三类信息:局部变量,执行环境,操作数栈。局部变 量用来存储一个类的方法中所用到的局部变量。执行环境用于保存解析器对于java字节码进行解释过程中需要的信息,包括:上次调用的方法、局部变量指针和 操作数栈的栈顶和栈底指针。操作数栈用于存储运算所需要的操作数和结果。StackFrame在方法被调用时创建,在某个线程中,某个时间点上,只有一个 框架是活跃的,该框架被称为Current Frame,而框架中的方法被称为Current Method,其中定义的类为Current Class。局部变量和操作数栈上的操作总是引用当前框架。当Stack Frame中方法被执行完之后,或者调用别的StackFrame中的方法时,则当前栈变为另外一个StackFrame。Stack的大小是由两种类 型,固定和动态的,动态类型的栈可以按照线程的需要分配。 下面两张图是关于栈之间关系以及栈和非堆内存的关系基本描述(来自http://www.programering.com/a/MzM3QzNwATA.html ):

    JVM 相关知识整理和学习 JVM 相关知识整理和学习

         2.4.2. Heap 是用来存放对象信息的,和Stack不同,Stack代表着一种运行时的状态。换句话说,栈是运行时单位,解决程序该如何执行的问题,而堆是存储的单位, 解决数据存储的问题。Heap是伴随着JVM的启动而创建,负责存储所有对象实例和数组的。堆的存储空间和栈一样是不需要连续的,它分为Young Generation和Old Generation(也叫 Tenured  Generation )两大部分。Young Generation分为Eden和Survivor,Survivor又分为From Space和 ToSpace。

         和Heap经常一起提及的概念是PermanentSpace,它是用来加载类对象的专门的内存区,是非堆内存,和Heap一起组成JAVA内存, 它包含MethodArea区(在没有CodeCache的HotSpotJVM实现里,则MethodArea就相当于GenerationSpace) 。 在JVM初始化的时候,我们可以通过参数来分别指定,PermanentSpace的大小、堆的大小、以及Young Generation和Old Generation的比值、Eden区和From Space的比值,从而来细粒度的适应不同JAVA应用的内存需求。

         2.4.3. PC Register 是 程序计数寄存器,每个JAVA线程都有一个单独的PC Register,他是一个指针,由Execution Engine读取下一条指令。如果该线程正在执行java方法,则PC Register存储的是 正在被执行的指令的地址,如果是本地方法,PC Register的值没有定义。PC寄存器非常小,只占用一个字宽,可以持有一个returnAdress或者特定平台的一个指针。

         2.4.4. Method Area 在HotSpot JVM的实现中属于非堆区,非堆区包括两部分:Permanet Generation和Code Cache,而Method Area属于Permanert Generation的一部分。Permanent Generation用来存储类信息,比如说: class definitions,structures,methods, field, method (data and code) 和 constants。Code Cache用来存储Compiled Code,即编译好的本地代码,在HotSpot JVM中通过JIT(Just In Time) Compiler生成,JIT是即时编译器,他是为了提高指令的执行效率,把字节码文件编译成本地机器代码,如下图:

    JVM 相关知识整理和学习

         引用一个经典的案例来理解Stack,Heap和Method Area的划分,就是Sring a="xx";Stirng b="xx",问是否a==b? 首先==符号是用来判断两个对象的引用地址是否相同,而在上面的题目中,a和b按理来说申请的是Stack中不同的地址,但是他们指向Method Area中Runtime Constant Pool的同一个地址,按照网上的解释,在a赋值为“xx”时,会在Runtime Contant Pool中生成一个String Constant,当b也赋值为“xx”时,那么会在常量池中查看是否存在值为“xx”的常量,存在的话,则把b的指针也指向“xx”的地址,而不是新生 成一个 String Constant 。我查阅了网络上大家关于 String Constant 的存储的说说法,存在略微差别的是,它存储在哪里,有人说Heap中会分配出一个常量池,用来存储常量,所有线程共享它。而有人说常量池是Method Area的一部分,而Method Area属于非堆内存,那怎么能说常量池存在于堆中?

         我认为,其实两种理解都没错。Method Area的确从逻辑上讲可以是Heap的一部分,在某些JVM实现里从堆上开辟一块存储空间来记录常量是符合JVM常量池设计目的的,所以前一种说法没问 题。对于后一种说法,HotSpot JVM的实现中的确是把方法区划分为了非堆内存,意思就是它不在堆上。 我在HotSpot JVM做了个简单的实验,定义多个常量之后,程序抛出OOM:PermGen Space异常,印证了JVM实现中常量池是在Permanent Space中的说法。但是,我的JDK版本是1.6的。查阅资料,JDK1.7中InternedStrings已经不再存储在 PermanentSpace中,而是放到了Heap中;JDK8中PermanentSpace已经被完全移除,InternedStrings也被放 到了MetaSpace中(如果出现内存溢出,会报OOM:MetaSpace,这里有个关于两者性能对比的文章:http://blog.csdn.net/zhyhang/article/details/17246223 )。 所 以,仁者见仁,智者见智,一个馒头足以引发血案,就算是同一个商家的JVM,毕竟JDK版本在更新,或许正如StackOverFlow上大神们所说,对 于理解JVM Runtime Data Area这一部分的划分逻辑,还是去看对应版本的JDK源码比较靠谱,或者是参考不同的版本JVM Specification(http://docs.oracle.com/javase/specs/ )。

         2.4.5. Native Method Stack 是供本地方法(非java)使用的栈。每个线程持有一个Native Method Stack。

         3.JVM的运行原理简介

         Java 程序被javac工具编译为.class字节 码文件之后,我们执行java命令,该class文件便被JVM的Class Loader加载,可以看出JVM的启动是通过JAVA Path下的java.exe或者java进行的。JVM的初始化、运行到结束大概包括这么几步:

         调用操作系统API判断系统的CPU架构,根据对应CPU类型寻找位于JRE目录下的/lib/jvm.cfg文件,然后通过该配置文件找到对应的 jvm.dll文件(如果我们参数中有-server或者-client, 则加载对应参数所指定的jvm.dll,启动指定类型的JVM),初始化jvm.dll并且挂接到JNIENV结构的实例上,之后就可以通过JNIENV 实例装载并且处理class文件了。class文件是字节码文件,它按照JVM的规范,定义了变量,方法等的详细信息,JVM管理并且分配对应的内存来执 行程序,同时管理垃圾回收。直到程序结束,一种情况是JVM的所有非守护线程停止,一种情况是程序调用System.exit(),JVM的生命周期也结 束。

         关于JVM如何管理分配内存,我通过class文件和垃圾回收两部分进行了学习。

    二、JVM的内存管理和垃圾回收

         JVM中的内存管理主要是指JVM对于Heap的管理,这是因为Stack,PC Register和Native Method Stack都是和线程一样的生命周期,在线程结束时自然可以被再次使用。虽然说,Stack的管理不是重点,但是也不是完全不讲究的。

         1.栈的管理

         JVM允许栈的大小是固定的或者是动态变化的。在Oracle的关于参数设置的官方文档中有关于Stack的设置(http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html#wp1024112),是通过-Xss来设置其大小。关于Stack的默认大小对于不同机器有不同的大小,并且不同厂商或者版本号的jvm的实现其大小也不同,如下表是HotSpot的默认大小:

    Platform
    Default
    Windows IA32
    64 KB
    Linux IA32
    128 KB
    Windows x86_64
    128 KB
    Linux x86_64
    256 KB
    Windows IA64
    320 KB
    Linux IA64
    1024 KB (1 MB)
    Solaris Sparc
    512 KB

    我们一般通过减少常量,参数的个数来减少栈的增长,在程序设计时,我们把一些常量定义到一个对象中,然后来引用他们可以体现这一点。另外,少用递归调用也可以减少栈的占用。

    栈是不需要垃圾回收的,尽管说垃圾回收是java内存管理的一个很热的话题,栈中的对象如果用垃圾回收的观点来看,他永远是live状态,是可以reachable的,所以也不需要回收,他占有的空间随着Thread的结束而释放。(参考自: http://stackoverflow.com/questions/20030120/java-default-stack-size )

    关于栈一般会发生以下两种异常:

         1.当线程中的计算所需要的栈超过所允许大小时,会抛出StackOverflowError。

         2.当Java栈试图扩展时,没有足够的存储器来实现扩展,JVM会报OutOfMemoryError。

         我针对栈进行了实验,由于递归的调用可以致使栈的引用增加,导致溢出,所以设计代码如下:

    JVM 相关知识整理和学习

         我的机器是x86_64系统,所以Stack的默认大小是128KB,上述程序在运行时会报错:

    JVM 相关知识整理和学习

         而当我在eclipse中调整了-Xss参数到3M之后,该异常消失。

    JVM 相关知识整理和学习

         另外栈上有一点得注意的是,对于本地代码调用,可能会在栈中申请内存,比如C调用malloc(),而这种情况下,GC是管不着的,需要我们在程序中,手动管理栈内存,使用free()方法释放内存。

         2.堆的管理

         堆的管理要比栈管理复杂的多,我通过堆的各部分的作用、设置,以及各部分可能发生的异常,以及如何避免各部分异常进行了学习。

    JVM 相关知识整理和学习

        上图是 Heap和PermanentSapce的组合图 ,其中 Eden区里面存着是新生的对象,From Space和To Space中存放着是每次垃圾回收后存活下来的对象 ,所以每次垃圾回收后,Eden区会被清空。 存 活下来的对象先是放到From Space,当From Space满了之后移动到To Space。当To Space满了之后移动到Old Space。Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor复制过来的对象。而且,Survivor区总有一个是空 的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

         Old Space中则存放生命周期比较长的对象,而且有些比较大的新生对象也放在Old Space中。

         堆的大小通过-Xms和-Xmx来指定最小值和最大值,通过-Xmn来指定Young Generation的大小(一些老版本也用-XX:NewSize指定), 即上图中的Eden加FromSpace和ToSpace的总大小。然后通过-XX:NewRatio来指定Eden区的大小,在Xms和Xmx相等的情 况下,该参数不需要设置。通过-XX:SurvivorRatio来设置Eden和一个Survivor区的比值。(参考自博文:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html )

         堆异常分为两种,一种是Out of Memory(OOM)  一种是Memory Leak(ML)。Memory Leak最终将导致OOM。实际应用中表现为:从Console看,内存监控曲线一直在顶部,程序响应慢,从线程看,大部分的线程在进行GC,占用比较多 的CPU,最终程序异常终止,报OOM。OOM发生的时间不定,有短的一个小时,有长的10天一个月的。关于异常的处理,确定OOM/ML异常后,一定要 注意保护现场,可以dump heap,如果没有现场则开启GCFlag收集垃圾回收日志,然后进行分析,确定问题所在。如果问题不是ML的话,一般通过增加Heap,增加物理内存来 解决问题,是的话,就修改程序逻辑。

         3.垃圾回收

         JVM中会在以下情况触发回收:对象没有被引用  作用域发生未捕捉异常  程序正常执行完毕 程序执行了System.exit()  程序发生意外终止。

         JVM中标记垃圾使用的算法是一种根搜索算法。简单的说,就是从一个叫GC Roots的对象开始 向下搜索  如果一个对象不能达到GC Roots对象的时候  说明它可以被回收了。这种算法比一种叫做引用计数法的垃圾标记算法要好,因为它避免了当两个对象啊互相引用时无法被回收的现象

         JVM中对于被标记为垃圾的对象进行回收时又分为了一下3种算法:

         1.标记清除算法 ,该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收,这种算法在存活对象较多时比较高效,但会产生内存碎片。

         2.复制算法 ,该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效。

         3.标记整理算法 ,标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题。

         JVM中,不同的 内存区域作用和性质不一样,使用的垃圾回收算法也不一样,所以JVM中又定义了几种不同的垃圾回收器(图中连线代表两个回收器可以同时使用):

    JVM 相关知识整理和学习

         1.Serial GC 。从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停。这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中。

         2.ParNew GC 。是在SerialGC的基础上,增加了多线程机制。但是如果机器是单CPU的,这种收集器是比SerialGC效率低的。

         3.Parrallel Scavenge GC 。这种收集器又叫吞吐量优先收集器,而吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用 1分钟,那么吞吐量就是99%。Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置。

         4.ParallelOld 是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代 只能使用串行回收收集器。

         5.Serial Old 是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器。

         6.CMS 又称响应时间优先回收器,使用标记清除算法。他的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记、并发标记、重新标记、并发清除。

         7.GarbageFirst(G1) 。比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间。

         对于垃圾收集器的组合使用可以通过下表中的参数指定:

    JVM 相关知识整理和学习

         默认的GC种类可以通过jvm.cfg或者通过jmap dump出heap来查看,一般我们通过jstat -gcutil [pid] 1000可以查看每秒gc的大体情况,或者可以在启动参数中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log来记录GC日志。

         GC中有一种情况叫做Full GC,以下几种情况会触发Full GC:

         1.Tenured Space空间不足以创建打的对象或者数组,会执行FullGC,并且当FullGC之后空间如果还不够,那么会OOM:java heap space。

         2.Permanet Generation的大小不足,存放了太多的类信息,在非CMS情况下回触发FullGC。如果之后空间还不够,会OOM:PermGen space。

         3.CMS GC时出现promotion failed和concurrent mode failure时,也会触发FullGC。promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。

         4.判断MinorGC后,要晋升到TenuredSpace的对象大小大于TenuredSpace的大小,也会触发FullGC。

         可以看出,当FullGC频繁发生时,一定是内存出问题了。

    三、JVM的数据格式规范和Class文件

         1.数据类型规范

         依据冯诺依曼的计算机理论,计算机最后处理的都是二进制的数,而JVM是怎么把java文件最后转化成了各个平台都可以识别的二进制呢?JVM自己定义了 一个抽象的存储数据单位,叫做Word。一个字足够大以持有byte、char、short、int、float、reference或者 returnAdress的一个值,两个字则足够持有更大的类型long、double。它通常是主机平台一个指针的大小,如32位的平台上,字是32 位。

         同时JVM中定义了它所支持的基本数据类型,包括两部分:数值类型和returnAddress类型。数值类型分为整形和浮点型。

         整形:

    byte
    值是8位的有符号二进制补码整数
    short

    值是16位的有符号二进制补码整数

    int

    值是32位的有符号二进制补码整数

    long

    值是64位的有符号二进制补码整数

    char

    值是表示Unicode字符的16位无符号整数

         浮点:
    float

    值是32位IEEE754浮点数

    double
    值是64位IEEE754浮点数
         returnAddress类型的值是Java虚拟机指令的操作码的指针。

         对比java的基本数据类型,jvm的规范中没有boolean类型。这是因为jvm中堆boolean的操作是通过int类型来进行处理的,而boolean数组则是通过byte数组来进行处理。

         至于String,我们知道它存储在常量池中,但他不是基本数据类型,之所以可以存在常量池中,是因为这是JVM的一种规定。如果查看String源码,我们就会发现,String其实就是一个基于基本数据类型char的数组。如图:

    JVM 相关知识整理和学习 。

         2.字节码文件

         通过字节码文件的格式我们可以看出jvm是如何规范数据类型的。下面是ClassFile的结构:

    JVM 相关知识整理和学习

         关于各个字段的定义(参考自JVM Specification 和 博文:http://www.cnblogs.com/zhuYears/archive/2012/02/07/2340347.html ),

    magic:

    魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。

    minor_version、major_version:

    分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。

    constant_pool_count:

    常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。

    constant_pool[]:

    常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。

    access_flags:

    访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表:

    JVM 相关知识整理和学习

    this_class:

    类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。

    super_class:

    父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那 constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当 然,如果某个类super_class的值是0,那么它必定是java.lang.Object类,因为只有它是没有父类的。

    interfaces_count:

    接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。

    interfaces[]:

    接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量。

    fields_count:

    字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。

    fields[]:

    字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。

    methods_count:

    方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。

    methods[]:

    方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。

    attributes_count:

    属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。

    attributes[]:

    属性表,attributes表的每个项的值必须是attribute_info结构。

    四、一个java类的实例分析

         为了了解JVM的数据类型规范和内存分配的大体情况,我新建了MemeryTest.java:

    JVM 相关知识整理和学习

         编译为 MemeryTest.class后  通过WinHex查看该文件,对应字节码文件各个部分不同的定义,我了解了下面16进制数值的具体含义,尽管不清楚ClassLoader的具体实现逻辑,但是可以想象这样一个严谨格式的文件给JVM对于内存管理和执行程序提供了多大的帮助。

    JVM 相关知识整理和学习

    运行程序后,我在windows资源管理器中找到对应的进程ID.

    JVM 相关知识整理和学习

    并且在控制台通过jmap -heap 10016查看堆内存的使用情况:

    JVM 相关知识整理和学习

         输出结果中表示当前java进程启动的JVM是通过4个线程进行Parallel GC,堆的最小 FreeRatio是40%,堆的最大FreeRatio是70%,堆的大小是4090M,新对象占用1.5M,Young Generation可以扩展到最大是1363M, Tenured Generation的大小是254.5M,以及NewRadio和SurvivorRadio中,下面更是具体给出了目前Young Generation中1.5M的划分情况,Eden占用1.0M,使用了5.4%,Space占了0.5M,使用了93%,To Space占了0.5M,使用了0%。

         下面我们通过jmap dump把heap的内容打印打文件中:

    JVM 相关知识整理和学习

         使用Eclipse的MAT插件打开对应的文件:

    JVM 相关知识整理和学习

         选择第一项内存泄露分析报告打开test.bin文件,展示出来的是MAT关于内存可能泄露的分析。

    JVM 相关知识整理和学习

    JVM 相关知识整理和学习

         从结果来看,有3个地方可能存在内存泄露,他们占据了Heap的22.10%,13.78%,14.69%,如果内存泄露,这里一般会有一个比值非常高的对象。打开第一个Probem Suspect,结果如下:

    JVM 相关知识整理和学习

         ShallowHeap是对象本身占用的堆大小,不包含引用,RetainedHeap是对象所持有的Shallowheap的大小,包括自己 ShallowHeap和可以引用的对象的ShallowHeap。垃圾回收的时候,如果一个对象不再引用后被回收,那么他的RetainedHeap是 能回收的内存总和。通过上图可以看出程序中并没有什么内存泄露,可以放心了。如果还有什么不太确定的对象,则可以通过多个时间点的 HeapDumpFile来研究某个对象的变化情况。

    五、小结

         以上便是我最近几天对JVM相关资料的整理,主要围绕他的基本组成和运行原理等,内存管理,节本数据类型和字节码文件。JVM是一个非常优秀的JAVA程序,也是个不错的规范,这次整理学习让我对他有了更加清晰的认知,对Java语言的理解也更加加深。 

         这次学习过程,坚定了我对程序员发展的认知。知识一定要精,下一步我将边工作边仔细阅读Oracle的3个版本的《JVM Specification》,并且结合实践让自己的Java基础素养更上一个层次。(http://docs.oracle.com/javase/specs/ )

    jvm2

    该文档主要介绍JVM内存管理模型,垃圾回收算法,常用GC参数,JVM调优四部分内容。

    一,内存管理模型

    JVM将其使用的内存划分为如下几个区域:

      • 本地方法站(native方法栈)
      • 虚拟机栈(方法栈)
      • 程序计数器(当前线程位置)
      • Perm(运行时常量池,静态方法,class对象等)
    1. 堆 
      • Young
        • Eden
        • S0
        • S1:S1与S0相同大小
      • Old

    大而言之其实就是两个部分:栈,堆。栈上空间一般都隶属各个线程(方法区,常量池线程共享),意思是每个线程有自己的栈空间,其上主要是一些原始类型变量。堆区负责存放对象,因为这些对象90%都是短命对象,所以按照对象的生命长度将堆区划分为短命堆区(年轻代),和长命堆区(年老代)。

    二,垃圾回收算法

     有了上面按照不同的生命周期来划分的区域,所以JVM也有了按照生命周期来回收垃圾对象。

    垃圾对象:没有被根引用的对象

    Young代回收算法:

    因为Young代都是短命对象,一次YoungGC会回收大部分对象,只有少量会剩下来。ParNew采用多线程,Stop The World的方式,采用复制算法进行回收。

    特点是:

    1. Stop The Workd
    2. 多线程并发回收
    3.  复制算法,内存回收后方便分配

    Old代采用CMS回收算法:

     回收步骤:

    1. 初始化清除:Stop The World 标记由根可以直达的对象
    2. 并发标记:并发标记可达对象
    3. 重新标记:Stop The World并发查找前一阶段新生代晋升或者新分配,被更新的对象
    4. 并发清理
    5. 线程重置

    优点:

    • 低延迟

    缺点:

    • 因为是基于标记清除,容易产生碎片 

    三,常用GC参数

    JVM parameters in Java

    我们根据JVM参数以-X开头或-XX开头将JVM参数分成两个部分:

    1) 以-X开头的都是非标准的(这些参数并不能保证在所有的JVM上都被实现),而且如果在新版本有什么改动也不会发布通知。

    2)以-XX开头的都是不稳定的并且不推荐在生产环境中使用。这些参数的改动也不会发布通知。

     


    对响应时间要求很高的系统来说,良好掌握JVM关于GC调优的参数是很重要的。比如一个高流量低延迟的电子交易平台,他要求的响应时间都是毫秒级的。要获得适合的参数组合需要大量的分析和不断的尝试,更依赖于交易系统的特性。

    关于JVM选项的几点:

    1) 布尔型参数选项:-XX:+ 打开, -XX:- 关闭。(比如-XX:+PrintGCDetails)

    2) 数字型参数选项通过-XX:<名称>=<设定>。数字可以是 m/M(兆字节),k/K(千字节),g/G(G字节)。比如:32K表示32768字节。(比如-XX:-XX:NewRatio=4)

    3) 字符行参数选项通过-XX:<名称>=<设定>,通常用来指定一个文件,路径,或者一个命令列表。(比如-XX:HeapDumpPath=./java_pid.hprof)

    命令 java -help可以列出java 应用启动时标准选项(不同的JVM实现是不同的)。java -X可以列出不标准的参数(这是JVM的扩展特性)。-X相关的选项不是标准的,被改变也不会通知。如果你想查看当前应用使用的JVM参数,你可以使用:ManagementFactory.getRuntimeMXBean().getInputArguments()。

    下面就是经常用到的JVM参数列表。


    1) 跟 Java 堆大小相关的 JVM 内存参数

    下面三个 JVM 参数用来指定堆的初始大小和最大值以及堆栈大小
     -Xms        设置 Java 堆的初始化大小
     -Xmx       设置最大的 Java 堆大小
     -Xss        设置Java线程堆栈大小
     -Xmn       设置新生代空间大小
    -------------------------------------------------

    2) 关于打印垃圾收集器详情的 JVM 参数

     -verbose:gc 记录 GC 运行以及运行时间,一般用来查看 GC 是否是应用的瓶颈

     -XX:+PrintGCDetails 记录 GC 运行时的详细数据信息,包括新生成对象的占用内存大小以及耗费时间等

     -XX:-PrintGCTimeStamps  打印垃圾收集的时间戳

    -------------------------------------------------

    3) 设置 Java 垃圾收集器行为的 JVM 参数

    -XX:+UseParallelGC      使用并行垃圾收集

    -XX:-UseConcMarkSweepGC 使用并发标志扫描收集 (Introduced in 1.4.1)

    -XX:-UseSerialGC        使用串行垃圾收集 (Introduced in 5.0.)

    需要提醒的是,但你的应用是非常关键的、交易非常频繁应用时,应该谨慎使用 GC 参数,因为 GC 操作是耗时的,你需要在这之中找到平衡点。 
    -------------------------------------------------

    4)JVM调试参数,用于远程调试

    -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

    -------------------------------------------------

    5) 关于性能诊断的 JVM 参数

    -Xprof

    -Xrunhprof
    -------------------------------------------------

    6) 关于类路径方面的 JVM 参数

    -Xbootclasspath用来指定你需要加载,但不想通过校验的类路径。JVM 会对所有的类在加载前进行校验并为每个类通过一个int数值来应用。这个是保证 JVM 稳定的必要过程,但比较耗时,如果你希望跳过这个过程,就把你的类通过这个参数来指定。
    -------------------------------------------------

    7) 用于修改 Perm Gen 大小的 JVM 参数

    下面的这三个参数主要用来解决 JVM 错误: java.lang.OutOfMemoryError:Perm Gen Space.
    -XX:PermSize and -XX:MaxPermSize
    -XX:NewRatio=2  Ratio of new/old generation sizes.
    -XX:MaxPermSize=64m     Size of the Permanent Generation.
    -------------------------------------------------

    8) 用来跟踪类加载和卸载的信息

    -XX:+TraceClassLoading和 -XX:+TraceClassUnloading 用来打印类被加载和卸载的过程信息,这个用来诊断应用的内存泄漏问题非常有用。

    -------------------------------------------------
    9) JVM switches related to logging

    -XX:+TraceClassLoading and -XX:+TraceClassUnloading print information class loads and unloads. Useful for investigating if you have a class leak or if old classes (like JITed Ruby methods in JRuby) are getting collected or not.You can read more about logging in Java on my post 10 Tips while logging in Java

    -XX:+PrintCompilation prints out the name of each Java method Hotspot decides to JIT compile. The list will usually show a bunch of core Java class methods initially, and then turn to methods in your application. In JRuby, it eventually starts to show Ruby methods as well
    -------------------------------------------------

    10)用于调试目的的 JVM 开关参数

    -XX:HeapDumpPath=./java_pid.hprof  Path to directory or file name for heap dump.

    -XX:-PrintConcurrentLocks       Print java.util.concurrent locks in Ctrl-Break thread dump.

    -XX:-PrintCommandLineFlags   Print flags that appeared on the command line. 

     下面贴一份我们项目中用到的JVM参数(4core,8G):

    复制代码
     1 /usr/local/java/bin/java -server -Dapp.key=api -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp
     2 
     3 -Djava.net.preferIPv6Addresses=false
     4 
     5 -Xmx4g -Xms4g -Xmn2g -XX:PermSize=128m
     6 
     7 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled
     8 
     9 -XX:+ExplicitGCInvokesConcurrent -XX:CMSInitiatingOccupancyFraction=70
    10 
    11 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSClassUnloadingEnabled
    12 
    13 -XX:+TieredCompilation -XX:CICompilerCount=4
    14 
    15 -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:/opt/logs/mobile/groupapi/groupapi.gc.log -XX:ErrorFile=/opt/logs/mobile/groupapi/groupapi.vmerr.log
    16 
    17 -XX:HeapDumpPath=/opt/logs/mobile/api/api.heaperr.log -Djetty.home= -Djetty.appkey=api
    18 
    19 -Djetty.context=/ -Djetty.logs=/opt/logs/mobile/groupapi -Djetty.webroot=/opt/meituan/mobile/api/webroot com.sankuai.mms.boot.Bootstrap 
    复制代码

    3.内存分析工具:MAT

    简介: 
    Eclipse提供的一个内存分析工具。它是一个功能丰富的 JAVA 堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗。
    官网地址:http://www.eclipse.org/mat 

    安装 (如果你使用的是MOTODEV Studio for Android则默认自带了该工具)
    你可以选择安装Eclipse提供的原版插件或使用MOTODEV Studio for Android插件

    >>1.原版插件 
    1).启动Eclipse,一次点击Help -> Intall New Software...;
    2).点击输入栏右侧小箭头,选择"Galileo"(我的是3.5版,不同版本要找到和自己版本名称对应的);
    3).等待列表更新完毕,找到并展开"General Purpose Tools";
    4).选中并下载其中的"Memory Analyser (Incubation)"和"Memory Analyser (Charts)"两项。

    >>2.MOTODEV Studio for Android插件 
    1).启动Eclipse,一次点击Help -> Intall New Software...;
    2).通过Add新建一个更新(最新地址可在此 获得):
        Name:MOTODEV Studio for Android Plugin
        Location:https://studio-android.motodevupdate.com/android/2.0
    3).下载并安装此更新。
    * 如果主要针对Android开发,建议使用MOTODEV Studio for Android的整合插件,应为它还提供了很多便利的工具,不过如果已经安装了ADT的需要先手动卸载才能安装,具体请参考:作为插件安装 MOTODEV Studio。

    使用方式1 —— 带参数运行 
    具体可参考:Eclipse Memory Analyser (MAT) - Tutorial
    归纳的大致步骤:
    (1) 在Run Conigurations..中的Arguments设置VM argument为:
    -XX:+HeapDumpOnOutOfMemoryError
    (2) 运行你的项目并生成.hprof文件,如果没看到请按F5刷新项目目录,就在根目录下;
    (3) 双击.hprof文件打开,选择"Leak Suspects Report"

    使用方式2 —— 结合Sequoyah监控特定的设备及应用
    <该方式需要你安装了Sequoyah 或MOTODEV Studio for Android插件>

    具体可参考:使用MAT分析应用内存

    --------------------------------------------------------
    *注意: 
    解析过大的HeapDump可能会导致Eclipse抛出OutOfMemory的错误:

     
    这时你需要手动调整Eclipse的缓存大小,可参考官网给出的处理方式(原文链接 ):

     
    >>我的使用情况: 
    我的机器只能设到512m,而且始终没法解决OutOfMemory的问题,看来对内存有一定要求呐...

    但是我下载了RPC版的MAT却是能正常打开的,Eclipse插件版打不开的朋友可以试试该方式:RPC版MAT下载地址
    --------------------------------------------------------

    打开后的界面如下: 


    更详细的使用示例请参考以下文章: 
    http://wiki.eclipse.org/index.php/MemoryAnalyzer
    —— 官方的帮助文档

    使用 Eclipse Memory Analyzer 进行堆转储文件分析
    —— 来自IBM的使用教程

    Eclipse Memory Analyser (MAT) - Tutorial
    —— 一个浅显易懂的MAT使用教程

    使用Memory Analyzer tool(MAT)分析内存泄漏(一)
    使用Memory Analyzer tool(MAT)分析内存泄漏(二)
    —— 详细地分析了内存泄露的原因,并举例说明,强烈推荐



    展开全文
  • 细说JVM系列:JVM介绍

    2016-08-25 20:01:59
    JVM是我们Javaer的最基本功底了,刚开始学Java的时候,一般都是从“Hello World”开始的,然后会写个复杂点class,然后再找一些开源框架,比如Spring,Hibernate等等,再然后就开发企业级的应用,比如网站、企业内部...

      JVM是我们Javaer的最基本功底了,刚开始学Java的时候,一般都是从“Hello World”开始的,然后会写个复杂点class,然后再找一些开源框架,比如Spring,Hibernate等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError,明天是网站系统报了个OutOfMemoryError,这种错误又很难重现,只有分析Javacore和dump文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了。我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢?——JVM。

    一.JVM介绍

      所谓虚拟机,就是一台虚拟的计算机,他是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。大名鼎鼎的visual box、vmware就属于系统虚拟机,他们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。程序虚拟机的代表就是java虚拟机,他专门为执行单个计算机程序而设计,在java虚拟机中执行的指令我们称为java字节码指令。

      JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。

      JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO,NO,JVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

      我们先把JVM这个虚拟机画出来,如下图所示:

    从这个图可以看出,JVM是运行在操作系统之上的,它与硬件没有直接的交互。

    二.JVM组成部分

      我们再来看下JVM有哪些组成部分,如下图所示:

    该图参考了网上广为流传的JVM构成图,大家看这个图,整个JVM分为四部分:

    1.ClassLoader类加载器

      类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:

    ClassFile {
          u4 magic;
          u2 minor_version;
          u2 major_version;
          u2 constant_pool_count;
          cp_info constant_pool[constant_pool_count-1];
          u2 access_flags;
          u2 this_class;
          u2 super_class;
          u2 interfaces_count;
          u2 interfaces[interfaces_count];
          u2 fields_count;
          field_info fields[fields_count];
          u2 methods_count;
          method_info methods[methods_count];
          u2 attributes_count;
          attribute_info attributes[attributes_count];
        }

      需要详细了解的话,可以仔细阅读《JVM Specification》的第四章“The class File Format”,这里不再详细说明。
    友情提示:Class Loader只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine负责的。

    2.Execution Engine 执行引擎

      执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。执行引擎是java虚拟机的最核心组件之一,它负责执行虚拟机的字节码。现代虚拟机为了提高执行效率,会使用即时编译技术将方法编译成机器码后再执行。

    3.Native Interface本地接口

      本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。

    4.Runtime data area运行数据区

      运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。

      整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!

    三.JAVA虚拟机规范

      虽然java语言和java虚拟机有着密切的联系,但两者是完全不同的内容。java虚拟机是一台执行java字节码的虚拟计算机,它拥有独立的运行机制,其运行的java字节码也未必由java语言编译而成,像Groovy、Scala等语言生成的java字节码也可以由java虚拟机执行。立足于java虚拟机,可以产生各种各样的跨平台语言。除了语言特性各不相同外,他们可以共享java虚拟机带来的跨平台性、优秀的垃圾回收器,以及可靠的即时编译器。

      因此,与java语言不通,java虚拟机是一个高效的、性能优异的、商用级别的软件运行和开发平台。

    java虚拟机规范的主要内容大概有以下几个部分:

    • 定义了虚拟机的内部结构
    • 定义了虚拟机执行的字节码类型和功能
    • 定义了Class文件的结构
    • 定义了类的装载、连接和初始化

    四.JVM执行程序的过程

    1) 加载.class文件
    2) 管理并分配内存
    3) 执行垃圾收集
    JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机。操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境:
    1) 创建JVM装载环境和配置
    2) 装载JVM.dll
    3) 初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
    4) 调用JNIEnv实例装载并处理class类。

    五.JVM的生命周期

    1) JVM实例对应了一个独立运行的java程序它是进程级别
    a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void
    main(String[] args)函数的class都可以作为JVM实例运行的起点
    b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程
    c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出

    2) JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的

    六.JRE/JDK/JVM是什么关系?

    JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
    JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
    JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

    七.细说JVM系列博客预告

    第二部分介绍了JVM的组成部分,以下内容也会围绕部分二的图示来解说JVM的各个组成部分。

    八.参考资料:

    jvm介绍(很好的一篇文章)
    http://wsqwsq000.iteye.com/blog/1120670

    JVM介绍
    http://www.cnblogs.com/549294286/p/3915691.html

    展开全文
  • JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 JVM屏蔽了与具体操作系统平台相关的信息,使...
  • JVM-五大区

    2018-05-25 10:23:34
    谈一谈Java内存区域的划分实际上是指JVM内存区域的划分,首先Java先介绍一下Java程序具体执行的过程 如上图所示,首先Java源代码们(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器...
  • 什么是JVM

    2018-04-20 16:05:05
    说明:做java开发的几乎都知道jvm这个名词,但是由于jvm对实际的简单开发的来说关联的还是不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是jvm,以及弄...
  • JVM是什么

    2019-03-03 14:24:25
    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 Java语言的一个非常重要的特点就是与平台的...
  • JVM

    2020-07-21 21:21:58
    连接[Linking]:将已读入内存的二进制数据合并到JVM运行的环境中 初始化[initialization]:JVM会按照初始化语句在类文件中的先后顺序执行 2. 加载 在VM [ JVM ] option中添加 java命令执行可以实现追踪类...
  • 1. Java 内存区域与内存溢出异常 1.1 运行时数据区域 根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。 ...字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行...
  • 深入详细讲解JVM原理

    2018-06-04 22:55:47
    一、JVM体系结构: 类装载器ClassLoader:用来装载.class文件 执行引擎:执行字节码,或者执行本地方法 运行时数据区:方法区、堆、Java栈、程序计数器、本地方法栈JVM把描述类数据的字节码.Class文件加载到内存...
  • JVM工作原理

    2019-07-04 09:23:02
    >>数据类型  Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。  基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。 ... “引用值”代表了某个对象的引用,而...
  • JVM内存模型总结

    2018-05-21 22:59:36
    JVM内存模型: 从这张图中很直观的看到,程序计数器,虚拟机栈,native栈是线程私有的,堆是线程共有的,现在详细介绍JVM各个区块。 1. 堆(Heap) 是java虚拟机所管理的内存中最大的一块内存区域,也是被各个...
  • 深入浅出JVM调优 基本概念: JVM把内存区分为堆区(heap)、栈区(stack)和方法区(method)。由于本文主要讲解JVM调优,因此我们可以简单的理解为,JVM中的堆区中存放的是实际的对象,是需要被GC的。其他的都无需GC。 ...
  • 总结了JVM一些经典面试题,分享出我自己的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新。 1.什么情况下会发生栈内存溢出。 思路: 描述栈定义,再描述为什么会溢出,再说明一下...
  • JVM之内存结构详解

    2019-10-20 12:15:34
    对于开发人员来说,如果不了解Java的JVM,那真的是很难写得一手好代码,很难查得一手好bug。同时,JVM也是面试环节的中重灾区。今天开始,《JVM详解》系列开启,带大家深入了解JVM相关知识。 我们不能为了面试而面试...
  • Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识进行详细介绍。 一、Java JVM内存介绍 JVM管理两种类型的内存,堆和非堆。按照官方的说法:“Java 虚拟机具有一个堆,...
  • 什么是jvm?

    2016-04-18 09:24:24
    说明:做java开发的几乎都知道jvm这个名词,但是由于jvm对实际的简单开发的来说关联的还是不多,一般工作个一两年(当然不包括爱学习的及专门做性能优化的什么的),很少有人能很好的去学习及理解什么是jvm,以及弄...
  • 一、JDK1.8 JVM内存模型概览 这里介绍的是JDK1.8 JVM内存模型。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别...
1 2 3 4 5 ... 20
收藏数 703,691
精华内容 281,476
关键字:

jvm