JVM学习笔记——内存结构篇( 四 )

  1. jmap工具
// jmap用于查看当前系统中堆内存占用情况(静态形式)// 我们直接在IDEA的输入台输入即可jmap -heap 进程id// 我们可以看到Heap Usage就是内存管理// 其中Eden space 为新产生的堆内存// 其中Old Generation 为之前产生的堆内存
  1. jconsole工具
// jconsole用于查看当前系统中堆内存占用情况(图形化界面app展示)// 我们直接在IDEA的输入台输入即可jconsole
  1. jvisualvm工具
// jvisualvm用于查看当前系统中堆内存占用情况(图形化界面app展示)// 我们直接在IDEA的输入台输入即可jvisualvm方法区这小节我们来介绍JVM内存结构中的方法区
方法区简介我们首先来简单介绍一下方法区:
  • 方法区是所有java虚拟机共享的一片区域
  • 方法区中存放着所有类的所有信息 , 包括有属性 , 方法,构造方法等
  • 方法区在虚拟机启动的一瞬间被创建,同样在虚拟机停止时方法区进行销毁
我们需要特别注意一点:
  • 方法区和程序计数器一样只是一个概念
  • 我们在实际开发中,jdk1.8之前采用的是永久代,在jdk1.8及以后均采用元空间
我们直接给出其内存结构图展示:
JVM学习笔记——内存结构篇

文章插图
方法区内存溢出问题方法区同样存在有内存溢出问题,但并不常见
我们将方法区的讲解分为两部分,有永久代也有元空间的讲解:
  1. 永久代内存溢出问题
// 永久代的概念仅存在于jdk1.8之前,我们可以通过-XX来控制永久代大小// 当方法区为永久代时,溢出就显示错误java.lang.OutOfMemoryError: PermGen spacepackage cn.itcast.jvm;import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;/** * 演示永久代内存溢出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 < 20000; i++, j++) {ClassWriter cw = new ClassWriter(0);cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);byte[] code = cw.toByteArray();test.defineClass("Class" + i, code, 0, code.length);}} finally {System.out.println(j);}}}
  1. 元空间内存溢出问题
// 元空间存在于jdk1.8之后,实际上这时的元空间已经作用于系统内存了 , 相当于元空间的大小几乎是不可能出现溢出的// 所以我们需要先设置元空间大小才能观察到溢出问题:-XX:MaxMetaspaceSize=8m// 当方法区为永久代时,溢出就显示错误java.lang.OutOfMemoryError: Metaspacepackage cn.itcast.jvm.t1.metaspace;import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;/** * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=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);}}}常量池简介我们再回到方法区来简单介绍一下常量池:
  • 我们在上面的图中可以看到常量池之前是放在方法区中的StringTable,但在jdk1.8之后放在了堆中的StingTable
  • 我们需要注意的是:即使StringTable在堆里面,在堆里存放的数据和在StringTable里存放的数据也不是同一个数据
我们先来简单介绍一下常量池:
  • 常量池实际上是一张表
  • 虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
我们再介绍一下运行时常量池:
  • 运行时常量池,常量池是 *.class 文件中的
  • 当该类被加载,它的常量池信息就会放入运行时常量池 , 并把里面的符号地址变为真实地址
常量池详细介绍我们通过一个简单的代码编译来介绍常量池:
// 下面是helloworld的源码package cn.itcast.jvm.t5;// 二进制字节码(类基本信息,常量池,类方法定义 , 包含了虚拟机指令)public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");}}

推荐阅读