栈、堆、方法区的交互关系
运行时数据结果图
栈、堆、方法区的交互关系
方法区的理解
/**
* 测试设置方法区大小参数的默认值
* <p>
* jdk7及以前:
* -XX:PermSize=100m -XX:MaxPermSize=100m
* <p>
* jdk8及以后:
* -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m
* <p>
* 设置堆内存大小
* -Xms60m -Xmx60m
*
* @author shkstart shkstart@126.com
* @create 2020 12:16
*/
public class MethodAreaDemo {
public static void main(String[] args) {
System.out.println("start...");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
查看 jvisualvm 的gc内存堆大小情况
查看 jvisualvm 的类加载情况
Hotpt中方法区的演进
方法区概述
Hotspot 中方法区的演进
设置方法区大小与OOM
查看运行类的进程
jps
查看 jvm 参数是否有效(1.8的环境下)
jinfo -flag PermSize 进程号
jinfo -flag MaxPermSize 进程号
jvm参数文档
/**
* 测试设置方法区大小参数的默认值
* <p>
* jdk7及以前:
* -XX:PermSize=100m -XX:MaxPermSize=100m
* <p>
* jdk8及以后:
* -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m
* <p>
* -Xms60m -Xmx60m
*
* @author shkstart shkstart@126.com
* @create 2020 12:16
*/
public class MethodAreaDemo {
public static void main(String[] args) {
System.out.println("start...");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
方法区 OOM 演示
/**
* jdk6/7中:
* -XX:PermSize=10m -XX:MaxPermSize=10m
*
* jdk8中:
* -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
*
* @author shkstart shkstart@126.com
* @create 2020 22:24
*/
public class OOMTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
OOMTest test = new OOMTest();
for (int i = 0; i < 10000; i++) {
//创建ClassWriter对象,用于生成类的二进制字节码
ClassWriter classWriter = new ClassWriter(0);
//指明版本号,修饰符,类名,包名,父类,接口
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//返回byte[]
byte[] code = classWriter.toByteArray();
//类的加载
test.defineClass("Class" + i, code, 0, code.length);//Class对象
j++;
}
} finally {
System.out.println(j);
}
}
}
异常 信息:
8531
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.atguigu.java.OOMTest.main(OOMTest.java:29)
Process finished with exit code 1
如何解决OOM
方法区的内部结构
字符串常量 在jdk不同的版本中存放的位置不一样
方法区存储什么(规范版本)
类型信息包含域信息和方法信息
查看class 字节码的反编译
/**
* 测试方法区的内部构成
* @author shkstart shkstart@126.com
* @create 2020 23:39
*/
public class MethodInnerStrucTest extends Object implements Comparable<String>,Serializable {
//属性
public int num = 10;
private static String str = "测试方法的内部结构";
//构造器
//方法
public void test1(){
int count = 20;
System.out.println("count = " + count);
}
public static int test2(int cal){
int result = 0;
try {
int value = 30;
result = value / cal;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public int compareTo(String o) {
return 0;
}
}
javap -v -p MethodInnerStrucTest.class > test.txt
方法区会记录class loader 的信息的,但是 test.txt中是没有class loader信息,因为class loader 存在于内存中
## 类型信息
public class com.atguigu.java.MethodInnerStrucTest extends java.lang.Object
implements java.lang.Comparable<java.lang.String>, java.io.Serializable
## 域信息
public int num;
descriptor: I
flags: ACC_PUBLIC
private static java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
## 方法信息
public void test1();
## 方法返回类型信息
descriptor: ()V
## 方法的权限
flags: ACC_PUBLIC
## code 字节码
Code:
## stack 栈的最大深度为 3
## locals 局部量表数组长度为 2
## args_size 方法的参数个数为 1
stack=3, locals=2, args_size=1
0: bipush 20
2: istore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: ldc #6 // String count =
15: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: iload_1
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
22: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: return
LineNumberTable:
line 17: 0
line 18: 3
line 19: 28
LocalVariableTable:
Start Length Slot Name Signature
0 29 0 this Lcom/atguigu/java/MethodInnerStrucTest;
3 26 1 count I
public class MethodAreaTest {
public static void main(String[] args) {
Order order = null;
order.hello(); // 静态方法及时没有实例也是可以访问的,不会报错
System.out.println(order.count);
}
}
class Order {
public static int count = 1;
public static final int number = 2;
public static void hello() {
System.out.println("hello!");
}
}
通过字节码文件可以看出,申明为final 的在编译时,值就已经分配配了,而static则没有分配
运行时常量池 & 常量池
先要能懂字节码中的常量池,才能能明白运行时常量池
常量池含义
为什么需要常量池
常量池中有什么?
运行时常量池
方法区使用举例
public class MethodAreaDemo {
public static void main(String[] args) {
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
System.out.println(a + b);
}
}
当方法不是静态方法时,本地变量表 0 的位置放的就是 this
方法区的演变细节
永久代为什么要被元空间替换
- 不容易确定永久代的大小
- 永久代不容易调优
StringTable(字符串常量表)为什么要移出永久代
因为开发中会有大量字符串被创建,如果字符串保存在永久代,那么只有执行Full GC时才会对字符串进行回收,回收效率低。放在堆中,能及时回收内存。
/**
* 结论:
* 静态引用对应的对象实体始终都存在堆空间
*
* jdk7:
* -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
* jdk 8:
* -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
* @author shkstart shkstart@126.com
* @create 2020 21:20
*/
public class StaticFieldTest {
private static byte[] arr = new byte[1024 * 1024 * 100];//100MB
public static void main(String[] args) {
System.out.println(StaticFieldTest.arr);
// try {
// Thread.sleep(1000000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
jdk1.8 测试
从gc日志上可以看到 arr 引用对应的对象实体放在堆空间中
而其他jdk版本静态引用对应的对象实体始终都存在堆空间(jdk1.6,jdk1.7,jdk1.8 等 ),而 静态变量如 arr 在不同的jdk版本中存放的位置是不一样的。
JHSDK 工具在 jdk1.9版本的 bin 目录中
/**
* 《深入理解Java虚拟机》中的案例:
* staticObj、instanceObj、localObj 变量本身存放在哪里?
* @author shkstart shkstart@126.com
* @create 2020 11:39
*/
public class StaticObjTest {
static class Test {
static ObjectHolder staticObj = new ObjectHolder(); // 静态类属性
ObjectHolder instanceObj = new ObjectHolder(); // 类属性
void foo() {
ObjectHolder localObj = new ObjectHolder(); // 局部变量
System.out.println("done");
}
}
private static class ObjectHolder {
}
public static void main(String[] args) {
Test test = new StaticObjTest.Test();
test.foo();
}
}
方法区的垃圾回收
总结
版权声明:本文为xyz9353原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。