【JVM 知识体系框架总结】
jvm 内存分布
- 线程共享数据区:
方法区->类信息,静态变量
堆->数组对象 - 线程隔离区
虚拟机栈-> 方法
本地方法栈->本地方法库 native - 堆、程序计数器
- jvm 运行数据
程序计数器
线程隔离 ,比较小的内存空间,当前线程所执行的字节码的行号
线程是一个独立的执行单元,由 cpu执行
唯一没有 oom 的地方,由虚拟机维护,所以不会出现 oom
虚拟机栈
执行的是java方法
方法的调用就是栈帧入虚拟机栈的过程
栈帧:局部变量表(变量) 、操作数栈(存放a+b的结果 )、 动态链接(对对象引用的地址),方法出口(return的值)
线程请求的栈深度大于虚拟机所允许的深度*error
本地方法栈
执行的是 native 方法的一块 java内存区域,一样有 栈帧
hotspot将 java 虚拟机栈和本地方法栈合二为一
jvm标准是 java 虚拟机栈和本地方法栈分开
堆
java内存中存放对象实例的区域,几乎所有的对象实例都在这里分配
所有线程共享
新生代、老年代
jmap -heap pid;
方法区
各个线程共享的内存区域
存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据
hotspot用永久代实现方法区(让垃圾回收器可以管理方法区),对常量池的回收和卸载
方法区会抛出 oom,当他无法满足内存分配需求时
运行时常量池
运行时常量池是方法区的一部分,class 中除了字段、方法、接口的 常量池,存放编译器生成的字面量和符号引用,这部分内容由类加载后进入方法区的运行时常量池中存放。
stringtable是hashset结构
方法区的一部分,受到方法区的限制,依然会 oom
java 对象创建过程
执行
对象属性的值->实例数据 引用计数器 标记-清除 标记-复制 复制操作增多 2. 额外 50%空间浪费 3. 经常需要额外的空间分配担保 4.可能老年代中对象 100% 存活 步骤: serial收集器的多线程版本 新生代收集器,复制算法,并行的多线程收集器 是serial 收集器的老年代版本 是 parallel scavenge收集器的老年代版本 获取最短回收停顿时间为目标的收集器,采用“标记-清除”算法,用于互联网、b/s 系统重视响应的系统 步骤: 缺点 : 面向服务端应用的垃圾收集器 java 堆分布图 对象分配的规则: 大对象是指需要大量连续内存空间的 java 对象,最典型的大对象是是那种很长的字符串以及数组 逃逸分析:分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,称为方法逃逸。甚至还有可能被外部线程访问到,比如赋值给类变量或其他线程中访问的实例变量,称为线程逃逸。 基于 jmx 的可视化监视、管理工具 互联网开发流程 minor gc:当 eden 区满,触发 minor gc
1) 指针碰撞(内存比较整齐)
步骤:1. 分配内存 2. 移动指针,非原子步骤可能出现并发问题,java虚拟机采用 cas 配上失败重试的方式保证更新操作的原子性
2)空闲列表(内存比较乱)
存储堆内存空闲地址
步骤:1.分配内存 2. 修改空闲列表地址 非原子步骤可能出现并发问题,java虚拟机采用 cas 配上失败重试的方式保证更新操作的原子性java 对象内存布局
对象头 64 位机器存 64 位,32 位机器存 32 位,8 的倍数java 对象的访问
对比:垃圾回收算法
当对象实例分配给一个变量时,该变量计数设置为 1,当任何其他变量被赋值为这个对象的引用的时,计数+1 (a =b,则b的引用对象实例计数器+1),当一个对象实例的某个引用超过了生命周期(方法执行完)或者被设置为一个新值,则该对象的实例引用计数器 -1
无法解决循环引用
可达性分析
gc root (虚拟机栈中的引用的对象、本地方法栈中引用的对象、方法区静态属性引用的对象、方法区常量引用的对象)
标记需要回收的对象,在标记完成后统一回收
不足:
1.效率问题,标记清除 2 个过程效率都不高
2.空间问题,标记清除后产生大量不连续的内存碎片,碎片过多当程序需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发一次垃圾回收动作
内存块 a存活的对象复制到内存块 b (survivor to)里,然后将内存块a (eden + survivor from)清空,
只有少部分对象移动,更多的对象是要被回收的
eden:survivor from:survivor to=8:1:1
98%对象“朝生夕亡”,新生代可用内存容量 90%(80%+10%),98%的对象可回收是一般情况,当小于 90%的对象被回收的时候(10%以上的对象存活时),则 survivor to 空间不够,则需要依赖老年代进行分配担保
老年代不适合复制算法
serial 收集器
单线程垃圾回收器,用户线程到安全点先暂定,然后 gc 线程单线程串行进行,等 gc 线程回收完,然后用户线程再继续
特点:stop the world
场景:桌面应用 (gc时间短)
用于新生代,client 端parnew 收集器
用于新生代,唯一能和cms 收集器配合工作,运行在 server 模式下
-xx:parallelgcthreads 限制垃圾收集器线程数 = cpu 核数(过多会导致上下文切换消耗)
并行:多条垃圾收集线程并行工作,用户线程仍然处于等待状态
并发:用户线程与垃圾收集器同时执行,用户线程和垃圾线程在不同 cpu 上执行parallel scavenge 收集器
关注吞吐量优先的收集器(吞吐量 = cpu 运行用户代码执行时间/cpu 执行总时间 ,比如: 99%时间执行用户线程,1%时间回收垃圾,这时吞吐量为 99%)高吞吐量可以高效率利用 cpu 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多的交互任务
cms 关注缩短垃圾回收停顿时间,适合与用户交互的程序,良好的响应速度能提升用户体验
-xx:maxgcpausemillis 参数 gc 停顿时间,参数过小会频繁 gc
-xx:gctimeratio 参数,默认 99%(用户线程时间占 cpu 总时间的 99%)serial old 收集器
单线程老年代收集器,采用“标记-整理”算法parallel old 收集器
多线程老年代收集器,采用“标记-整理”算法cms 收集器
g1 收集器
region->remembered set (解决 循环引用 )
检查 reference (程序对reference类型写操作,检查 reference 引用类型)
步骤:
优势:堆内存分配
大对象分配
-xx:pretenuresizethreshold 设置大于该值的对象直接分配在老年代,避免在 eden 区以及 2 个survivior区之间发生大量的内存复制逃逸分析和栈上分配
栈上分配:把方法中的变量和对象直接分配到栈上,方法执行完后自动销毁,不需要垃圾回收介入,从而提高系统性能
-xx:+doescapeanalysis 开启逃逸分析(jdk1.8默认开启 )
-xx:-doescapeanalysis 关闭逃逸分析命令
jmap -heap 9366;
jmap -histo 9366 | more; 显示堆中对象统计
jmap -dump:format=b,file=/users/mousycoder/desktop/a.bin 9366 生成dump文件
-xmx20m -xx:+heapdumponoutofmemoryerror -xx:heapdumppath=/users/mousycoder/desktop/
jhat /users/mousycoder/desktop/java_pid9783.hprof 图形分析heap
select s.tostring() from java.lang.string s where (s.value != null && s.value.length > 1000 )
shutdownhook 在关闭之前执行的任务
jstack -l -f pid 强制输出线程状态
jconsole
开启 jmx 端口
nohup java -xms800m -xmx800m -djava.rmi.server.hostname=192.168.1.250 -dcom.sun.management.jmx
remote.port=1111 -dcom.sun.management.jmxremote.ssl=false -dcom.sun.management.jmxremote.authenticate=false -jar hc-charging-
server.jar &
jconsole 内存分析思考过程fullgc
fullgc:
建议:互联网问题
解决方法:list.contain->set.contain->布隆过滤器(用户量大和用户量小系统解决方案不一样)
解决方法:jstack 以及 new thread带上名称
fullgc 出现正常频率为一天 1~2 次
解决方案:jmap , heap dump on oom + jhat
heap堆使用率很低,但是有 oom 以及 full gc
解决方法:btrace学习秘籍
下一篇: 用Agent+ASP技术制作语音聊天室