JAVA 程序运行与虚拟机之上,运行时须要内存空间。虚拟机执行 JAVA 程序的过程当中会把它管理的内存划分为不一样的数据区域方便管理。java
虚拟机管理内存数据区域划分以下图:linux
数据区域分类:面试
方法区: (Method Area)算法
虚拟机栈 : (VM Stack)数组
本地方法栈 : (Native Method Stack)tomcat
堆: (Heap)性能优化
程序计数器: (Program Counter Register)服务器
直接内存 : (Direct Memory)数据结构
说明:多线程
一、程序计数器
行号指示器,字节码指令的分支、循环、跳转、异常处理、线程恢复 (CPU 切换),每条线程都须要一个独立的计数器,线程私有内存互不影响, 该区域不会发生内存溢出异常。
二、虚拟机栈
是线程私有的,声明周期与线程相同,虚拟机栈是 Java 方法执行的内存模型,每一个方法被执行时都会建立一个栈帧,即方法运行期间的基础数据结构,栈帧用于存储:局部变量表、操做数栈、动态连接、方法出口等,每一个方法执行中都对应虚拟机栈帧从入栈处处栈的过程。
是一种数据结构,是虚拟机中的局部变量表,对应物理层之上的程序数据模型。
局部变量表,是一种程序运行数据模型,存放了编译期可知的各类数据类型例如:
Boolean、byte、char、short、int、float、long、double、对象引用类型 (对象内存地址变量,指针或句柄),程序运行时,根据局部变量表分配栈帧空间大小,在运行中,大小是不变的异常类型:stackOverFlowError 线程请求栈深度大于虚拟机容许深度 OutOfMemory 内存空间耗尽没法进行扩展。
三、本地方法栈
与虚拟机栈相似,虚拟机栈为 Java 程序服务,本地方法栈支持虚拟机的运行服务,具体实现由虚拟机厂商决定,也会抛出 stackOverFlowError、OutOfMemory 异常。
四、堆
是虚拟机管理内存中最大的一部分,被全部线程共享,用于存放对象实例 (对象、数组),物理上不连续的内存空间,因为 GC 收集器,分代收集,因此划分为:新生代 Eden、From SurVivor 空间、To SurVivor 空间,allot buffer(分配空间),可能会划分出多个线程私有的缓冲区,老年代。
五、方法区
与堆同样属于线程共享的内存区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码(动态加载 OSGI)等数据。理论上属于 java 虚拟机的一部分,为了区分开来叫作 Non-Heap 非堆。
这个区域能够选择不进行垃圾回收,该区域回收目的主要是常量池的回收,及类型的卸载 class, 内存区不足时会抛出 OutOfMemory 异常
运行时常量池:
方法区的一部分,Class 的版本、字段、接口、方法等,及编译期生成的各类字面量、符号引用,编译类加载后存放在该区域。会抛出 OutOfMemory 异常。
六、直接内存
直接内存不属于虚拟内存区域,是一种基于通道与缓冲区的 IO 方式,可使用本地函数直接分配堆外内存,在堆中存储引用的外部内存地址,经过引用完成对直接引用内存的操做,1.4 以后提供的 NIO 显著提升效率,避免了堆内存与 Native 内存的来回复制操做,不受虚拟机内存控制,会抛出 OUtOfMemory 异常。
对象访问 涉及到对象的地址变动状态变动,内存地址移动,变量、接口、实现类、方法、父类型等。
一、 句柄方式 (访问)
二、指针方式 (访问)
优缺点:
句柄访问方式:reference 中存储的是稳定的地址,对象变动时只会改变句柄实例数据指针,引用自己不须要修改
指针访问方式:优势速度快,节省了指针定位时间开销
开发过程当中,或程序运行过程当中每次遇到 OutOfMemory 异常或 GC 异常或 StackOverflowError 异常咱们都是一堆参数乱配,都把值调大,只是大致知道是跟 jvm 内存分配有关,具体应该怎么调,对应的异常应该调整那些参数,或者换句话说,jvm 内存分配区域中都分别对应那些参数大多数状况下都是不知道的,只是把相关的参数跳上去,预期结果都是应该起做用,到底能不能起做用,本身内心也没底。下面就来讲一下 jvm 堆、栈、方法区等内存区域对应的参数,及每一个区域可能抛出的异常类型,发生异常的场景分析。
一、参数类型
二、异常类型
三、辅助参数说明
四、参数说明、对应场景的异常
1). 堆内存参数
当最小值 = 最大值时,这时堆内存是不可扩展的。
例:-Xms80M -Xmx80M
一般将 -Xmx 和 -Xms 设置为同样的大小来减小 gc 的次数,堆内存不足时抛出 OutOfMemoryError 异常。
2). 栈内存参数
-Xss
例:-Xss128k
单线程下不管栈帧太大仍是栈容量过小,及引用深度超过虚拟机容许深度都会抛出 StackOverflowError 每一个方法压入栈的帧大小是不一致的。多线程下当每一个线程分配栈帧太大内存不可以扩展时抛出 OutOfMemoryError 异常线程栈帧越大,可建立的线程越少。
3). 方法区参数
-XX:PermSize 方法区内存最小值
-XX:MaxPermSize 方法区内存最大值
各个线程共享的内存区域,主要用来存储类的元数据、常量、静态变量、即时编译器编译后的代码等数据
例:-XX:PermSize=20M -XX:MaxPermSize=20M
异常类型 OutOfMemoryError :
缘由:常量过多,或代理反射等使用频繁
4). 本机直接内存参数
-XX:MaxDirectMemorySize
例:-XX:MaxDirectMemorySize=10M
不足时抛出 OutOfMemory 异常
经典的垃圾回收算法如下几种
一、标记–清除算法 (Mark-Sweep)
回收前状态:
回收后状态:
优缺点:
算法执行分为两个阶段标记与清除,全部的回收算法,基本都
基于标记回收算法作了深度优化
缺点:效率问题,内存空间碎片(不连续的空间)
二、复制算法 (Copying)
回收前状态:
Eden 内存空间 8
Survivor1 空间(From 空间)1
Survivor2 空间 (To 空间) 1
Eden 内存空间与 Survivor 空间 8:1
回收后状态:
Survivor1 空间(From 空间)1
Eden 内存空间与 Survivor 空间 8:1
优缺点:
比较标记清除算法,避免了回收形成的内存碎片问题,
缺点:以局部的内存空间牺牲为代价,不过空间的浪费比较小,默认 8:1 的比例 1 是浪费的。
复制也有必定的效率与空间成本
三、标记整理算法 (Mark-Compact)
回收前状态:
回收后状态:
优缺点:
避免了,空间的浪费,与内存碎片问题。
缺点:整理时复制有效率成本。
一、七种垃圾收集器
(1) Serial(串行 GC)-XX:+UseSerialGC
(2) ParNew(并行 GC)-XX:+UseParNewGC
(3) Parallel Scavenge(并行回收 GC)
(4) Serial Old(MSC)(串行 GC)-XX:+UseSerialGC
(5) CMS(并发 GC)-XX:+UseConcMarkSweepGC
(6) Parallel Old(并行 GC)-XX:+UseParallelOldGC
(7) G1(JDK1.7update14 才能够正式商用)
2、1~3 用于年轻代垃圾回收:年轻代的垃圾回收称为 minor GC
3、4~6 用于年老代垃圾回收(固然也能够用于方法区的回收):年老代的垃圾回收称为 full GC
G1 独立完成 "分代垃圾回收"
注意:并行与并发
并行:多条垃圾回收线程同时操做
并发:垃圾回收线程与用户线程一块儿操做
四、经常使用五种组合
Serial/Serial Old
ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
ParNew/CMS:当下比较高效的组合
Parallel Scavenge/Parallel Old:自动管理的组合
G1:最早进的收集器,可是须要 JDK1.7update14 以上
5. Serial/Serial Old
年轻代 Serial 收集器采用单个 GC 线程实现 "复制" 算法(包括扫描、复制)
年老代 Serial Old 收集器采用单个 GC 线程实现 "标记 - 整理" 算法
Serial 与 Serial Old 都会暂停全部用户线程(即 STW)
说明:
STW(stop the world):编译代码时为每个方法注入 safepoint(方法中循环结束的点、方法执行结束的点),在暂停应用时,须要等待全部的用户线程进入 safepoint,以后暂停全部线程,而后进行垃圾回收。
适用场合:
CPU 核数 <2,物理内存 <2G 的机器(简单来说,单 CPU,新生代空间较小且对 STW 时间要求不高的状况下使用)
-XX:UseSerialGC:强制使用该 GC 组合
-XX:PrintGCApplicationStoppedTime:查看 STW 时间
6.ParNew/Serial Old:
ParNew 除了采用多 GC 线程来实现复制算法之外,其余都与 Serial 同样,可是此组合中的 Serial Old 又是一个单 GC 线程,因此该组合是一个比较尴尬的组合,在单 CPU 状况下没有 Serial/Serial Old 速度快(由于 ParNew 多线程须要切换),在多 CPU 状况下又没有以后的三种组合快(由于 Serial Old 是单 GC 线程),因此使用其实很少。
-XX:ParallelGCThreads:指定 ParNew GC 线程的数量,默认与 CPU 核数相同,该参数在于 CMS GC 组合时,也可能会用到
7.Parallel Scavenge/Parallel Old:
特色:
年轻代 Parallel Scavenge 收集器采用多个 GC 线程实现 "复制" 算法(包括扫描、复制)年老代 Parallel Old 收集器采用多个 GC 线程实现 "标记 - 整理" 算 ParallelScavenge 与 Parallel Old 都会暂停全部用户线程(即 STW)
说明:
吞吐量:CPU 运行代码时间 /(CPU 运行代码时间 +GC 时间)CMS 主要注重 STW 的缩短(该时间越短,用户体验越好,因此主要用于处理不少的交互任务的状况)Parallel Scavenge/Parallel Old 主要注重吞吐量(吞吐量越大,说明 CPU 利用率越高,因此主要用于处理不少的 CPU 计算任务而用户交互任务较少的状况)
参数设置:
-XX:+UseParallelOldGC:使用该 GC 组合
-XX:GCTimeRatio:直接设置吞吐量大小,假设设为 19,则容许的最大 GC 时间占总时间的 1/(1+19),默认值为 99,即 1/(1+99)
-XX:MaxGCPauseMillis:最大 GC 停顿时间,该参数并不是越小越好
-XX:+UseAdaptiveSizePolicy:开启该参数,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold 这些参数就不起做用了,虚拟机会自动收集监控信息,动态调整这些参数以提供最合适的的停顿时间或者最大的吞吐量(GC 自适应调节策略),而咱们须要设置的就是 -Xmx,-XX:+UseParallelOldGC 或 -XX:GCTimeRatio 两个参数就好(固然 -Xms 也指定上与 -Xmx 相同就好)
注意:
-XX:GCTimeRatio 和 -XX:MaxGCPauseMillis 设置一个就好
不开启 -XX:+UseAdaptiveSizePolicy,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold 这些参数依旧能够配置,以 resin 服务器为例
-Xms2048m -Xmx2048m -Xmn512m -Xss1m -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+UseParallelOldGC -XX:GCTimeRatio=19 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps View Code
适用场合:
不少的 CPU 计算任务而用户交互任务较少的状况不想本身去过多的关注 GC 参数,想让虚拟机本身进行调优工做
八、调优方法
8.1 新对象预留新生代
因为 fullGC(老年代) 的成本远比 minorGC(新生代和老年代)的成本大,因此给应用分配一个合理的新生代空间,尽可能将对象分配到新生代减少 fullGC 的频率
8.2 大对象进入老年代
将大对象直接分配到老年代,保持新生代对象的结构的完整性,以提升 GC 效率, 以经过 -XX:PretenureSizeThreshold 设置进入老年代的阀值
8.3 稳定与震荡的堆大小
稳定的对大小是对垃圾回收有利的,方法将 -Xms 和 -Xmx 的大小一致
8.4 吞吐量优先
尽量减小系统执行垃圾回收的总时间,故采用并行垃圾回收器
-XX:+UseParallelGC 或使用 -XX:+UseParallelOldGC
8.5 下降停顿
使用 CMS 回收器, 同时减小 fullGC 的次数
九、获取 gc 信息的方法
9.1 -verbose:gc 或者 -XX:+PrintGC 获取 gc 信息
9.2 -XX:+PrintGCDetails 获取更加详细的 gc 信息
9.3 -XX:+PrintGCTimeStamps 获取 GC 的频率和间隔
9.4 -XX:+PrintHeapAtGC 获取堆的使用状况
9.5 -Xloggc:D:gc.log 指定日志状况的保存路径
十、jvm 调优实战 -tomcat 启动加速
在 tomcat 的 bin/catalina.bat 文件的开头添加相关的配置
十一、jvm 深刻学习
若是想要系统深刻学习 JVM 的话,我在这里给你们推荐一个 Java 方面的中高级交流学习群:650385180,里面会分享一些资深架构师录制的视频录像:有 Spring,MyBatis,Netty 源码分析,高并发、高性能、分布式、微服务架构的原理,JVM 性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工做和遇到技术瓶颈的码友,在这个群里会有你须要的内容。
监控工具:通常问题定位,性能调优都会使用到。
一、jps
Jps 是参照 Unix 系统的取名规则命名的,而他的功能和 ps 的功能相似,能够列举正在运行的饿虚拟机进程并显示虚拟机执行的主类以及这些进程的惟一 ID(LVMID,对应本机来讲和 PID 相同),他的用法以下:
Jps [option] [hostid]
jps -q 只输出 LVMID
jps -m 输出 JVM 启动时传给主类的方法
jps -l 输出主类的全名,若是是 Jar 则输出 jar 的路径
jps -v 输出 JVM 的启动参数
二、jstat
jstat 主要用于监控虚拟机的各类运行状态信息,如类的装载、内存、垃圾回收、JIT 编译器等,在没有 GUI 的服务器上,这款工具是首选的一款监控工具。其用法以下:
jstat [option vmid [interval [s|ms] [vount] ] ]
jstat 监控内容 线程好 刷新时间间隔 次数
jstat –gc 20445 1 20 : 监视 Java 堆,包含 eden、2 个 survivor 区、old 区和永久带区域的容量、已用空间、GC 时间合计等信息
jstat –gcutil 20445 1 20: 监视内容与 -gc 相同,但输出主要关注已使用空间占总空间的百分比
jstat –class 20445 1 20: 监视类的装载、卸载数量以及类的装载总空间和耗费时间等
…….-gccapcity……: 监视内容与 -gc 相同,但输出主要关注 Java 区域用到的最大和最小空间
…….-gccause……..: 与 -gcutil 输出信息相同,额外输出致使上次 GC 产生的缘由
…….-gcnew……….: 监控新生代的 GC 状况
…….-gcnewcapacity..: 与 -gcnew 监控信息相同,输出主要关注使用到的最大和最小空间
…….-gcold……….: 监控老生代的 GC 状况
…….-gcoldcapacity..: 与 -gcold 监控信息相同,输出主要关注使用到的最大和最小空间
…….-gcpermcapacity.: 输出永久带用到的最大和最小空间
…….-compiler…….: 输出 JIT 编译器编译过的方法、耗时信息
…….-printcompilation: 输出已经被 JIT 编译的方法
三、jinfo
jinfo 的做用是实时查看虚拟机的各项参数信息 jps –v 能够查看虚拟机在启动时被显式指定的参数信息,可是若是你想知道默认的一些参数信息呢?除了去查询对应的资料之外,jinfo 就显得很重要了。jinfo 的用法以下:
Jinfo [option] pid
四、jmap
map 用于生成堆快照(heapdump)。固然咱们有不少方法能够取到对应的 dump 信息,如咱们经过 JVM 启动时加入启动参数 –XX:HeapDumpOnOutOfMemoryError 参数,可让 JVM 在出现内存溢出错误的时候自动生成 dump 文件,亦能够经过 -XX:HeapDumpOnCtrlBreak 参数,在运行时使用 ctrl+break 按键生成 dump 文件,固然咱们也可使用 kill -3 pid 的方式去恐吓 JVM 生成 dump 文件。Jmap 的做用不只仅是为了获取 dump 文件,还能够用于查询 finalize 执行队列、Java 堆和永久带的详细信息,如空间使用率、垃圾回收器等。其运行格式以下:
Jmap [option] vmip
监控堆栈信息主要用来定位问题的缘由,生成堆栈快照
…….-dump……: 生成对应的 dump 信息,用法为 -dump:[live,]format=b,file={fileName}
…….-finalizerinfo……: 显示在 F-Queue 中等待的 Finalizer 方法的对象(只在 linux 下生效)
…….-heap……:显示堆的详细信息、垃圾回收器信息、参数配置、分代详情等
…….-histo……:显示堆栈中的对象的统计信息,包含类、实例数量和合计容量
…….-permstat……:以 ClassLoder 为统计口径显示永久带的内存状态
…….-F……:虚拟机对 -dump 无响应时可以使用这个选项强制生成 dump 快照
例子:jmap -dump:format=b,file=yhj.dump 20445
五、jstack
Jstack 用于 JVM 当前时刻的线程快照,又称 threaddump 文件,它是 JVM 当前每一条线程正在执行的堆栈信息的集合。生成线程快照的主要目的是为了定位线程出现长时间停顿的缘由,如线程死锁、死循环、请求外部时长过长致使线程停顿的缘由。经过 jstack 咱们就能够知道哪些进程在后台作些什么?在等待什么资源等!其运行格式以下:
Jstack [option] vmid
-F 当正常输出的请求不响应时强制输出线程堆栈
-l 除堆栈信息外,显示关于锁的附加信息
-m 显示 native 方法的堆栈信息
六、jconsole
在 JDK 的 bin 目录下, 监控内存,thread, 堆栈等
七、jprofile
相似于 jconsole, 比 jconsole 监控信息更全面,内存,线程,包,cup 类,堆栈,等等
注:文章有点长,你们以为做者总结的还能够,你们能够点击左边的二维码进行关注。《Java技术zhai》公众号聊的不只仅是Java技术知识,还有面试等干货,后期还有大量架构干货。你们一块儿关注吧!关注技术zhai,你会了解的更多..............