JVM
相关参数设置
-Xms
- 初始分配的大小,默认为物理内存的1/64,可以指定跟Xmx相同大,避免重新分配内存消耗时间
-Xmx
- 最大分配内存,默认为物理内存的1/4
-Xmn
- 尹甸区+幸存区
-Xss
- 线程栈的大小,这个值分配的越大,当前程序可同时创建的线程数越小
Xmx-Xmn就是老年代的大小
获取内存的默认大小
- Runtime.getRuntime().totalMemory()
Runtime.getRuntime().maxMemory()
XX参数
-
-XX:+或者-某个属性值
- +表示开启,-表示关闭
-XX:属性值key=属性值value
JVM的堆内存示意图
所有的创建对象都在尹甸区完成
在幸存0区和幸存1区经过15次垃圾回收会进入养老区,养老区内存满后会进行FullGC
若养老区执行FullGC之后依然无法对对象进行存储,则抛出OOM Error错误
1、永久区(1.7/需要分配) 元数据区(1.8/不需要分配,直接内存)
2、存储项目运行所必须依赖的环境,如Jdk自身所携带的元数据,引入的第三方jar包
3、不会被垃圾回收机制回收掉,直到服务停止才会结束
造成堆内存不足的原因
java虚拟机内存设置不够
- 可以通过指定-Xms,-Xmx参数来指定
代码中创建的大量对象,并且长期不能被垃圾回收器处理(存在引用)
堆内存/栈内存,出现的问题案例
堆内存不足经典案例(OutofMemerryError)
- 1、分页查询,如果没有加分页参数,一次性查询1000万条数据
2、频繁操作String,没用StringBuilder/StringBuffer
3、读写的stream流,只管开辟,不管回收
栈内存不足经典案例(StackOverFlowError)
- 无限递归
解决方案
调内存
调垃圾回收机制
-
算法
-
标记清除算法
- 标记无用对象,进行清除回收
- 碎片化
-
复制算法
- 按照容量大小分成两个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块内存上,然后再把已用的内存空间一次清理掉
- 浪费空间
-
标记整理算法
- 标记无用对象,让所有存活的对象向一端移动,清除掉边界以外的内存
增量算法
-
分代收集算法
- 根据对象的存活周期将内存分成几块,一般新生代采用复制算法,老年代采用标记整理算法
老年代一般用标记清除、标记整理,年轻代一般采用复制
-
-
4类7种
-
方式
-
串行GC
- 只有一个线程,执行垃圾回收程序停止的时间长
-
并行GC
- 多个线程执行垃圾回收,适用于吞吐量系统
-
并发GC
- 系统和垃圾回收一起执行,系统不会停止
G1垃圾收集器
-
-
7种
- 新生代GC之Serial收集器
- 新生代GC之ParNew收集器
- 新生代GC之Parallel收集器
- 老年代ParallelOld收集器
- 老年代SerialOld收集器
- 老年代CMS收集器
-
-
判断对象是否可以被垃圾回收
- 引用计数器法:为每一个对象创建一个引用计数,有对象引用时计算器+1,引用被释放时计数-1,当计数器为0
时可以被回收,缺点:不能解决循环引用的问题。 - 可达性分析算法:从GC Roots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC roots没有任何引用链相连时,则证明此对象时可以被回收的。
- 引用计数器法:为每一个对象创建一个引用计数,有对象引用时计算器+1,引用被释放时计数-1,当计数器为0
示例
问题描述
- 在某次版本迭代过程中,我们自测通过,联调OK,测试同学在进行全面压测的时候,突然程序假死或者响应卡顿,日志不在打印,过了一会,java服务宕机,问题能够复现
解决
-
其他同学
- 分析堆内存的文件报告,带上 -XX:+HeapDumpOnOutOfMemoryError: 指定文件输出路径及文件名称参数,如果出现相应的内存溢出,则会在目录下生产一个dump文件,将这个文件使用Memory Analyse Tool工具打开就可以看到当前dump 内存空间的分析内容
-
me
- 1、首先,我dk给我们提供的工具 jstat -gc 进程号 1000(每隔多少毫秒打印一次)
jstat -gcutil 10777(进程号) 1000
2、查看伊甸园以及老年代的内存使用情况,我发现每次ygc以后,老年代内存使用率就会提升,然后我对这个java 进程进行持续关注,执行全面压测脚本,发现在经过短暂的时间之后,老年代内存使用情况占比达到90%以上,然后疯狂FullGC,非常频繁。一秒钟2-5次。非常可怕。然后一段时间之后,程序假死,日志不再打印,持续几秒,进程自动终止。服务宕机。
3、我直接给这个服务 分配 2G的最大内存,给新生代600m,1.4G全留给老年代。
然后我怀疑是内存给小了,然后我直接跳转参数 -Xmx=2G,重启服务倒要看看,你tm这个老年代你到底能用多少,然后持续全面压测
1)、如果给多少用多少,那么肯定是代码有问题,再去分析内存dump文件定位代码问题不迟;
2)、如果最终老年代内存使用,定格在某个值发送不动了,那么证明确实是我给内存分配小了;然后根据它的内存最终占有情况,给他分配一个合理的值,即ok,解决问题!老年代 稳定在1.2G 不再继续增长,FGC频率不再飙升,程序压测无问题,上线。问题完美收官。然后告诉其它小伙伴,你们别瞎鸡儿折腾了!
- 1、首先,我dk给我们提供的工具 jstat -gc 进程号 1000(每隔多少毫秒打印一次)
jdk分析工具
linux
jstat -gc 进程号 间隔毫秒数
jstat -gcutil 进程号 间隔毫秒数
jmap -histo:live 进程号 直接在页面打印内存中对象的情况
jmap -dump:format=b,file=fileName.hprof 进程号(生成dump文件)
windows
- jconsole 可视化工具 分析 java进程 内存使用情况,线程开辟情况,gc情况....
- jvisualVM★★★★★★,图形界面的,多功能的检测工具,检测大对象。
- jmap -heap 进程号
类加载
类加载过程
- 加载:.class文件加载到内存
- 验证:保证class文件的正确性
- 准备:为类的静态变量分配内存,赋默认初始值
- 解析:将类的符号引用转换成直接引用
- 初始化:为类的静态成员变量赋正确的值
类加载器的种类
- 1、Bootstrp,用c语言写的
- 2、ExtClassLoader,父加载器是Bootstrp
- 3、AppClassLoader,父加载器是ExtClassLoader
双亲委托机制的工作流程
- 1、当AppClassLoader收到一个类加载的请求时,它不会加载该类,而是委派给父类ExtClassLoader
2、当ExtClassLoader收到一个类加载的请求时,它也不会加载该类,而是委派给父类Bootstrp
3、Bootstrp加载失败,就让ExtClassLoader去加载
4、ExtClassLoader加载失败,就让AppClassLoader去加载
5、AppClassLoader加载失败,就让自定义类加载器加载(自定义类加载器--继承java.lang.ClassLoader类,重写写findClass(name)方法)
6、自定义类加载器加载失败,抛出异常 - 好处:避免类的重新加载
JVM的内存结构
本地方法栈
-
被native修饰
- 融合不同语言编写的接口为java所用
调用操作系统相关的资源
栈
程序计数器
- 每一个线程都有一个程序技术器,就是一个指针,指向方法区中的字节码(下一个要执行的指令代码),由执行引擎执行,非常小的内存空间
堆
- 堆内存开辟出来的地址值都是16进制的,以0x开头
- 堆内存的数据都有默认的初始值
方法区
-
被所有线程锁共享,定义的方法信息都保存在该区域,也就是元空间
- 静态变量、常量、类信息
- 方法区中的静态变量会有默认的初始值,非静态变量没有
字节码文件加载时所进入的内存
分为静态区和非静态区
静态的成员变量会有默认的初始值,非静态的成员变量没有默认的初始值(要加载进堆内存中才有默认的初始值)
GC种类
普通GC(Minor GC)新生代
全局GC(Major GC)老年代
JVM在垃圾回收,大部分指新生代
引用类别
软引用
- 软引用维护一些可有可无的对象,在内存空间足够的情况下,软引用对象不会被回收
弱引用
- 弱引用相比于软引用,更加没用,拥有更短的生命周期,无论内存是否充足,都会被回收
强引用
-
就算抛出OOM异常,也不会对该对象回收
- 造成OOM的主要原因之一
虚引用
-
形同虚设
- 任何时候都可能会被回收
内存溢出与内存泄露
内存溢出
-
申请内存的时候没有给到足够的空间,产生内存溢出
- 堆内存溢出,栈内存溢出
内存泄露
-
分配出去的内存不再使用,但是无法被回收
- 长生命周期的对象持有短周期对象的引用,因为长生命周期持有他的引用而不能被回收