1. JVM的内存区域
JVM将内存分为五块区域分别是程序计数器、虚拟机栈、本地方法栈、堆和方法区
1.1 程序计数器
定义:
唯一一个不会发生内存溢出异常的内存区域.
保存的是当前JVM解释器执行命令的行号
最小的一块区域
1.2. 虚拟机栈
定义: 保存的是局部变量表等信息
局部变量表
-
局部变量表
- reference
- 基本类型变量
- returnAddress
1.3. 本地方法栈
和虚拟机栈差不多,保存的是本地方法的信息,在hotspot虚拟机中将虚拟机栈和本地方法栈合二为一
1.4. 堆
定义:
最大的一块内存区域.
存放的是对象的实例数据
JVM启动时创建
分为新生代、老年代
-
新生代
-
老年代
-
元空间
和永久代差不多,将class文件的元数据保存到元空间,元空间使用的是本地内存
1.5. 方法区
-
运行时常量池
定义: 存放的是class文件的常量池的数据
-
常量
-
静态变量
-
JIT及时编译后的代码
-
类信息
2. 垃圾收集算法&垃圾收集器
介绍常用的垃圾收集算法和垃圾收集器
2.1 判断对象存活的算法
-
引用计数
定义: 只作为了解,没有虚拟机使用这种方式.
有对象引用就+1,对象不引用就-1,计数器为0就回收对象.
缺点: 无法解决对象循环引用问题
-
可达性分析
定义: 只要GCRoots连接对象的实例,对象就不能被回收
- 虚拟机栈引用的对象
- 方法区静态属性引用的对象
- 方法区常量引用的对象
- 本地方法区引用的 对象
2.2 垃圾收集算法
-
标记清除
定义: 用在老年代,收集的空间是不连续的,大对象无法分配时,可能会提前触发一次full GC.
-
复制
定义: 用在新生代,有一个Eden区和两个survivor区, 按8:1的比例,每次minor gc的时候会把Eden和一个survivor区的存活对象分配到另一个survivor区中,如果survivor区的空间不够,就直接分配到老年代
-
标记整理
定义: 和标记清除相比,就是整理的空间是连续的
-
分代收集
定义: 新生代用复制算法,老年代用标记清除或标记整理算法
2.3 垃圾收集器
-
新生代
-
serial
- 单线程
- 进行垃圾收集,必须暂停其他所有工作线程,"Stop The World"
- 简单高效,Client模式下首选
-
parNew
1.除了使用多条线程进行垃圾收集之外,其他与Serial相同
- 可以与CMS配合
-
parallel scavenger
- 吞吐量收集器
- 最大停顿时间参数(调低停顿时间,会增加停顿频率)
- 停顿比率参数(0-100)
- GC自适应的调节策略
-
-
老年代
-
serial old
- 在CMS并发收集发生Concurrent Mode Failure时,作为备胎使用
-
cms
-
停顿段,并发收集
-
对CPU资源敏感
CPU不足4个时,影响程序的性能
-
无法清除浮动垃圾
浮动垃圾: 和用户线程并行执行产生的新垃圾.
这一部分垃圾无法回收,因为cms和用户线程行执行,所以需要留一部分内存给cms使用,老年代空间使用92%以上时启动cms,如果空间不够会报"Concurrent mode Failure",这时候会启动备用方案serial old进行垃圾回收.
-
标记清除算法
收集的空间是不完整的
-
-
parallel scavenger old
parallel scavenger的老年代版本
-
-
G1
特点:
充分利用计算机资源
分代收集
空间整合
建立可预测的停顿通过remember set解决全局扫描region对象互相引用问题. 在reference对象进行写操作的时候 ,发生一个写中断,如果有跨region引用就通过cardTable把信息记录到region的remember set中,在垃圾回收的时候通过枚举根节点和remember set就可以不会遗漏要回收的垃圾对象了.
回收的四个步骤:
初始标记
并发标记
最终标记
筛选回收找到所有GCRoots,修改ntmd的值,在进行并发标记的时候,保证用户线程可以将对象分配在正确可用的region中
用户线程和垃圾收集线程同时执行
同时执行产生变动的那部分,记录到remember set logs 中,并把这个logs的内容放到 remember set中
在用户规定的时间里,回收最大价值的region的垃圾-
特点
- 充分利用计算机资源
- 分代收集
- 空间整合
- 建立可预测的停顿
-
垃圾收集步骤
-
初始标记
找到所有GCRoots,修改ntmd的值,在进行并发标记的时候,保证用户线程可以将对象分配在正确可用的region中
-
并发标记
用户线程和垃圾收集线程同时执行
-
最终标记
同时执行产生变动的那部分,记录到remember set logs 中,并把这个logs的内容放到 remember set中
-
筛选回收
在用户规定的时间里,回收最大价值的region的垃圾
-
-
2.3.1 垃圾收集器配合使用图
2.4 内存分配与回收策略
- 对象优先在eden分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判定
同年龄的对象的大小超过整个Survivor区的一半,大于等于这个年龄的对象都会被放入老年代.
- 空间分配担保流程图
之所以进行这么多选择,是为了尽全力避免Full GC.
3.1 实战
解决问题
-
堆溢出
报错: java.lang.OutOfMemoryError: Java heap space
原因: 一般都是应该回收的对象没有回收造成的内存溢出.
解决: 看是否有应该回收的对象没有回收,如果没有尝试扩大堆内存
-
栈溢出
报错: StackOverflowError
原因: 递归调用太多,一般不会出现这种错误
解决: 1. 调大栈深度 2. 从代码入手,改正错误的调用方法
-
方法区逸出
Java开始使用元空间替换永久代.
元空间不在虚拟机中,使用的是本地内存.
解决元空间导致的内存溢出的方法:
先看是不是代码有错误,导致元空间占用过多的内存
如果不是,那就只能扩展本机内存了 -
本机直接内存溢出
报错: java.lang.OutOfMemoryError: null
at sun.misc.Unsafe.allocateMemory(Native Method) ~[na:1.8.0_201]解决:
使用参数-XX:MaxDirectMemorySize=10M
调大直接内存大小
-
gc日志分析
设置参数: -Xloggc:D:/logs/admin_client.log
需要提前建好目录
- admin_client.log
设置虚拟机参数
-
-Xmx3550m 最大堆大小
-
-Xms3550m 最小堆大小
-
-Xmn2g 新生代
-
-Xss128k 虚拟机栈大小
设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-
设置垃圾回收器
在Java8中的测试,默认垃圾收集器Parallel GC是最快的,可以不进行更换
-
-XX:+HeapDumpOnOutOfMemoryError自动生成堆转储文件的参数
-XX:+HeapDumpOnOutOfMemoryError
报错: Failed to write core dump. Minidumps are not enabled by default on client versions
加入参数: -XX:+CreateMinidumpOnCrash
-Xms200m -Xmx200m -XX:+HeapDumpOnOutOfMemoryError -XX:+CreateMinidumpOnCrash
生成的hprof文件可以使用Jprofiler直接打开