jvm内存分配及对象创建和回收过程
- 2019 年 10 月 4 日
- 筆記
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_37933685/article/details/80617250
个人博客:https://suveng.github.io/blog/
Java历史
2004.9 jdk1.5 tiger 自动装箱拆箱,泛型,,注解,枚举,变长参数,增强for循环 spring2.x spring4.x
2006 jdk1.6 javaee Javase Javame jdk6
- 提供脚本支持
- 提供编译api以及http服务器api
2009 jdk1.7 收购sun 74亿
2014 jdk1.8
2017 jdk1.9
2018 jdk10
java 技术体系
Java程序设计语言
java 虚拟机
class 类文件格式
Java API
第三方Java类库
Java8新特性
- 接口默认方法和静态方法
- lambda表达式和函数式编程
- dateAPI
- 重复注解
- nashorn JavaScript引擎
jvm可视化监控工具
jconsole.exe
在jdk/bin目录下,双击打开可运行,监控吗某个Java程序的状态
编写测试类观察jvm内存
JconsoleTest.java
package jconsole; import java.util.ArrayList; import java.util.List; /** * @author Veng Su [email protected] * @date 2018/4/29 11:28 */ public class JconsoleTest { public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); fill(1000); } private static void fill(int n) throws InterruptedException { List<JconsoleTest> jconsoleTests =new ArrayList<JconsoleTest>(); for (int i=0;i<n;i++){ Thread.sleep(200); jconsoleTests.add(new JconsoleTest()); } } }
jvm内存溢出
Main.java
import java.util.ArrayList; import java.util.List; /** * @author Veng Su [email protected] * @date 2018/4/29 10:39 */ public class Main { public static void main(String[] args) { // 测试堆内存溢出 List<Demo> demoList=new ArrayList<Demo>(); while (true){ demoList.add(new Demo()); } } }
Demo.java
import java.util.ArrayList; import java.util.List; /** * @author Veng Su [email protected] * @date 2018/4/29 10:39 */ public class Main { public static void main(String[] args) { // 测试堆内存溢出 List<Demo> demoList=new ArrayList<Demo>(); while (true){ demoList.add(new Demo()); } } }
抛出的异常:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:265) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231) at java.util.ArrayList.add(ArrayList.java:462) at Main.main(Main.java:13)
jvm参数
导出堆内存
-XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m
Java虚拟机的内存管理
运行时数据区
线程共享区
方法区
运行时常量池
Java堆
线程独立区
虚拟机栈
存放方法运行时所需的数据,称为栈帧
本地方法栈
为jvm调用到的native,即本地方法服务
程序计数器
记录当前线程执行到字节码的行号
程序计数器
- 如果线程执行的是Java代码,这个计数器记录的正在执行的虚拟机字节码指令的地址,如果正在执行的native方法,这个计数器的值为undefined
- 此区域是唯一一个在Java虚拟机规范中没有规定任何的OutOfMemoryError的情况的区域
Java虚拟机栈
- 这个描述的是Java方法执行的动态内存模型
- 栈帧:每个方法执行都会创建一个栈帧,伴随方法从创建到执行完成,用于存储局部变量表,操作数栈,动态链接,方法出口等
- 局部变量表:存放编译期已知的各种基本数据类型,引用类型,returnAddress类型 局部变量表的内存空间在编译期完成分配,在进入一个方法时,这个方法需要在帧分配多少内存是固定的,在方法运行期间是不会改变的
- 虚拟机栈的大小 可能存在StackOverFlowError OutOfMemoryError内存不足,申请不到内存空间了
本地方法栈
Java虚拟机栈为虚拟机执行Java方法服务
本地方法栈为虚拟机执行native方法服务
Java堆
存放对象实例
垃圾搜集器管理的主要区域
新生代,老年代,Eden空间
申请不到空间同样抛出outofmemoryerror
方法区
存储虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码等数据
类信息:
类的版本
字段
方法
接口
方法区和永久代 Hotspot使用永久代实现方法区,两者不等价
垃圾回收在方法区的行为
异常的定义
申请空间失败抛出outofmemoryerror
运行时常量池
常量池相当于一个hashset存放这写常量,
而new 出来的实例肯定是放到堆内存中去
运行时常量和字节码常量的区别,运行时创建的常量和编译期创建的常量的区别
直接内存
对象的创建

给对象分配内存的方法
- 指针碰撞
- 空闲列表
可能会出现线程安全性问题
如何解决
线程同步 缺点:效率低
本地分配缓冲
对象的结构
- header (对象头) 自身运行时数据(MarkWord) 哈希值 GC分代年龄 锁状态标志 线程持有的锁 偏向线程ID 偏向时间戳 类型指针、

- instanceData
- Padding 占位符填充8的整数倍的作用
对象的访问定位
- 使用句柄 定位句柄池,在找到对象地址
- 直接指针 直接找到对象地址 性能高 Hotspot使用直接地址定位
其他虚拟机
Sun hotshot
Bea JRockit
IBM J9
虚拟机的发展
- sun classic vm
- 世界山第一个商用虚拟机
- 只能使用纯解释器的方式执行java代码
- exact vm
- exact memory management 准确试内存管理
- 编译器和解释器混合工作以及两级及时编译器
- 只在 Solaris平台发布
- hotspot vm
- kvm(kilobyte)
- JRocket BEA 世界上最快的虚拟机 专注服务器端的应用 优势 垃圾搜集器 MissionControll服务套件 寻找运行时的内存泄露的问题
- J9 IBM Technology for Java virtual machine
- Azul vm
- Liquid vm
- Dalvik vm 不是Java虚拟机 寄存器架构,非栈架构 Google的
- Microsoft jvm 只能运行在windows平台下
- taobaovm 深度定制
垃圾回收
如何判定对象为垃圾对象
- 引用计数法 在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就加1,当引用失效的时候,计数器的值就减1 -verbose :gc -XX:+PrintGCDetails 打印垃圾回收的信息
- 可达性分析法 GCRoot对象
- 虚拟机栈
- 方法区类属性所引用的对象
- 方法区常量所引用的对象
- 本地方法栈所引用的对象
如何回收
- 回收策略
- 标记-清除算法 效率问题 空间问题
- 复制算法 堆
- 新生代
- Eden 伊甸园
- survivor 存活区
- Tenured Gen
- 老年代
虚拟机栈 本地方法栈 程序计数器
- 新生代
- 标记-整理-清除算法 针对老年代
- 分代收集算法
- 垃圾回收器
- Serial 单线程
- Parnew
- parallel scanvenge收集器 -XX:MaxGFPauseMillis 垃圾收集器停顿时间1 -XX:CGTimeRatio 吞吐量大小 复制算法(新生代收集器) 多线程收集器 达到可控吞吐量 吞吐量:CPU运行代码的时间与CPU消耗的总时间的比值
- CMS收集器 current Mark sweep
- 工作过程
- 初始标记
- 并发标记
- 重新标记
- 并发清理
- 优点
- 并发收集
- 低停顿
- 缺点
- 占用大量CPU资源
- 无法处理浮动垃圾
- 出现current mode failure
- 空间碎片
- 工作过程
- G1
内存分配

只要电脑运行内存大于2g,CPU核心是多核, 默认是ServerVM
可以看到我们的虚拟机是HotSpot
内存分配策略
- 优先分配到新生代的Eden区 VM option -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8
- 大对象直接进入到老年代 指定进入老年代的对象的内存大小 -XX:PretenureSizeThreshold
- 长期存活的对象分配到老年代 -XX:MaxTenuringThreshold 具有年龄计数器。每次回收时存活,年龄加1.到达阈值就进入老年代中
- 空间分配担保 如果内存空间不足,向担保借; -XX:(+/-)HandlePromotionFailure
- 逃逸分析和栈上分配 通过逃逸分析,分析出没有逃逸的对象,直接在栈上分配空间。 什么是逃逸分析? 分析对象的作用域。如果对象只有在方法体内有效,则判定为没有逃逸。否则,为逃逸对象
- 动态对象年龄判断
虚拟机工具
- JPS JAVA PROCESS STATUS JPS 名称: jps – Java Virtual Machine Process Status Tool 命令用法: jps options hostid options:命令选项,用来对输出格式进行控制 hostid:指定特定主机,可以是ip地址和域名, 也可以指定具体协议,端口。 功能描述: jps是用于查看有权访问的hotspot虚拟机的进程. 当未指定hostid时,默认查看本机jvm进程,否者查看指定的hostid机器上的jvm进程,此时hostid所指机器必须开启jstatd服务。 jps可以列出jvm进程lvmid,主类类名,main函数参数, jvm参数,jar名称等信息。
- 没添加option的时候,默认列出VM标示符号和简单的class或jar名称
- -p :仅仅显示VM 标示,不显示jar,class, main参数等信息.
- -m:输出主函数传入的参数. 下的hello 就是在执行程序时从命令行输入的参数
- -l: 输出应用程序主类完整package名称或jar完整名称.
- -v: 列出jvm参数, -Xms20m -Xmx50m是启动程序指定的jvm参数
- -V: 输出通过.hotsportrc或-XX:Flags=指定的jvm参数
- -Joption:传递参数到javac 调用的java lancher.
- JSTAT jstat命令可以类装载,内存,垃圾收集,jit编译。命令的格式如下: jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
- 类加载统计
jstat -class
Loaded:加载class的数量 Bytes:所占用空间大小 Unloaded:未加载数量 Bytes:未加载占用空间 Time:时间 - 编译统计
jstat -compiler
Compiled:编译数量。 Failed:失败数量 Invalid:不可用数量 Time:时间 FailedType:失败类型 FailedMethod:失败的方法 - 垃圾回收统计
jstat -gccapacity
S0C:第一个幸存区的大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 EC:伊甸园区的大小 EU:伊甸园区的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法区大小 MU:方法区使用大小 CCSC:压缩类空间大小 CCSU:压缩类空间使用大小 YGC:年轻代垃圾回收次数 YGCT:年轻代垃圾回收消耗时间 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间 - 新生代垃圾回收统计
jstat -gcnew
S0C:第一个幸存区大小 S1C:第二个幸存区的大小 S0U:第一个幸存区的使用大小 S1U:第二个幸存区的使用大小 TT:对象在新生代存活的次数 MTT:对象在新生代存活的最大次数 DSS:期望的幸存区大小 EC:伊甸园区的大小 EU:伊甸园区的使用大小 YGC:年轻代垃圾回收次数 YGCT:年轻代垃圾回收消耗时间 - 新生代内存统计
jstat -gcnewcapacity
NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:当前新生代容量 S0CMX:最大幸存1区大小 S0C:当前幸存1区大小 S1CMX:最大幸存2区大小 S1C:当前幸存2区大小 ECMX:最大伊甸园区大小 EC:当前伊甸园区大小 YGC:年轻代垃圾回收次数 FGC:老年代回收次数 - 老年代垃圾回收统计
jstat -gcold
MC:方法区大小 MU:方法区使用大小 CCSC:压缩类空间大小 CCSU:压缩类空间使用大小 OC:老年代大小 OU:老年代使用大小 YGC:年轻代垃圾回收次数 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间 - 老年代内存统计
jstat -gcoldcapacity
OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:当前老年代大小 OC:老年代大小 YGC:年轻代垃圾回收次数 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间 - 元数据空间统计
jstat -gcmetacapacity
MCMN:最小元数据容量 MCMX:最大元数据容量 MC:当前元数据空间大小 CCSMN:最小压缩类空间大小 CCSMX:最大压缩类空间大小 CCSC:当前压缩类空间大小 YGC:年轻代垃圾回收次数 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间 - 总结垃圾回收统计
jstat -gcutil
S0:幸存1区当前使用比例 S1:幸存2区当前使用比例 E:伊甸园区使用比例 O:老年代使用比例 M:元数据区使用比例 CCS:压缩使用比例 YGC:年轻代垃圾回收次数 FGC:老年代垃圾回收次数 FGCT:老年代垃圾回收消耗时间 GCT:垃圾回收消耗总时间 - JVM编译方法统计
jstat -printcompilation
Compiled:最近编译方法的数量 Size:最近编译方法的字节码数量 Type:最近编译方法的编译类型。 Method:方法名标识。
- 类加载统计
- JINFO jinfo是jdk自带的命令,用来查看jvm的配置参数。通常会先使用jps查看java进程的id,然后使用jinfo查看指定pid的jvm信息 查看jvm的参数
jinfo -flags process_id
查看java系统参数jinfo -sysprops process_id
- JMAP JVM Memory Map命令用于生成heap dump文件,如果不使用这个命令,还可以使用-
XX:+HeapDumpOnOutOfMemoryError
参数来让虚拟机出现OOM的时候自动生成dump文件。 参数 option:选项参数,不可同时使用多个选项参数 pid:java进程id,命令ps -ef | grep java获取 executable:产生核心dump的java可执行文件 core:需要打印配置信息的核心文件 remote-hostname-or-ip:远程调试的主机名或ip server-id:可选的唯一id,如果相同的远程主机上运行了多台调试服务器,用此选项参数标识服务器 options参数 heap : 显示Java堆详细信息 histo : 显示堆中对象的统计信息 permstat :Java堆内存的永久保存区域的类加载器的统计信息 finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象 dump : 生成堆转储快照 F : 当-dump没有响应时,强制生成dump快照 -dump dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名 -heap 打印heap的概要信息,GC使用的算法,heap的配置及使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况 -finalizerinfo 打印等待回收的对象信息, -histo 打印堆的对象统计,包括对象数、内存大小等等。jmap -histo:live 这个命令执行,JVM会先触发gc,然后再统计信息 jmap -histo:live 24971 | grep com.yuhuo 查询类名包含com.yuhuo的信息 jmap -histo:live 24971 | grep com.yuhuo > histo.txt 保存信息到histo.txt文件 -permstat 打印Java堆内存的永久区的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。 -F 强制模式。如果指定的pid没有响应,请使用jmap -dump或jmap -histo选项。此模式下,不支持live子选项。 - JHAT JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。在此要注意,一般不会直接在服务器上进行分析,因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析。【内存分析】 参数 -J< flag > 因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数. 例如, -J-Xmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数,则传入多个 -Jxxxxxx. -stack false|true 关闭对象分配调用栈跟踪(tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用. 则必须将此标志设置为 false. 默认值为 true. -refs false|true 关闭对象引用跟踪(tracking of references to objects)。 默认值为 true. 默认情况下, 返回的指针是指向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references), 会统计/计算堆中的所有对象。 -port port-number 设置 jhat HTTP server 的端口号. 默认值 7000。 -exclude exclude-file 指定对象查询时需要排除的数据成员列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时, 引用路径涉及 java.lang.String.value 的都会被排除。 -baseline exclude-file 指定一个基准堆转储(baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的(marked as not being new). 其他对象被标记为新的(new). 在比较两个不同的堆转储时很有用。 -debug int 设置 debug 级别. 0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息。 -version 启动后只显示版本信息就退出。
- JSTACK
- JCONSOLE
性能调优
- 常用思路
- 优化sql
- 监控CPU
- 监控内存
- FULL GC 垃圾收集时间过长
- 解决方案
- 调整堆内存大小
- 解决方案
- FULL GC 垃圾收集时间过长
问题:
- 不定期出现内存溢出,把堆内存加大也没用,导出内存信息没有任何信息.内存监控,也正常
处理思路:
- 控制变量法
- 硬件环境
- CPU
- 内存
- 软件环境
- 操作系统
- Java版本
- 容器
- 代码问题
- 硬件环境