JVM调优及常量池——JVM(4)

调优分成3种

一.JVM调优1:JVM内存的分代划分

栈不用管,JVM将内存分成新生代,老年代和持久代。如图
在这里插入图片描述
这种特殊的划分内存的方式,是JVM的调优方式之一。

  • 至于哪个代具体大小是多少,一般有个依据。就是活跃数据大小的倍数。活跃数据就是程序稳定下来后堆中的数据。
    在这里插入图片描述

二.JVM调优2:扩容新生代

扩容新生代能够提高GC(垃圾回收)效率,为什么呢?
我们看下面一张图我们看

当垃圾回收的时候,会进行两个步骤,①扫描新生代②复制非垃圾对象。假设这两个步骤的时间分别为T1,T2。在上图中都有标出。

假设A存活的时间为750ms

  • 新生代容量为R时(未扩容),GC间隔为500ms,那么在一秒的时间内,前500ms进行了T1+T2,后500ms进行了T1(A已经死了,无T2).总时间为2T1+T2
  • 新生代容量为2R(扩容之后),那么GC间隔变成1000ms(因为容量变成了两倍),那么在1s的时间内,只进行了2T1,少了一个T2

这只是一种情况,并不一定能完全提高GC效率,但是不会减少GC效率,而且很多情况下还是可以提高GC效率的

三.JVM调优3:卡表(card table)

在GC的时候,经常会有一些跨代引用。比如在Minor GC的时候,有老年代引用了新生代,那按上一篇文章的规则讲,就是老年代的相应对象应该被视为根。
在这里插入图片描述
比如这里3号位置的对象就应该被视为根。
那么在扫描的时候怎么避免只能扫描全堆后才能找到这个根呢?就是通过card table。这个就是一个数组,里面记录了根的位置,这样扫描的时候就能避免扫描全堆,提高效率
在这里插入图片描述
如这里在3的位置做了标记。

四.常量池面试题

可看这篇文章进行详细学习

1.class常量池

一般在反编译后的Constant pool里面,如图
在这里插入图片描述

Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。

2.运行时常量池

运行时常量池是方法区的一部分。运行时常量池是当Class文件被加载到内存后,Java虚拟机会将Class文件常量池里的内容转移到运行时常量池里(运行时常量池也是每个类都有一个)。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中

3.字符串常量池

String的创建方式有多种,每一种都有特殊的地方,只有将这些特殊的地方理解了,才能理解字符串常量池。
下面一一列举(主要理解前三点)

①采用字面值的方式创建字符串对象
public class a {
    public static void main(String[] args) {
        String str1="aaa";
        String str2="aaa";
        System.out.println(str1==str2);   
    }
}
运行结果:
true

采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"aaa"这个对象,如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str,这样str会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。

对于上述的例子:
这是因为,创建字符串对象str2时,字符串池中已经存在"aaa"这个对象,直接把对象"aaa"的引用地址返回给str2,这样str2指向了池中"aaa"这个对象,也就是说str1str2指向了同一个对象,因此语句System.out.println(str1== str2)输出:true
在这里插入图片描述

②采用new关键字新建一个字符串对象
public class a {
    public static void main(String[] args) {
        String str1=new String("aaa");
        String str2=new String("aaa");
        System.out.println(str1==str2);
    }
}
运行结果:
false

采用new关键字新建一个字符串对象时,JVM首先在字符串常量池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str1,这样,str1就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串常量池池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str1引用,这样,str1指向了堆中创建的这个"aaa"字符串对象。

对于上述的例子:
因为,采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用str1str2指向的是两个不同的对象,因此语句System.out.println(str1 == str2)输出:false
和第一个例子的不同点在于,这个在堆中多出了两个对象,有四个对象,而第一个例子中在堆中只有str1str2两个对象
在这里插入图片描述

③使用了intern方法

在这里插入图片描述
用了这个方法,会复用字符串常量池中的String对象。比如new都是创建新对象,当字符串对象b创建的时候,因为调用了intern方法,所以会去字符串常量池中找king,结果找到了,则直接返回该对象的引用。
所以这里打印出a==b

set方法赋值

在这里插入图片描述
这种其实和第一种基本一样,就不多说了

⑤创建时用了”+“号
String str1="ab" + "cd" + "ef";

因为Stringfinal的,不可更改,所以如理论上这句代码是这样执行的:首先创建三个String对象,分别是abcdef。然后将abcd拼接形成新对象abcd,然后再与ef拼接形成abcdef。这样的效率极低。所以编译器帮我们做了优化,就直接变成

String str1="abcdef";
⑥循环比较大的时候创建

当循环比较大的时候,会用StringBuilder进行优化
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值