一、内存结构
[TOC]
1. 程序计数器
1. 定义: program counter register 程序计数器(寄存器)
2. 作用: 保存下一条字节码指令的地址
2. 虚拟机栈
2.1定义
定义:每个线程运行需要的空间
组成:由多个栈帧组成
栈帧:是方法需要的内存.
- 存放方法参数,局部变量,返回地址
- 1个方法的调用对应1个栈帧的入栈和出栈
- 每个线程只能由1个活动栈帧,对应着当前正在执行的方法
- idea中的frames即为栈帧
几个问题
nohup命令:nohup java cn.ustc.jvm.Demo1_16 & (让Java代码在后台运行)
top命令:监测进程对CPU,内存的占用情况,定位不到线程
ps命令:可以查看线程对cpu的占用情况
案例1: 定位cpu 占用过多-空循环
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack(JDK提供) 进程id
- 会列出所有线程,可以根据线程id 找到有问题的线程(nid),进一步定位到问题代码的源码行号
public static
public static void main(String[] args) { new Thread(null, () -> { System.out.println("1..."); while(true) { } }, "thread1").start();
- 会列出所有线程,可以根据线程id 找到有问题的线程(nid),进一步定位到问题代码的源码行号
案例2:Jstack排查死锁问题
- 先用nohup运行,后直接用jstack得出下图
3. 本地方法栈Native Method Stacks
- 什么叫本地方法?
- 通过 new 关键字,创建对象都会使用堆内存
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
4.2 堆内存溢出 java.lang.OutOfMemoryError:Java heap space
例:
- jps 工具:查看当前系统中有哪些 java 进程
- jmap 工具: 查看某一时刻堆内存占用情况 jmap -heap 进程id
- jconsole工具:图形界面的,多功能的监测工具,可以连续监测不仅堆内存的情况
jmap例:演示堆内存-分别看Eden Space这个区域used情况
public class public class Demo1_4 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);//用来在new 对象前敲jmap命令
byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
System.out.println("2...");
Thread.sleep(20000);
array = null;
System.gc();//显示垃圾回收
System.out.println("3...");
Thread.sleep(1000000L);
}
}
jconsole例:演示堆内存
- 仍然用上面的main方法
- 命令行输入jconsole->选择进程->可看到堆内存的动态变化情况
例:垃圾回收后,内存占用仍然很高,怎么用工具来排查?
jmap 查看进程的新生代和老年代内存使用情况
用jconsole 对进程执行GC,观察出内存使用从250MB,只降到了200多MB
再用jmap 查看发现新生代使用的减少到了6MB,但是老年代仍然使用了200多MB
jvisualvm命令-可视化虚拟机
- 用堆Dump这个功能抓取堆的当前快照
- 查找占用内存最大的前20个对象
- 发现arraylist大小200,每个元素占1MB
- 分析代码
//演示查看对象个数 堆转储 dump public class Demo1_13 { public static void main(String[] args) throws InterruptedException { List
students = new ArrayList<>(); for (int i = 0; i < 200; i++) { students.add(new Student()); // Student student = new Student(); } Thread.sleep(1000000000L); } } class Student { private byte[] big = new byte[1024*1024]; } 5. 方法区
5.1 定义
- 用堆Dump这个功能抓取堆的当前快照
逻辑上是堆的一个组成部分,具体实现不同厂商可能不一样,甚至上实现上不是
- Hotspot虚拟机在1.8以前用的堆中的永久代区域实现的方法区
- 1.8用的不是堆的内存,叫元空间,用的本地内存, 是操作系统的内存
存储每个类的结构/信息,如field,(运行时)常量池,构造器和成员方法,类方法的代码
是所有线程共享的,和堆一样
在虚拟机启动时创建
会发生OutOfMemoryError
5.2溢出java.lang.OutOfMemoryError
元空间溢出
永久代空间溢出
/** * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m * 1.8用的元空间实现,默认没有大小限制,为了演示OutOfMemoryError,得修改上面的虚拟机参数 */ /** * 演示1.6 永久代内存溢出 java.lang.OutOfMemoryError: PermGen space * -XX:MaxPermSize=8m */ public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码 public static void main(String[] args) { int j = 0; try { Demo1_8 test = new Demo1_8(); for (int i = 0; i < 10000; i++, j++) { // ClassWriter 作用是生成类的二进制字节码 ClassWriter cw = new ClassWriter(0); //参数: 版本号, public, 类名, 包名, 父类, 接口 cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); // 返回 byte[] byte[] code = cw.toByteArray(); // 只执行了类的加载(没有初始化等其他过程) test.defineClass("Class" + i, code, 0, code.length); // Class 对象 } } finally { System.out.println(j); } } }
实际场景中spring,mabatis大量使用了运行中加载、生成类-动态生成字节码技术,使用不当可能会导致方法区内存溢出
5.3 运行时常量池
什么是常量池?
class文件结构中的常量池,class文件还包括类的基本信息,类方法(虚拟机指令)
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量(字符串,基本类型常量) 等信息
什么是运行时常量池?
- 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址
总结:
- 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
- 常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
- 堆区:用于存放类的对象实例。
- 栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。
jvm中的运行时内存区域还包括本地方法栈和程序计数
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!