> 文章列表 > jvm内存结构

jvm内存结构

jvm内存结构

1. 栈

jvm内存结构

  1. 程序计数器 2. 虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区

1.2栈内存溢出

栈帧过多导致栈内存溢出

/*** 演示栈内存溢出 java.lang.StackOverflowError* -Xss256k*/
public class Demo1_2 {private static int count;public static void main(String[] args) {try {method1();} catch (Throwable e) {e.printStackTrace();System.out.println(count);}}private static void method1() {count++;method1();}
}

默认不调参数

jvm内存结构

栈帧过大导致栈内存溢出

调小栈内存-Xss256k 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npiu0y9q-1681392343049)(null)]

栈帧内存大于256k

1.3 线程运行诊断

top定位哪个进程对cpu的占用过高

ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)

jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

2.堆内存

jvm内存结构

Heap 堆 通过 new 关键字,

创建对象都会使用堆内存

特点:

它是线程共享的,堆中对象都需要考虑线程安全的问题

有垃圾回收机制

2.1堆内存溢出

  1. jps 工具
    。查看当前系统中有哪些 java 进程
  2. jmap 工具
    。查看堆内存占用情况 jmap -heap 进程id
  3. jconsole 工具
    。图形界面的,多功能的监测工具,可以连续监测

如何解决垃圾回收后内存占用高的问题

jvisualvm命令

jvm内存结构

jvm内存结构

点击线程Dump

查找对象
jvm内存结构

3.方法区

Java 虚拟机的结构 (oracle.com)

方法区

Java 虚拟机具有 在所有 Java 虚拟机之间共享的方法区域 线程。方法区域类似于已编译的存储区域 传统语言的代码或类似于“文本”段的代码 操作系统进程。它存储每个类结构,如 运行时常量池、字段和方法数据以及 方法和构造函数,包括特殊方法 用于类和实例初始化 和接口初始化。

创建方法区域 在虚拟机启动时。虽然方法区域在逻辑上是 作为堆的一部分,简单的实现可以选择不 垃圾收集或压缩它。这 规范不要求方法区域的位置或 用于管理已编译代码的策略。方法区域可以是 固定大小或可根据计算需要进行扩展,并且可以 如果不需要更大的方法区域,则收缩。记忆 对于方法区域不需要是连续的。

Java 虚拟机实现可以为程序员或 用户控制方法区域的初始大小,以及, 对于不同尺寸的方法区域,控制最大值 和最小方法区域大小。

以下特殊 条件与方法区域相关联:

  • 如果 方法区域中的内存无法用于满足 分配请求时,Java 虚拟机会抛出 .OutOfMemoryError

3.3方法区内存溢出

/*** 演示元空间内存溢出 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);}}
}

-XX:MaxMetaspaceSize=8m设置元空间参数

JDK1.8 元空间溢出 (元空间使用系统内存)

jvm内存结构

JDK1.6 永久代溢出

jvm内存结构

3.4 运行时常量池

常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息

运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址

生成*.class文件后使用javap -v *.class命令反编译二进制文件

Classfile /F:/jvm/jvm/jvm/out/production/jvm/cn/itcast/jvm/t5/Helloworld.classLast modified 2023-4-2; size 567 bytesMD5 checksum 8efebdac91aa496515fa1c161184e354Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#20         // java/lang/Object."<init>":()V     //常量池 {  //方法定义public cn.itcast.jvm.t5.HelloWorld();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>
":()V4: returnLineNumberTable:3: ldc           #3                  // String hello world5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

4、StringTable

4.1 StringTable 特性

常量池中的字符串仅是符号,第一次用到时才变为对象

利用串池的机制,来避免重复创建字符串对象

字符串变量拼接的原理是 StringBuilder (1.8)

字符串常量拼接的原理是编译期优化

可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

​ 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串 池中的对象返回

​ 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回

// StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
public class Demo1_22 {// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象// ldc #2 会把 a 符号变为 "a" 字符串对象// ldc #3 会把 b 符号变为 "b" 字符串对象// ldc #4 会把 ab 符号变为 "ab" 字符串对象public static void main(String[] args) {String s1 = "a"; // 懒惰的String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()     new String("ab")String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为abSystem.out.println(s3 == s5);}
}输出:true

4.2 StringTable 延迟加载


/*** 演示字符串字面量也是【延迟】成为对象的*/
public class TestString {public static void main(String[] args) {int x = args.length;System.out.println(); // 字符串个数 2273System.out.print("1");System.out.print("2");System.out.print("3");System.out.print("4");System.out.print("5");System.out.print("6");System.out.print("7");System.out.print("8");System.out.print("9");System.out.print("0");System.out.print("1"); // 字符串个数 2283System.out.print("2");System.out.print("3");System.out.print("4");System.out.print("5");System.out.print("6");System.out.print("7");System.out.print("8");System.out.print("9");System.out.print("0");System.out.print(x); // 字符串个数}
}

debug模式下

jvm内存结构

串池中的数目为2273 ,我们单步执行到第二个 System.out.print(“1”);

jvm内存结构

串池中的数目为2283,因此我们得出结论,StringTable是见一个爱一个,见一个加载一个。这到底对不对?

我们再执行一轮相同的到 System.out.print(x);

jvm内存结构

串池中的数目依旧为2283,因此StringTable 加载字符串是不同的、池子没有的字符串才加载

4.4 intern()方法

可以使用 inter 方法,主动将串池中还没有的字符串对象放入串池

intern() 方法返回字符串对象的规范化表示形式。

String s = new String("a") + new String("b");

上面代码会在串池中创建[“a”,“b”,“ab”] ,堆 new String(“a”) new String(“b”) new String(“ab”)

    String s = new String("a") + new String("b");// 堆  new String("a")   new String("b") new String("ab")String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回System.out.println( s2 == "ab");输出:true 

修改代码

public static void main(String[] args) {String x = "ab";String s = new String("a") + new String("b");// 堆  new String("a")   new String("b") new String("ab")String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回System.out.println( s2 == x);System.out.println( s == x );
}输出:
true
false

S2执行intern()方法发现串池有ab,就不放入了,并且返回串池值ab,对象是同一个。

String x = “ab”;
String s = new String(“a”) + new String(“b”);

// 堆  new String("a")   new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回System.out.println( s2 == x);
System.out.println( s == x );

}

输出:
true
false


S2执行intern()方法发现串池有ab,就不放入了,并且返回串池值ab,对象是同一个。S没有执行方intern()法,但是两个String在不同串池,相比较时发现串池有一样 的放不进去,就原路返回,对象不一样。持续更新中...