本章节属于Java进阶系列,前面关于设计模式讲解完了,有兴趣的童鞋能够翻看以前的博文,后面会讲解JVM的优化,整个系列会完整的讲解整个java体系与生态相关的中间件知识。本次将对jvm有更深刻的学习,咱们不只要让程序能跑起来,并且是能够跑的更快!能够分析解决在生产环境中所遇到的各类“棘手”的问题,好比运行的应用卡住了,日志不输出,程序没有反应,CPU负载忽然升高,多线程应用下,如何分配线程数量等。java
做为java工程师,对于jvm确定不陌生。JVM是Java Virtual Machine的缩写,通俗来讲也就是运行java代码的容器。当项目启动时,会根据jvm相关配置参数,在计算机的内存中开启一片空间用于运行JVM。以后java相关代码就会被加载进JVM中运行。c++
百度百科对JVM的定义:程序员
对于Java程序员来讲,在虚拟机自动内存管理机制的帮助下,再也不须要为每个new操做去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是由于Java程序员把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,若是不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工做。设计模式
由上面的图能够看出,JVM虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎。
类加载子系统
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终造成能够被虚拟机直接使用的Java类型。
运行时数据区
Java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。这些区域有各自的用途,以及建立和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而创建和销毁。
执行引擎
执行引擎用于执行JVM字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行以前先进行编译再执行。解释执行启动快,执行效率低。编译执行,启动慢,执行效率高。垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。
本地方法库、本地库接口
在jdk的底层中,有一些实现是须要调用本地方法完成的(使用c或c++写的方法),就是经过本地库接口调用完成的。好比:System.currentTimeMillis()方法。缓存
运行时数据区是jvm中最为重要的部门。也是咱们在调优时须要重点关注的区域,下面咱们一块儿了解下这个部分的具体内容。服务器
根据《Java虚拟机规范》中的规定,在运行时数据区将内存分为方法区(Method Area)、Java堆区(Java
Heap)、Java虚拟机栈(Java Virtual Machine Stack)、程序计数器(Program Counter Register)、本地方法
栈(Native Method Stacks)。多线程
程序计数器(Program Counter Register)是一块较小的内存空间,它能够看做是当前线程所执行的字节码的行号指示器。字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。架构
因为Java虚拟机的多线程是经过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(对于多核处理器来讲是一个内核)都只会执行一条线程中的指令。所以,为了线程切换后能恢复到正确的执行位置,每条线程都须要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,咱们称这类内存区域为“线程私有”的内存。app
与程序计数器同样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。Java虚拟机栈描述的是Java方法执行的线程内存模型:每一个方法被执行的时候,Java虚拟机都会同步建立一个栈帧,用于存储局部变量表、操做数栈、动态链接、方法出口等信息。每个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。jvm
以 int i = 1; 这样代码为例,看看虚拟机栈的执行
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的做用是很是类似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
Java堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。此内存区域的惟一目的就是存放对象实例,Java世界里“几乎”全部的对象实例都在这里分配内存。
须要注意的是,《Java虚拟机规范》并无对堆进行细致的划分,因此对于堆的讲解要基于具体的虚拟机,咱们以使用最多的HotSpot虚拟机为例进行讲解。
Java堆是垃圾收集器管理的内存区域,所以它也被称做“GC堆”,这就是咱们作JVM调优的重点区域部分。
由上图能够看出,jdk1.8的内存模型是由2部分组成,年轻代+ 年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。
须要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永
久代最大的区别所在。
空间分配
若是没有指定堆内存大小,默认初始堆内存为物理内存的1/64,最大不超过物理内存的1/4或1G。注意的是元空间会自动扩容,默认状况下不收限制。
官方给出的解释是:移除永久代是为融合HotSpot JVM与 JRockit VM而作出的努力,由于JRockit没有永久代,不须要配置永久代。
Java程序会经过栈上的reference数据来操做堆上的具体对象。
主流的访问方式主要有使用句柄和直接指针两种:
句柄访问
Java堆中将可能会划分出一块内存来做为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息.使用直接指针访问Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,若是只是访问对象自己的话,就不须要多一次间接访问的开销
指针访问
使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是很是广泛的行为)时只会改变句柄中的实例数据指针,而reference自己不须要被修改。使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销。HotSpot虚拟机采用的是指针访问方式实现。