1、常量
常量在java中就值的是一般的字面量,比如字符串,整数,浮点数等等数据。简单理解java中什么叫常量
2、常量池,也叫静态常量池或者class文件常量池,说常量池一定要指明是编译器产生的。它的组成为字面量和符号引用。
3、运行时常量池。当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。运行时常量池在jdk1.8时,在方法区(即元空间)中。
4、字符串常量池。就是String类型的字符串,包括代码写的字符串,比如方法名,类名都是字面量,还有String定义的字符串。字符串常量池,jdk1.8时在堆中,全局共享,独一份,之前在方法区中。
那么问题就来了,
1、字符串常量池在1.8时在堆内存中,如何证明。
2、字符串常量能被GC回收吗?
3、如何证明jdk1.8方法区为元空间。参考简单理解jdk1.8中的方法区
上面1和2问题,通过一个实例可以证明,
先设置JVM虚拟机启动参数-Xms1m -Xmx1m -XX:+PrintGCDetails -XX:MaxMetaspaceSize=8m
。把堆内存设置小一点,1M。
public class DemoClass {
public static void main(String[] args) throws Exception {
//字符串常量池在哪,在堆中。并且常量也会被GC回收。
String s = "love";
for (int i = 0; i < 10000000; i++) {
s = s + "qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm";
if (i%10==0){
System.out.println("第"+i+"次");
Thread.sleep(100);
}
}
}
}
运行的结果截取一部分,如下
[GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->496K(1536K), 0.0010906 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Connected to the target VM, address: '127.0.0.1:51561', transport: 'socket'
[GC (Allocation Failure) [PSYoungGen: 1000K->504K(1024K)] 1008K->632K(1536K), 0.0045804 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1002K->506K(1024K)] 1130K->786K(1536K), 0.0192090 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC (Allocation Failure) [PSYoungGen: 1013K->488K(1024K)] 1294K->896K(1536K), 0.0077057 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 488K->463K(1024K)] [ParOldGen: 408K->358K(512K)] 896K->822K(1536K), [Metaspace: 2930K->2930K(1056768K)], 0.0062971 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 974K->442K(1024K)] [ParOldGen: 358K->343K(512K)] 1332K->785K(1536K), [Metaspace: 3141K->3141K(1056768K)], 0.0066005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
第0次
[Full GC (Ergonomics) [PSYoungGen: 954K->429K(1024K)] [ParOldGen: 343K->363K(512K)] 1297K->793K(1536K), [Metaspace: 3399K->3399K(1056768K)], 0.0065477 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
第10次
---
---
[Full GC (Ergonomics) [PSYoungGen: 603K->599K(1024K)] [ParOldGen: 480K->480K(512K)] 1084K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0065548 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 940K->769K(1024K)] [ParOldGen: 480K->480K(512K)] 1421K->1250K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0064790 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 940K->428K(1024K)] [ParOldGen: 480K->481K(512K)] 1421K->909K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0071489 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 604K->599K(1024K)] [ParOldGen: 481K->481K(512K)] 1085K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0113774 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 599K->599K(1024K)] [ParOldGen: 481K->481K(512K)] 1080K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0066770 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 1024K, used 649K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
eden space 512K, 43% used [0x00000000ffe80000,0x00000000ffeb71a8,0x00000000fff00000)
from space 512K, 83% used [0x00000000fff80000,0x00000000fffeb2f8,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 512K, used 481K [0x00000000ffe00000, 0x00000000ffe80000, 0x00000000ffe80000)
object space 512K, 93% used [0x00000000ffe00000,0x00000000ffe78488,0x00000000ffe80000)
Metaspace used 3436K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 370K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at com.jd.qsm.first.demo.controller.DemoClass.main(DemoClass.java:8)
Disconnected from the target VM, address: '127.0.0.1:51561', transport: 'socket'
回答第一个问题,由于我们一直生成的是字符串,最终导致OOM的原因是Java heap space
堆空间,而在生成字符串时Metaspace几乎就没有什么变化过。所以证明字符串常量池在堆空间中。
回答第二个问题,可以看到程序经历了很多次的GC和fullGC,看到新生代和老年代的内存占用都有减少,最终由于堆内存不足才OOM的。所以由于每次GC都有减少,所以字符串是可以被GC回收的。
4、常量池怎么查看。
5、常量池有什么作用,为什么需要它
6、常量池与运行时常量池有什么关联
回答4-6问题
编写一段代码
public class DemoClass {
public static void main(String[] args) throws Exception {
System.out.println("qsm");
}
}
找到字节码文件,运行javap -v DemoClass.class
D:\idea\code\firstdemo\target\classes\com\jd\qsm\first\demo\controller>javap -v DemoClass.class
Classfile /D:/idea/code/firstdemo/target/classes/com/jd/qsm/first/demo/controller/DemoClass.class
Last modified 2021-1-9; size 666 bytes
MD5 checksum 711ba65168cdd7d23241623fe88153f6
Compiled from "DemoClass.java"
public class com.jd.qsm.first.demo.controller.DemoClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #26 // qsm
#4 = Methodref #27.#28 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #29 // com/jd/qsm/first/demo/controller/DemoClass
#6 = Class #30 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/jd/qsm/first/demo/controller/DemoClass;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 Exceptions
#19 = Class #31 // java/lang/Exception
#20 = Utf8 MethodParameters
#21 = Utf8 SourceFile
#22 = Utf8 DemoClass.java
#23 = NameAndType #7:#8 // "<init>":()V
#24 = Class #32 // java/lang/System
#25 = NameAndType #33:#34 // out:Ljava/io/PrintStream;
#26 = Utf8 qsm
#27 = Class #35 // java/io/PrintStream
#28 = NameAndType #36:#37 // println:(Ljava/lang/String;)V
#29 = Utf8 com/jd/qsm/first/demo/controller/DemoClass
#30 = Utf8 java/lang/Object
#31 = Utf8 java/lang/Exception
#32 = Utf8 java/lang/System
#33 = Utf8 out
#34 = Utf8 Ljava/io/PrintStream;
#35 = Utf8 java/io/PrintStream
#36 = Utf8 println
#37 = Utf8 (Ljava/lang/String;)V
{
public com.jd.qsm.first.demo.controller.DemoClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jd/qsm/first/demo/controller/DemoClass;
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String qsm
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
Exceptions:
throws java.lang.Exception
MethodParameters:
Name Flags
args
}
SourceFile: "DemoClass.java"
从的出来的结果可以看到有一部分叫做Constant pool:
,这里就是class文件常量池,是编译之后生成的,是一个静态的概念。它包含了字面量和符号引用。比如
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String qsm
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
比如ldc指令指向了
#3
,而#3
在常量池中,指的是#3 = String #26 // qsm
,#26
指的是#26 = Utf8 qsm
,也就是说这里的#3
就是符号引用。这些符号引用到加载到jvm(解析阶段)或者运行时(虚拟机栈的动态链接),才会变成直接引用。
问题4的答案,使用javap -v xxx.class
命令就可以查看到常量池。
问题5的答案,一个类编译为字节码中,肯定是需要了很多其他的类,比如本代码就需要了System,Object等类信息,这些数据很大,不可能全部放在一个类的字节码文件中,所以先使用常量池,对这些数据的引用使用符号引用,等真正加载到jvm或者运行时才将这些符号引用转换为直接引用。
问题6、当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中了。但是jdk1.8把其中的字符串常量池放入了jvm中。
7、什么叫符号引用,什么叫直接引用
8、什么地方进行了符号引用转换为直接引用
8、什么叫静态链接和动态链接
还是上面的javap
解析出来的的字节码文件为例
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String qsm
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
0: getstatic #2
,第一句getstatic
得到静态属性,里面的#2
就是符号引用,在常量池中它又指向了其他符号引用,但最终实际代表了PrintStream类的一个对象。这些符号引用在编译的时候,仅仅是表面自己需要什么,到加载jvm或者运行时才执行真正的地址,这个就是直接引用了。
jvm加载的时候有一个阶段叫做解析,这里就把能够确定的符号引用转变为直接引用了。这里的解析也成为静态链接。还有一个地方,线程的虚拟机栈执行方法的时候,有一个动态链接,这个地方也是将符号引用转为直接引用的,由于是在运行中,所以叫做动态链接。
那么什么情况下使用静态链接,什么情况使用动态链接。一般静态链接的时候,都是能够直接确定地址的,比如类的静态方法,final方法,private方法,构造器等,这些都叫做非虚方法。
而一般的public void hi();
等成员方法就是虚方法。这些在加载的时候,是无法确定的。只有正运行的时候才知道。主要原因还是多态(重写)因素。
比如,Father person= new SonA();
,执行person.hi()
的时候,是不知道这个hi方法具体是执行谁的,是SonA的还是Father的,并不清楚SonA是否重写了父类的hi方法。所以这里只有真正运行方法的时候,才知道才符号引用指向直接引用。
再看5: invokevirtual #4
,invokevirtual 就是调用虚方法,它下面还有子类。
【完,喜欢就点个赞呗】
正在去BAT的路上修行
今天的文章简单理解常量、常量池、运行时常量池和字符串常量池分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/32407.html