JVM方法区踩坑深挖

方法区这一部分需要仔细剖析一下。

方法区常被称为永久代,本质上两者并不等价,仅仅是因为GC分代收集会扩展至方法区。方法区存放已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。需要注意的是,Java6时,String等字符串常量的信息是置于方法区中的,但到了Java7,已经移动到了堆区域。具体的可以通过String.intern()方法体现出来。

类信息包括以下几种:

1.类型全限定名。

2.类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)。

3.类型是类类型还是接口类型。

4.类型的访问修饰符(public、abstract或final的某个子集)。

5.任何直接超接口的全限定名的有序列表。

6.类型的常量池。

7.字段信息。

8.方法信息。

9.除了常量意外的所有类(静态)变量。

10.一个到类ClassLoader的引用。

11.一个到Class类的引用

 

需要注意的是,class对象是存放在堆区的,而不是方法区。类的元数据(元数据并不是类的class对象,class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等都是在方法区的)才是存放在方法区的

方法区中存放的内容在上图一目了然。

永久代

绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。

Java8中就不存在永久代,取而代之的是元空间(Metaspace)。

元空间的本质与永久代类似,都是对JVM规范中方法区的实现。二者最大的区别在于,元空间并不在虚拟机之中,而是使用本地内存。默认情况下,元空间的大小受本地内存的限制,但是可以通过以下参数来指定元空间的大小。

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

作出永久代向元空间的转换原因有以下几点:

1.字符串存在永久代中,容易出现性能问题和内存溢出。

2.类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出

3.永久代会为GC带来不必要的复杂度,并且回收效率偏低

------------------------------

运行时常量池:

运行时常量池作为方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池中存放。

运行时常量池相对于Class文件常量池的另外一个重要特性是具备动态性,Java语言并不要求常量一定只有编译期才能产生,并非预先放入class文件的常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中。比如String.intern()方法。

常量池的好处:

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。可以节省内存空间,常量池中所有相同的字符串常量可以被合并,只占用一个空间。同时可以节省运行时间。比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

双等号==的含义

  • 基本数据类型之间应用双等号,比较的是他们的数值。
  • 复合数据类型(类)之间应用双等号,比较的是他们在内存中的存放地址。

下面为常见的考点:

Integer与常量池

Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = new Integer(40); Integer i5 = new Integer(40); Integer i6 = new Integer(0); System.out.println("i1=i2 " + (i1 == i2)); System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); System.out.println("i1=i4 " + (i1 == i4)); System.out.println("i4=i5 " + (i4 == i5)); System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6)); i1=i2 true i1=i2+i3 true i1=i4 false i4=i5 false i4=i5+i6 true 40=i5+i6 true

Integer i1=40; Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40),从而使用常量池中的对象。同理,i2与i1引用同一个常量池中的对象。

Integer i4=new Integer(40);这种情况下回创建新的对象。

i4与i5分别代表不同的对象。

语句i4=i5+i6,因为+这个操作不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加。即i4==40.然后Integer对象无法与数值进行直接比较,所以i4自动拆箱为int值40,最终这条语句转为40==40.进行数值比较。

String与常量池

String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true

Java中字符串创建的两种方式:1.字面量形式,如String str="abc"另外一种使用new这种标准的构造对象的方法,如String str=new String("abc");

问题1

当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池中,并返回该引用。

当使用new来构造字符串对象时,不管字符串常量池中有没有相同内容的对象的引用。新的字符串对象都会创建。所以str1与str2代表不同的对象。

注意若str3=str2.intern()则有str1==str3为true。对于使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池中,可以使用intern方法。调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。

针对问题2中的str3与str4.

str3等价于直接在字符串常量池中创建一个“string”对象,同时返回引用。str4则是

而字符串拼接中间会产生StringBuilder对象,之后返回sb.toString().

有一道有意思的题目

  1. String s = null;
  2. s += "abc";
  3. System.out.println(s);

答案是nullabc

两个字符串str1, str2的拼接首先会调用 String.valueOf(obj),这个Obj为str1,而String.valueOf(Obj)中的实现是return obj == null ? "null" : obj.toString(), 然后产生StringBuilder, 调用的StringBuilder(str1)构造方法, 把StringBuilder初始化,长度为str1.length()+16,并且调用append(str1)! 接下来调用StringBuilder.append(str2), 把第二个字符串拼接进去, 然后调用StringBuilder.toString返回结果!

所以这里str3==str4返回false的原因是二者对应的内存地址不一样。但是equal时,返回true。

这样一来,str3==str5也就解释的通了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值