内存的分析和工具
内存分析之线程栈分析
JVM线程模型
JVM线程分类
JVM 内部线程主要分为以下几种:
- VM 线程:单例的 VMThread 对象,负责执行 VM 操 作
- 定时任务线程:单例的 WatcherThread 对象, 模拟在 VM 中执行定时操作的计时器中断
- GC 线程:垃圾收集器中,用于支持并行和并发垃圾回收的线程
- 编译器线程: 将字节码编译为本地机器代码(对于HotSpot会使用JIT编译成本地机器代码)
- 信号分发线程:等待进程指示的信号,并将其分配给Java 级别的信号处理方法
如何实现线程暂停-安全点
安全点:
- 方法代码中被植入的安全点检测入口;
- 线程处于安全点状态:线程暂停执行,这个时候线程栈不再发生改变;
- JVM 的安全点状态:所有线程都处于安全点状态
通过安全点,可以改变线程的暂停或执行,当需要暂停一个线程时,就可以通过改变线程的众多安全点状态,当线程执行到临近安全点时,就知道自己需要暂停
获得JVM线程栈信息
JVM 支持多种方式来进行线程转储:
- JDK 工具, 包括: jstack 工具, jcmd 工具,jconsole,jvisualvm, Java Mission Control 等;
- Shell 命令或者系统控制台,比如 Linux 的 kill -3,Windows 的 Ctrl + Break 等;
- JMX 技术, 主要是使用 ThreadMxBean
线程栈分析工具
获得JVM线程栈信息以后,可以通过fastthread 线程分析工具,分析当前的所有线程执行状态
内存分析之堆内存分析
一个class对象内存中的实际大小
- 在堆内存中,一个class对象的内存结构大致可以分为对象头 + 对象体 + 外部对齐部分
- 通常JVM分为32位和64位,这是物理机的限制导致的,这就意味着,地址寻址、数据存储都是以64位为基准,所以当堆内存中存储的数据不满足这个固定位数时就会填充空位使其满足
- 对齐方式分为两种:内部对齐padding、外部对齐alignment
内部对齐:针对单独的某个数据字段,使其所在class对象使用的堆内存大小满足物理机位数的整数倍
外部对其:针对的是整个class对象,使其使用的堆内存大小满足物理机位数的整数倍
-------------------------------------------------------------------------------------------
比如:一个 X 类的实例占用17字节的空间。 但是由于需要对齐(padding),JVM 分配的内存是8字节的整数倍,所以占用的空间不是17字节,而是24字节
- 在64位 JVM 中,对象头占据的空间是 12byte,但是以8字节对齐,所以一个空类的实例至少占用16字节
- 在32位 JVM 中,对象头占8个字节,以4字节的倍数对齐
- 通常在32位 JVM,以及内存小于 -Xmx32G 的64位JVM 上(默认开启指针压缩),一个引用(class指针)占的内存默认是4个字节,因此,64位 JVM 一般需要多消耗堆内存
对象头中的标记字
容易浪费堆内存的class对象
- class对象中的包装类型属性比原生数据类型属性占用更多堆内存
比如:Integer占用16字节,而 int 部分占4个字节, 所以使用 Integer 比原生类型int 要多消耗 300% 的内存
- 相同容量的多维数组对象比一维数组对象更占用堆内存
比如:在二维数组 int[dim1][dim2] 中,每个嵌套的数组 int[dim2] 都是一个单独的 Object,会额外占用16字节的空间。当数组维度更大时,这种开销特别明显。int[128][2] 实例占用3600字节。 而 int[256] 实例则只占用1040字节。里面的有效存储空间是一样的,3600 比起1040多了246%的额外开销
- 于10字符以内的非空 String
String 对象的空间随着内部字符数组的增长而增长,String 类的对象有24个字节的额外开销,对于10字符以内的非空 String,这部分额外开销比起有效载荷(每个字符2字节 + 4 个字节的 length),多占用了100% 到 400% 的内存
常见内存问题
- OutOfMemoryError: Java heap space 创建新的对象时,堆内存中的空间不足以存放新创建的对象
1、新对象过大
2、超出预期的访问量/数据量:应用系统设计时,一般是有 “容量” 定义的,部署这么多机器,用来处理一定流量的数据/业务。 如果访问量突然飙升,超过预期的阈值,类似于时间坐标系中针尖形状的图谱。那么在峰值所在的时间段,程序很可能就会卡死、并触发 java.lang.OutOfMemoryError: Java heap space 错误
3、内存泄露(Memory leak):这也是一种经常出现的情形。由于代码中的某些隐蔽错误,导致系统占用的内存越来越多。如果某个方法/某段代码存在内存泄漏,每执行一次,就会(有更多的垃圾对象)占用更多的内存。随着运行时间的推移,泄漏的对象耗光了堆中的所有内存,那么 java.lang.OutOfMemoryError: Java heap space 错误就爆发了
- OutOfMemoryError: PermGen space/OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: PermGen space 的主要原因,是加载到内存中的类数量太多或体积太大,超过了 PermGen 区的大小
解决办法:
1、增大 PermGen/Metaspace :-XX:MaxPermSize=512m / -XX:MaxMetaspaceSize=512m
2、高版本 JVM 也可以:-XX:+CMSClassUnloadingEnabled
- OutOfMemoryError: Unable to create new native thread
java.lang.OutOfMemoryError: Unable to create new native thread 错误是程序创建的线程数量已达到上限值的异常信息
解决思路:
- 调整系统参数 ulimit -a,echo 120000 > /proc/sys/kernel/threads-max
- 降低 xss 等参数
- 调整代码,改变线程创建和使用方式(方法的调用层数过多导致当个线程栈溢出)