Java虚拟机(JVM)是Java应用的运行环境,从通常意义上来说,JVM是经过规范来定义的一个虚拟的计算机,被设计用来解释执行从Java源码编译而来的字节码。html
JVM主要有子系统和内存区:bootstrap
Class Loader
类加载器。 用于读入Java源代码并将类加载到数据区。缓存
Execution Engine
执行引擎。 执行来自数据区的指令。性能优化
运行时数据区使用的是底层操做系统分配给JVM的内存。(JVM的内存模型)服务器
JVM在下面几种不一样的层面使用不一样的类加载器:多线程
bootstrap class loader(引导类加载器):是其余类加载器的父类,它用于加载Java核心库,而且是惟一一个用本地代码编写的类加载器。并发
extension class loader(扩展类加载器):是bootstrap class loader加载器的子类,用于加载扩展库。jvm
system class loader(系统类加载器):是extension class loader加载器的子类,用于加载在classpath中的应用程序的类文件。函数
user-defined class loader(用户定义的类加载器):是系统类加载器或其余用户定义的类加载器的子类。性能
当一个类加载器收到一个加载类的请求,首先它会检查缓存,确认该类是否已经被加载,而后把请求代理给它的父类。若是父类没能成功的加载类,那么子类就会本身去尝试加载该类。子类可检查父类加载器的缓存,但父类不能看到子类所加载的类。之所类加载体系会这样设计,是认为一个子类不该该重复加载已经被父类加载过的类。
执行引擎一个接一个地执行被加载到数据区的字节码。为了保证字节码指令对于机器来讲是可读的,执行引擎使用下面两个方法:
解释执行:执行引擎把它遇到的每一条指令解释为机器语言。
即时编译:若是一条指令常常被使用,执行引擎会把它编译为本地代码并存储在缓存中。这样,全部和这个方法相关的代码都会直接执行,从而避免重复解释。
尽管即时编译比解释执行要占用更多的时间,可是对于须要使用成千上万次的方法,只须要处理一次。相比每次都解释执行,以本地代码的方式运行会节约不少执行时间。
JVM规范中并不规定必定要使用即时编译。即时编译也不是用于提升JVM性能的惟一的手段。规范仅仅规定了每条字节码对应的本地代码,至于执行引擎如何实现这一对应过程的,彻底由JVM的具体实现来决定。
Java内存模型创建在自动内存管理的概念之上。当一个对象再也不被一个应用所引用,垃圾回收器就会回收它,从而释放相应的内存。这一点和其余不少须要自行释放内存的语言有很大不一样。
JVM从底层操做系统中分配内存,并将它们分为如下几个区域:
堆空间(Heap Space):这是共享的内存区域,用于存储能够被垃圾回收器回收的对象。
方法区(Method Area):这块区域之前被称做“永生代”(permanent generation),用于存储被加载的类。这块区域最近被JVM取消了。如今,被加载的类做为元数据加载到底层操做系统的本地内存区。
本地区(Native Area):这个区域用于存储基本类型的引用和变量。
一个有效的管理内存方法是把对空间划分为不一样代,这样垃圾回收器就不用扫描整个堆区。大多数的对象的生命周期都很段短暂,那些生命周期较长的对象每每直到应用退出才须要被清除。
当一个Java应用建立了一个对象,这个对象是被存储到“初生池”(eden pool
)。一旦初生池存储满了,就会在新生代触发一次minor gc(小范围的垃圾回收)。首先,垃圾回收器会标记出那些“死对象”(再也不被应用所引用的对象),同时延长全部保留对象的生命周期(这个生命周期长度是用数字来描述,表明了期所经历过的垃圾回收的次数)。而后,垃圾回收器会回收这些死对象,并把剩余的活着的对象移动到“幸存池”(survivor pool
),从而清空初生池。
当一个对象存活达到必定的周期后,它就会被移动到堆中的老生代:“终身代”(tenured pool
)。最后,当终身代被填满时,就会触发一次full gc或major gc(彻底的垃圾回收),以清理终身代。
(译者注:通常咱们把初生池和幸存池所在的区域合并成为新生代,把终身代所在的区域成为老生代。对应的,在新生代上产生的gc称为minor gc,在老生代上产生的gc称为full gc。但愿这样你们在其余地方看到对应的术语时能更好理解)
当垃圾回收(gc)执行的时候,全部应用线程都要被中止,系统产生一次暂停。minor gc很是频繁,因此被优化的可以快速的回收死对象,是新生代的内存的主要的回收方式。major gc运行起来就相对慢得多,由于要扫描很是多的活着的对象。垃圾回收器自己也有多种实现,有些垃圾回收器在必定状况下能更快的执行major gc。
堆的大小是动态的,只有堆须要扩张的时候才会从内存中分配。当堆被填满时,JVM会从新给堆分配更多的内存,直到达到堆大小的上限,这种从新分配一样会致使应用的短暂中止。
JVM是运行在一个独立的进程中的,但它能够并发执行多个线程,每一个线程都运行本身的方法,这是Java必备的一个部分。以即时消息客户端这样一个应用为例,它至少运行两个线程。一个线程用于等待用户输入,另外一个检查服务端是否有新的消息传输。再以服务端应用为例,有时一个请求可能要涉及多个线程并发执行,因此须要多线程来处理请求。
在JVM的进程中,全部的线程共享内存和其余可用的资源。每个JVM进程在进入点(main方法)处都要启动一个主线程,其余线程都从主线程启动,成为执行过程当中的一个独立部分。线程能够再不一样的处理器上并行执行,一样也能够共享一个处理器,线程调度器负责处理多个线程共享一个处理器的状况。
不少应用(特别是服务端应用)会处理不少任务,须要并行运行。这些任务中有些是很是重要的,须要实时执行的。而另一些是后台任务,能够在CPU空闲时执行。任务是在不一样的线程中运行的。举例子来讲,服务端可能有一些低优先级的线程,它们会根据一些数据来计算统计信息。同时也会启动一些高优先级的进程用于处理传入的数据,响应对这些统计信息的请求。这里可能有不少的源数据,不少来自客户端的数据请求,每一个请求都会使服务端短暂的中止后台计算的线程以响应这个请求。因此,你必须监控在运行的线程数目而且保证有足够的CPU时间来执行必要的计算。
(译者注:这一段在原文中是在性能优化的章节,译者认为这多是做者的不当心,彷佛放在线程的章节更合适。)
堆的优化、栈的优化和垃圾回收器的选择。
JVM的性能取决于其配置是否与应用的功能相匹配。尽管垃圾回收器和内存回收进程是自动管理内存的,可是你必须掌管它们的频率。一般来讲,你的应用可以使用的内存越多,那么这些会致使应用暂停的内存管理进程须要起做用的就越少。
(1)若是垃圾回收发生的频率比你想的要多不少,那么能够在启动JVM的时候为其配置更大的最大堆大小值。堆被填满的时间越久,就越能下降垃圾回收发生的频率。最大堆大小值能够在启动JVM的时候,用-Xmx
参数来设定。默认的最大堆大小是被设置为可用的操做系统内存的四分之一,或者最小1GB。
(2)若是问题出在常常从新分配内存,那么你能够把初始化堆大小设置为和最大堆大小同样。这就意味着JVM永远不须要为堆从新分配内存。但这样作就会失去动态堆大小适配的优化,堆的大小从一开始就被固定下来。配置初始化对大小是在启动JVM,用-Xms
来设定。默认初始化堆大小会被设定为操做系统可用的物理内存的六十四分之一,或者设置一个最小值。这个值是根据不一样的平台来肯定的。
(3)若是你清楚是哪一种垃圾回收(minor gc或major gc)致使了性能问题,能够在不改变整个堆大小的状况下设定新生代和老生代的大小比例。对于须要产生大量临时对象的应用,须要增大新生代的比例(固然,后果是减少了老生代的大小)。对于长生命周期对象较多的应用,则需增大老生代的比例(天然须要减小新生代的大小)。如下几种方法能够用来设定新生代和老生代的大小:
在启动JVM时,使用-XX:NewRatio
参数来具体指定新生代和老生代的大小比例。好比,若是想让老生代的大小是新生代的五倍,则设置参数为-XX:NewRatio=5,默认这个参数设定为2(即老生代占用堆空间的三分之二,新生代占用三分之一)。
在启动JVM时,直接使用-Xmn
参数设定初始化和最大新生代大小,那么堆中的剩余大小便是老生代的大小。
在启动JVM时,直接使用-XX:NewSize
和-XX:MaxNewSize
参数设定初始化和最大新生代大小,那么堆中的剩余大小便是老生代的大小。
(4)每个线程都有一个栈,用于保存函数调用、返回地址等等,这些栈有着对应的内存分配。若是线程过多,就会致使OutOfMemoryError。即便你有足够的空间的堆来存放对象,你的应用也可能会由于建立一个新的线程而崩溃。这种状况下,须要考虑限制线程中的栈大小的最大值。线程栈大小能够在JVM启动的时候,经过-Xss
参数来设置,默认这个值被设定为320KB至1024KB之间,这和平台相关。
当开发或运行一个Java应用的时候,对JVM的性能进行监控是很重要的。配置JVM不是一次配置就万事大吉的,特别是你要应对的是Java服务器应用的状况。你必须持续的检查堆内存和非堆内存的分配和使用状况,线程数的建立状况和内存中加载的类的数据状况等。这些都是核心参数。
使用Anturis控制台,你能够为任何的硬件组件上运行的JVM配置监控(例如,在一台电脑上运行的一个Tomcat网页服务器)。
JVM监控可使用如下衡量标准:
总内存使用状况(MB):即JVM使用的总内存。若是JVM使用了全部可用内存,这项指标能够衡量底层操做系统的总体性能。
堆内存使用(MB):即JVM为运行的Java应用所使用的对象分配的全部内存。不使用的对象一般会被垃圾回收器从堆中移除。因此,若是这个指数增大,表示你的应用没有把不使用的对象移除或者你须要更好的配置垃圾回收器的参数。
非堆内存的使用(MB):即为方法区和代码缓存分配的全部内存。方法区是用于存储被加载的类的引用,若是这些引用没有被适当的清理,永生代池会在每次应用被从新部署的时候都会增大,致使非堆的内存泄露。这个指标也可能指示了线程建立的泄露。
池内总内存(MB):即JVM所分配的全部变量内存池的内存和(即除了代码缓存区外的全部内存和)。这个指标可以让你明确你的应用在JVM过载前所能使用的总内存。
线程:即全部有效线程数。举个例子,在Tomcat服务器中每一个请求都是一个独立的线程来处理,因此这个衡量指标能够表示当前有多少个请求数,是否影响到了后台低权限的线程的运行。
类:即全部被加载的类的总数。若是你的应用动态的建立不少类,这多是服务器内存泄露的一个缘由。