虚拟机主要由三部分组成:编译器(执行引擎),堆与栈。java
编译器分为即时编译器与解释器。算法
即时编译器将代码编译成本地代码存于code区。所以它快,但它有内存限制!服务器
解释器逐行解释字节码,至关于脚本顺序执行,很慢,性能约为C语言的80%。优化的一部分是使代码尽早进入编译器。将部分代码内联(函数散开于代码中,与编译器无关)。多线程
栈是JVM的函数栈。全部函数必分配于栈。栈中一个帧就是一个函数,因函数之间互相调用,栈帧中包含参数,返回地址,返回值等。第一个参数必然是this指针。递归函数会造成大量的栈帧,搞很差会溢出了。栈的大小能够配置,太大并很差。它是惟一能够配置的地方,其它就不可优化了。并发
堆是优化的重点,理解为全部对象在堆中。堆划分为年轻代,老年代,持久代(java8是metaspace-使用系统内存,而不是虚拟机堆内存,再也不是perm,也就不是持久代了)。年轻代又分为幸存区两个或多个(其中一个必空)及eden(伊甸园区)。app
初始的对象在伊甸园区,通过GC收集后,进入存活区,已被收集的对象固然哪也不去,被销毁了。存活区通过15次收集还存活,则进入老年代。存活区满了后交换存活区,并清空原存活区。函数
老年代最大,因它要放置大对象及长久不消失的对象。工具
从以上能够看出来堆的优化,就是合理的设置堆内存空间的大小,使之少发生GC,不浪费,不拥挤,不抖动。性能
JVM的参数分为-X,-XX,-D三种类型。虚拟机自身划分为客户端与server模式。咱们固然要用server模式。server模式中编译器默认为混合模式,也就是即时编译器JIT与解释器混合使用。不须要改它。优化
说明:
编译流程把咱们编写的源代码通过词法分析器、语法分析器、语义分析器、字节码生成器处理后最终生成JVM的字节码
执行引擎的编译器和解释器获取到编译好的JVM字节码之后执行
编译器会对JVM的字节码进行机器无关的优化(如将字符串的拼接优化为append方式)、机器相关优化、寄存器分配器(将JVM字节码优化为计算机的逻辑门)存放于code区运行
解释器是一行一行的取出代码来执行,性能很慢
说明:
在Java中一个线程就会相应有一个线程栈与之对应。
堆是全部线程共享的。
栈是运行单位,信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;
而堆只负责存储对象信息。
栈帧模型
栈帧中包含方法索引、输入输出参数、本地变量、返回地址,返回值等。第一个参数必然是this指针。递归函数会造成大量的栈帧,搞很差会溢出了。栈的大小能够配置,太大并很差。它是惟一能够配置的地方,其它就不可优化了。
说明:
堆是优化的重点,理解为全部对象在堆中。堆划分为年轻代,老年代,持久代(java8是metaspace-使用系统内存,而不是虚拟机堆内存,再也不是perm,也就不是持久代了)。年轻代又分为幸存区两个或多个(其中一个必空)及eden(伊甸园区)。
初始的对象在伊甸园区,通过GC收集后,进入存活区,已被收集的对象固然哪也不去,被销毁了。存活区通过15次收集还存活,则进入老年代。存活区满了后交换存活区,并清空原存活区。
老年代最大,因它要放置大对象及长久不消失的对象。
注意:
Eden区满了进行的是minor GC,老年代满了进行full GC,进行full GC时整个系统会出现卡顿现象,因此堆优化的重点是合理配置堆空间,减小GC,尤为是full GC
Perm 默认大小64M
NewRatio配比
SurvivorRatio配比
Xmx,Xms,Xmn
1.Serial GC。年轻代使用的收集器,单线程,全部的线程暂停(stop the world)。通常用于Client模式的JVM中。
2.ParNew GC。年轻代使用的收集器,是在SerialGC的基础上,增长了多线程机制。
3.Parrallel Scavenge GC。年轻代使用的收集器,吞吐量优先收集器,吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间), 运行100分钟,GC占用1分钟,吞吐量=99%。server模式JVM默认配置。
4.ParallelOld。老年代使用的收集器,使用了标记整理算法,是JDK1.6中引进的。
5.Serial Old。老年代使用的收集器,CMS收集器失败后的备用收集器。
6.CMS。老年代使用的收集器,又称响应时间优先回收器,使用标记清除算法。他的回收线程数为(CPU核心数+3)/4,因此当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记、并发标记、从新标记、并发清除
7. G1收集器。年轻代和老年代都使用的收集器,属于现代收集器。面向服务器- server、多CPU,多核、并行与并发、创建可预测的停顿时间模型
在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。
这个算法的基本思路就是经过一系列名为GC Roots的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。
下图对象obj8, obj9, obj10它们到GC Roots是不可达的,因此它们将会断定为是可回收对象。
标记-清除算法将垃圾回收分为两个阶段:
①.标记阶段:首先标记出全部须要回收的对象。
②.清除阶段:标记完成后,统一回收被标记的对象
缺点:
①.效率问题:标记清除过程效率都不高。
②.空间问题:标记清除以后会产生大量的不连续的内存碎片(空间碎片太多可能会致使之后在程序运行过程当中须要分配较大的对象时,没法找到足够的连续的内存空间而不得不提早触发另外一次垃圾收集动做。)
1).标记阶段:首先标记出全部须要回收的对象。与“标记-清除”同样
2).让存活的对象向内存的一段移动。而不跟“标记-清除”直接对可回收对象进行清理
3).再清理掉边界之外的内存。
因为老年代存活率高,没有额外内存对老年代进行空间担保,那么老年代只能采用标记-清理算法或者标记整理算法。
3.1.算法思想:
1).将现有的内存空间分为两块,每次只使用一块.
2).当其中一块用完的时候,就将还存活的对象复制到另一块上去。
3).再把已使用过的内存空间一次清理掉。
3.2.优势:
1).因为是每次都对整个半区进行内存回收,内存分配时没必要考虑内存碎片问题。
2).只要移动堆顶指针,按顺序分配内存便可,实现简单,运行高效。
3.3.缺点:
1).内存减小为原来的一半,太浪费了。
2).对象存活率较高的时候就要执行较多的复制操做,效率变低。
3).若是不使用50%的对分策略,老年代须要考虑的空间担保策略。
以上三种算法的综合:
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少许存活,选用:复制算法
在老年代中由于对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。
•jps
•jmap
•Jstat•Jvisualvm: window下启动远程监控,并在被监控服务端,启动jstatd服务。
命令格式:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
可选项:
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
使用示例:
jmap -heap 11892 其中11892是java程序的进程id
运行结果:
Heap Configuration: #堆内存初始化配置 MinHeapFreeRatio = 40 #-XX:MinHeapFreeRatio设置JVM堆最小空闲比率 MaxHeapFreeRatio = 70 #-XX:MaxHeapFreeRatio设置JVM堆最大空闲比率 MaxHeapSize = 100663296 (96.0MB) #-XX:MaxHeapSize=设置JVM堆的最大大小 NewSize = 1048576 (1.0MB) #-XX:NewSize=设置JVM堆的‘新生代’的默认大小 MaxNewSize = 4294901760 (4095.9375MB) #-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小 OldSize = 4194304 (4.0MB) #-XX:OldSize=设置JVM堆的‘老生代’的大小 NewRatio = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率 SurvivorRatio = 8 #-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值 PermSize = 12582912 (12.0MB) #-XX:PermSize=<value>:设置JVM堆的‘持久代’的初始大小 MaxPermSize = 67108864 (64.0MB) #-XX:MaxPermSize=<value>:设置JVM堆的‘持久代’的最大大小 Heap Usage: New Generation (Eden + 1 Survivor Space): #新生代区内存分布,包含伊甸园区+1个Survivor区 capacity = 30212096 (28.8125MB) used = 27103784 (25.848182678222656MB) free = 3108312 (2.9643173217773438MB) 89.71169693092462% used Eden Space: #Eden区内存分布 capacity = 26869760 (25.625MB) used = 26869760 (25.625MB) free = 0 (0.0MB) 100.0% used From Space: #其中一个Survivor区的内存分布 capacity = 3342336 (3.1875MB) used = 234024 (0.22318267822265625MB) free = 3108312 (2.9643173217773438MB) 7.001809512867647% used To Space: #另外一个Survivor区的内存分布 capacity = 3342336 (3.1875MB) used = 0 (0.0MB) free = 3342336 (3.1875MB) 0.0% used tenured generation: #当前的Old区内存分布 capacity = 67108864 (64.0MB) used = 67108816 (63.99995422363281MB) free = 48 (4.57763671875E-5MB) 99.99992847442627% used Perm Generation: #当前的 “持久代” 内存分布(java8已经没有持久代的概念了) capacity = 14417920 (13.75MB) used = 14339216 (13.674942016601562MB) free = 78704 (0.0750579833984375MB) 99.45412375710227% used