JAVA虚拟机 JVM 详细分析 原理和优化(我的经验+网络搜集整理学习)

JVM是java实现跨平台的主要依赖就不具体解释它是什么了 ,简单说就是把java的代码转化为操做系统能识别的命令去执行,下面直接讲一下它的组成java

Jvm Internal

1.ClassLoader(类加载器)算法

加载Class 文件到内存中安全

2.ClassArea(方法区)服务器

在类装载器加载class文件到内存的过程当中,虚拟机会提取其中的类型信息,并将这些信息存储到方法区。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。因为全部线程都共享方法区,所以它们对方法区数据的访问必须被设计为是线程安全的。注:常说的String 字符串存储的常量池 ,存在于方法区内部网络

3.Heap(堆)多线程

存储Java程序建立的类实例。全部线程共享,所以设计程序时也要考虑到多线程访问对象(堆数据)的同步问题。并发

4.Stack(栈)jsp

Java栈是线程私有的。每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操做:以帧为单位的压栈或出栈。当线程调用java方法时,虚拟机压入一个新的栈帧到该线程的java栈中。当方法返回时,这个栈帧被从java栈中弹出并抛弃。一个栈帧包含一个java方法的调用状态,它存储有局部变量表、操做栈、动态连接、方法出口等信息。布局

 5.Program Counter Register(程序计数器)性能

一个运行中的Java程序,每当启动一个新线程时,都会为这个新线程建立一个本身的PC(程序计数器)寄存器。程序计数器的做用能够看作是当前线程所执行的字节码的行号指示器。字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。若是线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是Natvie方法,这个计数器值则为空(Undefined)。 

6.Native Method Stack(本地方法栈)

本地方法栈与虚拟机栈所发挥的做用是很是类似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会建立一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,再也不在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。若是某个虚拟机实现的本地方法接口是使用C链接模型的话,那么它的本地方法栈就是C栈。注:这部分功能产生的内存占用属于非堆内容,容易形成非堆内存溢出问题

7.Execution Engine(执行引擎)

负责执行字节码。方法的字节码是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操做码,后面跟随0个或多个操做数。执行引擎执行字节码时,首先取得一个操做码,若是操做码有操做数,取得它的操做数。它执行操做码和跟随的操做数规定的动做,而后再取得下一个操做码。这个执行字节码的过程在线程完成前将一直持续。

接下来分析一下,

JVM的内存组成和优化(重点)

JVM内存模型:按照JVM规范,JAVA虚拟机在运行时会管理如下的内存区域:
程序计数器:当前线程执行的字节码的行号指示器,线程私有。
JAVA虚拟机栈:Java方法执行的内存模型,每一个Java方法的执行对应着一个栈帧的进栈和出栈的操做。
本地方法栈:相似“ JAVA虚拟机栈 ”,可是为native方法的运行提供内存环境。
JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,全部线程共享。可分为新生代,老生代。
方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,    Hotspot中的“永久代”。
运行时常量池:方法区的一部分,存储常量信息,如各类字面量、符号引用等。
直接内存:并非JVM运行时数据区的一部分, 可直接访问的内存, 好比NIO会用到这部分(好多堆外内存溢出就是这个引发)
按照JVM规范,除了程序计数器不会抛出OOM外,其余各个内存区域均可能会抛出OOM。

最多见的OOM状况有如下三种:
java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种状况最多见,通常因为内存泄露或者堆的大小设置不当引发。对于内存泄露,须要经过内存监控软件查找程序中的泄露代码,而堆大小能够经过虚拟机参数-Xms,-Xmx等修改。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,通常出现于大量Class或者jsp页面,或者采用cglib等反射机制的状况,由于上述状况会产生大量的Class信息存储于方法区。此种状况能够经过更改方法区的大小来解决,使用相似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤为是字符串也会致使方法区溢出。
java.lang.StackOverflowError ------> JAVA虚拟机栈溢出,通常是因为程序中存在死循环或者深度递归调用形成的,栈大小设置过小也会出现此种溢出。能够经过虚拟机参数-Xss来设置栈的大小。

从最开始的图片能看出:动态内存分配的部分主要有堆和栈,其它部份内存变化并不大,着重说一下这两个。

栈内存:

线程私有,生命周期和线程相同,栈由一系列帧组成(所以Java栈也叫作帧栈),帧保存一个方法的局部变量、操做数栈、常量池指针,每一次方法调用建立一个帧,并压栈。

官方说法:

Java虚拟机栈描述的是Java方法执行的内存模型:每一个方法被调用的时候都会建立一个栈帧,用于存储局部变量表、操做栈、动态连接、方法出口等信息。每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

在Java虚拟机规范中,对这个区域规定了两种异常状况:

    (1)若是线程请求的栈深度太深,超出了虚拟机所容许的深度,就会出现StackOverFlowError(好比无限递归。由于每一层栈帧都占用必定空间,而 Xss 规定了栈的最大空间,超出这个值就会报错)

    (2)虚拟机栈能够动态扩展,若是扩展到没法申请足够的内存空间,会出现OOM

堆内存:

       JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减小堆直到 -Xms的最小限制。所以服务器通常设置-Xms、-Xmx相等以免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

    详细解释堆的组成

Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。

年轻代
全部新生成的对象首先都是放在年轻代。年轻代的目标就是尽量快速的收集掉那些生命周期短的对象。年轻代通常分3个区,1个Eden区,2个Survivor区(from 和 to)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另一个Survivor区,当另外一个Survivor区也满了的时候,从前一个Survivor区复制过来的而且此时还存活的对象,将可能被复制到年老代。
2个Survivor区是对称的,没有前后关系,因此同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另外一个Survivor区复制过来的对象;而复制到年老区的只有从另外一个Survivor区过来的对象。并且,由于须要交换的缘由,Survivor区至少有一个是空的。特殊的状况下,根据程序须要,Survivor区是能够配置为多个的(多于2个),这样能够增长对象在年轻代中的存在时间,减小被放到年老代的可能。
针对年轻代的垃圾回收即 Young GC。
年老代
在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。所以,能够认为年老代中存放的都是一些生命周期较长的对象。
针对年老代的垃圾回收即 Full GC。

内存申请过程以下:
JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域(注:若是新生成的对象很大,占用连续内存则直接放在老年代)。
当Eden区空间足够时,内存申请结束。不然执行下一步。
JVM试图释放在Eden区中全部不活跃的对象(Young GC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
Survivor区被用来做为Eden区及年老代的中间交换区域。当年老代空间足够时,Survivor区中存活了必定次数的对象会被移到年老代。
当年老代空间不够时,JVM会在年老代进行彻底的垃圾回收(Full GC)。
Full GC后,若Survivor区及年老代仍然没法存放从Eden区复制过来的对象,则会致使JVM没法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。

下面部分参数说明为网络搜集,出现错误请指出

参数说明
-Xmx3550m:设置JVM最大堆内存为3550M。
-Xms3550m:设置JVM初始堆内存为3550M。此值能够设置与-Xmx相同,以免每次垃圾回收完成后JVM从新分配内存。
-Xss128k:设置每一个线程的栈大小。JDK5.0之后每一个线程栈大小为1M,以前每一个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右。须要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上下降系统的性能。
-Xmn2g:设置年轻代大小为2G。在整个堆内存大小肯定的状况下,增大年轻代将会减少年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
-XX:NewSize=1024m:设置年轻代初始值为1024M。
-XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
-XX:PermSize=256m:设置持久代初始值为256M。
-XX:MaxPermSize=256m:设置持久代最大值为256M。
-XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
-XX:MaxTenuringThreshold=7:表示一个对象若是在Survivor区(救助空间)移动了7次尚未被垃圾回收就进入年老代。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入年老代,对于须要大量常驻内存的应用,这样作能够提升效率。若是将此值设置为一个较大值,则年轻代对象会在Survivor区进行屡次复制,这样能够增长对象在年轻代存活时间,增长对象在年轻代被垃圾回收的几率,减小Full GC的频率,这样作能够在某种程度上提升服务稳定性。

部分参数优先级
-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3组参数均可以影响年轻代的大小,混合使用的状况下,优先级是什么?
以下:
高优先级:-XX:NewSize/-XX:MaxNewSize 
中优先级:-Xmn(默认等效  -Xmn=-XX:NewSize=-XX:MaxNewSize=?) 
低优先级:-XX:NewRatio 
推荐使用-Xmn参数,缘由是这个参数简洁,至关于一次设定 NewSize/MaxNewSIze,并且二者相等,适用于生产环境。-Xmn 配合 -Xms/-Xmx,便可将堆内存布局完成。
-Xmn参数是在JDK 1.4 开始支持。

垃圾回收器选择
JVM给出了3种选择:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的状况,因此生产环境的选择主要是并行收集器和并发收集器。
默认状况下JDK5.0之前都是使用串行收集器,若是想使用其余收集器须要在启动时加入相应参数。JDK5.0之后,JVM会根据当前系统配置进行智能判断。

串行收集器
-XX:+UseSerialGC:设置串行收集器。

并行收集器(吞吐量优先)
-XX:+UseParallelGC:设置为并行收集器。此配置仅对年轻代有效。即年轻代使用并行收集,而年老代仍使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时有多少个线程一块儿进行垃圾回收。此值建议配置与CPU数目相等。
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0开始支持对年老代并行收集。
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间(单位毫秒)。若是没法知足此时间,JVM会自动调全年轻代大小,以知足此时间。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动调全年轻代Eden区大小和Survivor区大小的比例,以达成目标系统规定的最低响应时间或者收集频率等指标。此参数建议在使用并行收集器时,一直打开。

并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC:即CMS收集,设置年老代为并发收集。CMS收集是JDK1.4后期版本开始引入的新GC算法。它的主要适合场景是对响应时间的重要性需求大于对吞吐量的需求,可以承受垃圾回收线程和应用线程共享CPU资源,而且应用中存在比较多的长生命周期对象。CMS收集的目标是尽可能减小应用的暂停时间,减小Full GC发生的概率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存。
-XX:+UseParNewGC:设置年轻代为并发收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,因此无需再设置此参数。
-XX:CMSFullGCsBeforeCompaction=0:因为并发收集器不对内存空间进行压缩和整理,因此运行一段时间并行收集之后会产生内存碎片,内存使用效率下降。此参数设置运行0次Full GC后对内存空间进行压缩和整理,即每次Full GC后马上开始压缩和整理内存。
-XX:+UseCMSCompactAtFullCollection:打开内存空间的压缩和整理,在Full GC后执行。可能会影响性能,但能够消除内存碎片。
-XX:+CMSIncrementalMode:设置为增量收集模式。通常适用于单CPU状况。
-XX:CMSInitiatingOccupancyFraction=70:表示年老代内存空间使用到70%时就开始执行CMS收集,以确保年老代有足够的空间接纳来自年轻代的对象,避免Full GC的发生。

其它垃圾回收参数
-XX:+ScavengeBeforeFullGC:年轻代GC优于Full GC执行。
-XX:-DisableExplicitGC:不响应 System.gc() 代码。
-XX:+UseThreadPriorities:启用本地线程优先级API。即便 java.lang.Thread.setPriority() 生效,不启用则无效。
-XX:SoftRefLRUPolicyMSPerMB=0:软引用对象在最后一次被访问后能存活0毫秒(JVM默认为1000毫秒)。
-XX:TargetSurvivorRatio=90:容许90%的Survivor区被占用(JVM默认为50%)。提升对于Survivor区的使用率。

辅助信息参数设置 -XX:-CITime:打印消耗在JIT编译的时间。 -XX:ErrorFile=./hs_err_pid.log:保存错误日志或数据到指定文件中。 -XX:HeapDumpPath=./java_pid.hprof:指定Dump堆内存时的路径。 -XX:-HeapDumpOnOutOfMemoryError:当首次遭遇内存溢出时Dump出此时的堆内存。 -XX:OnError=";":出现致命ERROR后运行自定义命令。 -XX:OnOutOfMemoryError=";":当首次遭遇内存溢出时执行自定义命令。 -XX:-PrintClassHistogram:按下 Ctrl+Break 后打印堆内存中类实例的柱状信息,同JDK的 jmap -histo 命令。 -XX:-PrintConcurrentLocks:按下 Ctrl+Break 后打印线程栈中并发锁的相关信息,同JDK的 jstack -l 命令。 -XX:-PrintCompilation:当一个方法被编译时打印相关信息。 -XX:-PrintGC:每次GC时打印相关信息。 -XX:-PrintGCDetails:每次GC时打印详细信息。 -XX:-PrintGCTimeStamps:打印每次GC的时间戳。 -XX:-TraceClassLoading:跟踪类的加载信息。 -XX:-TraceClassLoadingPreorder:跟踪被引用到的全部类的加载信息。 -XX:-TraceClassResolution:跟踪常量池。 -XX:-TraceClassUnloading:跟踪类的卸载信息。

相关文章
相关标签/搜索