【JVM】JVM02(方法区-串池-直接内存)

方法区的内存溢出问题

  • 1.8以前会导致永久代内存溢出

  • 1.8之后会导致元空间内存溢出

我们通过一个实例来演示方法区内存溢出的情况

为了更好的演示,我们需要调整虚拟机参数

-XX:MaxMetaspaceSize=8m

在这里插入图片描述

/*演示元空间内存溢出

*/

public class Demo02 extends ClassLoader {//ClassLoader能动态加载类的二进制字节码

public static void main(String[] args) {

int j = 0;

try {

Demo02 test = new Demo02();

for (int i = 0; i < 10000; i++) {

//ClassWriter:生成类的二进制字节码

ClassWriter cw = new ClassWriter(0);

//版本号,public,类名,包名,父类,实现的接口

cw.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,“Class”+i,null,“java.lang/Object”,null);

//生成一个类,并返回该类的字节码数组

byte[] code = cw.toByteArray();

//执行了类的加载

test.defineClass(“Class”+i,code,0,code.length);//Class对象

}

}finally {

System.out.println();

}

}

}

由于类加载过多,导致了方法区内存溢出

3002

Exception in thread “main” java.lang.OutOfMemoryError: Compressed class space // 元空间内存溢出

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.wql.jvm.MethodArea.Demo02.main(Demo02.java:23)

运行时常量池

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

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

我们通过反编译来查看类的信息

首先找到类的.class文件

.class文件在out目录下

在这里插入图片描述

输入指令

javap -v Demo01.class

类的信息:

在这里插入图片描述

常量池信息:

在这里插入图片描述

在这里插入图片描述

类的方法定义,第一个是构造方法,第二个是main方法

在这里插入图片描述

我们以getstatic为例说明虚拟机指令的执行流程

在这里插入图片描述

执行过程中需要对#2,#3,#4进行查常量池表翻译,比如#2,在常量池表中对应#21,#22

在这里插入图片描述

所以继续找表中的#21,#22

#21,#22在表中又对应#28,#29,#30

在这里插入图片描述

#28代表静态变量所在的类为java/lang/System

#29代表要找到System类中名叫out的变量

#30代表它的类型是java/io/PrintStream

所以getstatic指令代表找到在java/lang/System类下的名叫out的成员变量,变量类型为java/io/PrintStream

⭐️2.StringTable串池

===============================================================================

StringTable特性

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

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

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

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

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

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

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

注意:无论是串池还是堆里面的字符串,都是对象

串池作用:用来放字符串对象且里面的元素不重复

⭐️2.1 常量池与串池的关系

=============================================================================

public class Demo03 {

public static void main(String[] args) {

String s1 = “a”;

String s2 = “b”;

String s3 = “ab”;

}

}

编译之后我们查看以上代码常量池的结果

在这里插入图片描述

在这里插入图片描述

我们可以看到常量值中最中存放的是[“a”,“b”,“ab”]

常量池最初存在于字节码文件中,运行的时候被加载到运行时常量池中,也就是Constant pool,此时池中的信息还没有成为字符串对象,还仅仅是符号,直到执行到ldc指令,比如ldc #2,通过#2在池中找到a符号,才会将a符号变为字符串对象,"a"作为key去StringTable串池(长度固定,不能扩容)中进行寻找,如果没有取值相同的key,则"a"加入串池

我们需要注意,字符串对象的创建是懒惰的,只有运行到String s1 = "a"并且串池中不存在"a"的时候的时候,该字符串对象才会被真正的创建并添加进串池

⭐️2.2串池中字符串变量的拼接

==============================================================================

案例1:使用字符串变量创建字符串

public class Demo04 {

public static void main(String[] args) {

String s1 = “a”;

String s2 = “b”;

String s3 = “ab”;

String s4 = s1 + s2;//new StringBuilder().append(s1).append(s2).toString()

}

}

我们编译后查看常量池,观察新生成的s4是如何工作的

在这里插入图片描述

我们可以看到StringBuilder对象被创建,说明s4是通过StringBuilder对象拼接,然后调用toString()方法创建了一个新的字符串对象,该对象在堆中,所以

System.out.println(s3==s4);false

因为这已经是两个不同的对象了

案例2:使用字符串常量创建字符串

public class Demo05 {

String s1 = “a”;

String s2 = “b”;

String s3 = “ab”;

String s4 = “a”+“b”;

}

我们编译之后查看常量池,观察s4是如何生成的

在这里插入图片描述

我们可以看到,ldc #6,到常量池中寻找“ab”,而"ab"在常量池中已经存在,所以直接返回

但是为什么"a"+"b"变成了"ab"呢?这是因为

在拼接字符串常量时,javac在编译期间的优化,他认为"a"和"b"都是常量,拼接之后的值是固定的,所以直接确定编译结果,如果串池中已经有拼接好的值,则不用创建直接取

在拼接字符串变量时,因为内容不确定,所以编译期间会创建StringBuilder对象进行内容拼接,调用toString()方法又产生了新的字符串对象

⭐️2.3 intern()方法【jdk1.8】

======================================================================================

将字符串对象尝试放入串池(StringTable)中,如果有则不放入

public class Demo06 {

public static void main(String[] args) {

String s1 = new String(“a”)+new String(“b”);

String s2 = s1.intern();

}

}

以上面代码为例,new String(“a”)创建字符串"a"对象,并在串池中添加"a",new String(“b”)创建字符串"b"对象,并在串池中添加"b",s1为"ab"字符串对象,但是不存在于串池中,调用intern()方法之后将s1对象放入串池并将值"ab"返回

那么如果串池中提前存在了"ab"呢

public static void main(String[] args) {

String x = “ab”;

String s1 = new String(“a”)+new String(“b”);

String s2 = s1.intern();

System.out.println(s2==x);

System.out.println(s1==x);

}

因为串池中已经有"ab",所以s1.intern();并不会把s1再放入串池,但是返回的s2是串池中的对象,

所以答案是true,false

注意:

串池在1.6之前存放的是字符串对象的引用,在1.8之后,因为位置移动到了Heap中,所以存放的是对象本身和字面量

⭐️2.4 intern()方法【jdk1.6】

======================================================================================

jdk1.6和1.8不同的是,如果调用intern()方法且串池中没有此对象,则会把此对象复制一份,放入串池,将串池中的对象返回

⭐️2.5面试题

======================================================================

public class Demo02 {

public static void main(String[] args) {

String s1 = “a”;

String s2 = “b”;

String s3 = “a”+“b”;

String s4 = s1 + s2;

String s5 = “ab”;

String s6 =s4.intern();

//问

System.out.println(s3==s4);

System.out.println(s3==s5);

System.out.println(s3==s6);

String x2 = new String(“c”)+new String(“d”);

String x1 = “cd”;

x2.intern();

//问,如果调换了最后两行代码的位置呢,如果是jdk1.6呢

System.out.println(x1==x2);

}

}

false

true

true

false

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

权威指南-第一本Docker书

引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

image

image

image

image

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。

总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。

[外链图片转存中…(img-5ZmFdHub-1712073009217)]

[外链图片转存中…(img-3vECnGeP-1712073009218)]

[外链图片转存中…(img-ce5mhZAn-1712073009218)]

[外链图片转存中…(img-Q03LIiGC-1712073009218)]

关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值