这是我在公司给团队小伙伴一次技术小分享。
新手司机能够收藏、学习,老司机能够批评指正。
ps:
内容参考了众多优秀博文、书籍,部分图片来源于博文,若有侵权请联系删除。
为何Java
能够实现所谓的“一次编写,处处运行”,主要是由于虚拟机的存在。Java
虚拟机负责Java
程序设计语言的安全特性和平台无关性。Java虚拟机屏蔽了与具体操做系统平台相关的信息,使得Java语言编译器只须要生成在Java
虚拟机上运行的字节码,就能够在多种平台上不加修改地运行。Java虚拟机使得Java摆脱了具体机器的束缚,使跨越不一样平台编写程序成为了可能。java
Java虚拟机基本上都是JDK
自带的虚拟机HotSpot
,这款虚拟机也是目前商用虚拟中市场份额最大的一款虚拟机,能够经过在命令行程序中输入java -version
来查看:linux
其实市面上还有不少别的优秀的虚拟机。Sun公司除了有大名鼎鼎的HotSpot
外,还有KVM
、Squawk VM
、Maxine VM
,BEA公司有JRockit VM
、IBM公司有J9 VM
等等。算法
Java
虚拟机(JVM
)内部定义了程序在运行时须要使用到的内存区域。内存区域主要分为主内存和工做内存。主内存即主机物理内存,工做内存按做用域可划分为线程独享区和线程共享区。安全
宏观来看是这样子的,以下图:框架
Java内存模型规定了全部的变量都存储在主内存(Main Memory
)中,每条线程还有本身的工做内存(Working Memory
),线程的工做内存中保存了被该线程使用到的变量和主内存副本拷贝,线程对变量全部的操做(读取、赋值)都必须在工做内存中进行,而不能直接读写主内存中的变量。不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递均须要经过主内存来完成,jvm
Jvm运行时内存模型,不包含主内存。以下图:ide
下面将会逐一详细介绍上面的内存区域。函数
Java stack
。声明周期和线程相同,方法执行会建立栈帧,用于存储局部变量、操做数栈、动态连接、方法出口等信息。Native method stack
。做用和虚拟机栈同样,不过面向的是本地方法。不属于jvm
规范,hotspot
没有这块区域。Program counter register
。区域小,是线程执行的字节码的行号指示器,至关于存的是一条条的指令。用于存放对象实例,是全部内存区域中最大的一块。实际上这块内存还被划分的更细:新生代和老年代,空间占用比例为1 : 2
,新生代再细致一点有:Eden空间、From Survivor(S0)、To Survivor(S1),空间占用比例为8 : 1 : 1
。进一步划分的目的是更好地回收内存,或者更快地分配内存。工具
用于存放虚拟机加载的类信息、常量(常量池)、静态变量、即便编译器编译后的代码等数据,即“HotSpot”的永久代。在JDK 7以后,咱们使用的HotSpot应该就没有永久代这个概念,采用的是Native Memory来实现方法区的规划。布局
直接内存,即主内存,并非虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,可是这部份内存也被频繁地使用。并且也可能致使OutOfMemoryError异常出现。
JDK1.4中新加入的NIO(New Input/Output)类,能够直接使用Native函数库直接分配堆外内存,这样就能在一些场景中显著提升性能,由于避免了在Java堆和Native堆之间来回复制数据。
哪些内存须要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象。那么如何肯定要回收的对象,以及采用什么样的策略去回收,适合什么样的场景,这是咱们要关注的几个点。
了解一个对象知足什么样的条件就认为是可被回收的对象是重要的一环。
给对象添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1
;当引用失效时,计数器值-1
。当计数值为0的对象就是不可能再被使用的。这种算法使用场景不少,可是,Java中却没有使用这种算法,由于这种算法很难解决对象之间相互引用的状况。
public class ReferenceCountingGC{ public static void main(String[] args){ ReferenceCountingGC objectA = new ReferenceCountingGC(); ReferenceCountingGC objectB = new ReferenceCountingGC(); objectA.instance = objectB; objectB.instance = objectA; } }
这个算法的基本思想是经过一系列称为GC Roots
的对象做为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证实此对象是不可用的。在Java语言中能够做为GC Roots
的对象包括:
Native
方法)引用的对象采用内存空间比例为1 : 1
的2块内存上,只使用其中一块,当须要回收时,将存活的对象复制到另一块,原有的那一块内存空间直接所有清除。这种算法比较简单粗暴,缺点也很明显,内存只能使用1/2
。
对标识为可清理的对象直接进行清理操做,不会发生复制或者移动,相对复制算法成本比较小。缺点:对标记的对象清除以后,因为未移动过对象,将产生大量不连续的内存碎片,当大对象出现时,因为没有足够的连续内存致使不得不对碎片进行整理,也就是Full GC。
标记-整理算法可以解决标记-清除算法带来的碎片化问题
根据上面提到的回收算法,jvm
内置了拥有众多的收集器来适应不一样的场景。根据运行环境的物理配置信息,会自动的选择使用client模式、server模式的垃圾收集器,还能够继续根据运行时数据的状况来筛选适合当前场景的垃圾收集器。
上图展现了新生代和老年代的几种垃圾收集器,其中有连线的表明是能够组合使用的。
javap -verbose
-X
参数:全部的这类参数都以-X
开始,例如经常使用的-Xmx
,+
或-
,而后才设置JVM
选项的实际名称。例如,-XX:+
相似true
,表启用,-XX:-
相似false
。string
或者integer
,咱们先写参数的名称,后面加上=
,最后赋值。例如 -XX:ParamName
=Value
-Xms
即 -XX:InitialHeapSize
的缩写,指定JVM的初始内存大小
-Xms20M 设置JVM启动内存的最小值为20M,必须以M为单位
-Xmx
即 -XX:MaxHeapSize
的缩写,指定JVM的最大堆内存大小
-Xmx20M 表示设置JVM启动内存的最大值为20M,单位为M,将-Xmx和-Xms设置为同样能够避免JVM内存自动扩展。
-verbose:gc
输出虚拟机中GC的详细状况-Xss128k
设置虚拟机栈的大小为128k-Xoss128k
设置本地方法栈的大小为128k。HotSpot不区分虚拟机栈和本地方法栈,所以对于HotSpot这个参数无效。-XX:PermSize=10M
JVM初始分配的永久代的容量,必须以M为单位-XX:MaxPermSize=10M
JVM容许分配的永久代的最大容量,必须以M为单位,大部分状况下这个参数默认为64M-Xnoclassgc
关闭JVM对类的垃圾回收-XX:+TraceClassLoading
查看类的加载信息-XX:+TraceClassUnLoading
查看类的卸载信息-XX:NewRatio=4
设置年轻代:老年代的大小比值为1:4,这意味着年轻代占整个堆的1/5-XX:SurvivorRatio=8
设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8-Xmn20M
设置年轻代的大小为20M-XX:+HeapDumpOnOutOfMemoryError
可让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照-XX:+UseG1GC
让JVM使用G1垃圾收集器-XX:+PrintGCDetails
在控制台上打印出GC具体细节-XX:+PrintGC
在控制台上打印出GC信息-XX:PretenureSizeThreshold=3145728
对象大于3145728(3M)时直接进入老年代分配,单位为byte-XX:MaxTenuringThreshold=1
对象年龄大于1,自动进入老年代-XX:CompileThreshold=1000
一个方法被调用1000次以后,会被认为是热点代码,并触发即时编译-XX:+PrintHeapAtGC
能够看到每次GC先后堆内存布局-XX:+PrintTLAB
能够看到TLAB的使用状况-XX:+UseSpining
开启自旋锁-XX:PreBlockSpin
更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁命令行
java -jar projectName.jar -verbose:gc -Xms20M -Xmx20M
所谓工具,就是经过一些简便的脚本去执行程序去呈现结果数据。这里涉及到一些语法格式。
统一语法都相似这种形式:$ cmd [option id[ pid | vmid |hostid ]]
其中hostid
为可选项,默认为localgost
, vmid
/pid
依赖jps
获取
jps
是Java Process Status
的缩写,查看当前java
进程的运行状态快照。理解为linux
命令ps
的java
版本
-m
运行时传入的参数-v
虚拟机参数-l
运行的主类全限定名或jar
包名称示例
jps -mlv
jstat
是JVM Statistics Monitoring Tool
的缩写,查看虚拟机统计信息监控数据,如类信息、内存、垃圾收集、JIT
编译等
-gc
显示gc的信息,查看gc
次数以及时间-class
监视类装载、卸载数量、总空间以及类装载所耗费的时间-gc
监视Java堆情况,包括Eden
区、两个Survivor
区、老年代、永久带等的容量、已用空间、GC
时间合计等信息-gccapacity
监视内容基本与-gc
相同,但输出主要关注Java堆各个区域使用到的最大、最小空间-gcutil
监视内容基本与-gc
相同,但输出主要关注已使用的空间占总空间的百分比-gccause
与-gcutil
功能同样,可是会额外输出致使上一次GC
产生的缘由-gcnew
监视新生代GC
情况-gcnewcapacity
监视内容基本与-gcnew
相同,但输出主要关注使用到的最大、最小空间-gcold
监视老年代GC
情况-gcoldcapacity
监视内容基本与-gcold
相同,但输出主要关注使用到的最大、最小空间-gcpermcapacity
输出永久代使用到的最大、最小空间-compiler
输出JIT编译器编译过的方法、耗时等信息-printcompilation
输出已经被JIT
编译的方法jstat -gcutil pid
依赖jps
得到pid
查看类装载、内存、垃圾收集、jit
编译信息示例
jstat -gcutil 3333 1000 10
对pid
为3333
的进程每隔1
秒打印1
次,总打印10
次jinfo
即Configuration Info for Java
,实时查看和调整jvm
参数
-flag <name>
打印jvm参数的值-flag [+|-]<name>
启用/禁用jvm参数-flag <name>=<value>
修改jvm参数值-flags <pid>
打印全部jvm参数值-sysprops <pid>
打印java系统属性<no option> <pid>
打印上面全部信息示例
jmap
即Memory Map for Java
,内存映像工具用于生成堆转存快照
-dump
生成Java堆转储快照。格式为-dump:[live, ]format=b,file=<filename>
,其中live
自参数说明是否只dump
出存活的对象-finalizerinfo
显示在F-Queue
中等待Finalizer
线程执行finalize
方法的对象。只在Linux
和Solaris
系统有效-heap
显示Java
堆详细信息,如使用哪一种收集器、参数配置、分代情况等。只在Linux
和Solaris
系统有效-histo
显示堆中对象统计信息,包括类、实例数量、合计容量-permstat
以ClassLoader
为统计口径显示永久代内存状态。只在Linux
和Solaris
系统下有效-F
当虚拟机进行对-dump
选项没有响应时,可以使用这个选项强制生成dump
快照。只在Linux
和Solaris
系统下有效示例
jmap -dump:live,format=b,file=heap.bin 7298
将pid
为7298
的虚拟机内活对象导出为heap.bin
二进制文件jhat
即JVM Heap Analysis Tool
,虚拟机堆分析工具
示例
jhat /data/dump.bin
分析导出的堆快照jstack
即Stack Trace for Java
, 堆栈跟踪工具,查看虚拟机线程快照。目的主要是定位线程长时间出现停顿的缘由,如线程间死锁、死循环、请求外部资源致使的长时间等待等都是致使线程长时间停顿的缘由。
-F
即force
,强制打印线程快照信息-m
即mixed mode
,同时打印java
框架信息和本地库信息-l
即long listing
,打印更长(更多)的列信息示例
jstack -F 7298
jstack -l 7298
jstack -m 7298