JVM专题-方法区
Posted IT老刘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM专题-方法区相关的知识,希望对你有一定的参考价值。
1.定义
方法区是一种规范,永久代和元空间都只是它的实现。
2.组成
3.方法区内存溢出
元空间大小调整参数
-XX:MaxMetaspaceSize=8m
案例代码
package com.method.area;
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);
}
}
}
由于元空间使用本地内存,所以很难方法区溢出所以需要手动调整元空间大小。
-XX:MaxMetaspaceSize=8m
运行结果 OutOfMemoryError: Metaspace
场景
spring、mybatis都用到了cglib技术,字节码的动态生成技术,动态加载类,动态生成类,运行期间,经常会产生大量的类,可能会产生方法区溢出
4.运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是
*.class
文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。
案例
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
通过反编译这段程序来学习相关的知识,用到的命令
- 先进入到java源文件的目录
cd 目标目录
- 将HelloWorld.java 编译成 HelloWorld.class
javac HelloWorld.java
- 反编译HelloWorld.class
javap -v HelloWorld.class
结果如下
类基本信息
常量池
类方法定义
方法运行流程
-
对于主方法来说,解释器依次执行指令。getstatic–>得到某个常量,索引为#2。
-
以#2作为索引去常量池查询,得到 Fieldref ,即属性索引,索引为 #16,#17
-
再以#16,#17为索引继续查询常量池
-
依照这个步骤下去继续阅读即可
5.StringTable
5.1.常量池与串池的关系
// 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";
}
运行时常量池
方法区
局部变量表
常量池最初存在于字节码中,运行时会被加载到运行时常量池中。这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
字符串的加载是懒惰的,当执行到具体的引用时,才会创建对象。如String s1 = “a”。创建了s1对象。
同时会创建StringTable串池。将s1作为key去串池中寻找,如果没有,才会加入串池
5.2.变量字符串拼接
// 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";
// new StringBuilder().append("a").append("b").toString()
// new String("ab")
String s4 = s1 + s2;
// 输出false,s3在常量池,s4在堆里
System.out.println(s3 == s4);
}
}
可以看到StringBuilder.toString是new了一个String,new的对象在堆里。
5.3.编译期优化(常量字符串拼接)
// 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 在编译期间的优化,结果已经在编译期确定为ab
// true
System.out.println(s3 == s5);
}
}
5.4 字符串延迟加载
/**
* 演示字符串字面量也是【延迟】成为对象的
*/
public class TestString {
public static void main(String[] args) {
int x = args.length;
System.out.println(); // 字符串个数 4695
System.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"); // 字符串个数 4703
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(x); // 字符串个数2285
}
}
以上是关于JVM专题-方法区的主要内容,如果未能解决你的问题,请参考以下文章