赞
踩
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的路上修行
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。