调优分成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"
这个对象,也就是说str1
和str2
指向了同一个对象,因此语句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
出来的都是一个新的对象,也即是说引用str1
和str2
指向的是两个不同的对象,因此语句System.out.println(str1 == str2)
输出:false
和第一个例子的不同点在于,这个在堆中多出了两个对象,有四个对象,而第一个例子中在堆中只有str1
和str2
两个对象
③使用了intern
方法
用了这个方法,会复用字符串常量池中的String
对象。比如new
都是创建新对象,当字符串对象b
创建的时候,因为调用了intern
方法,所以会去字符串常量池中找king
,结果找到了,则直接返回该对象的引用。
所以这里打印出a==b
④set
方法赋值
这种其实和第一种基本一样,就不多说了
⑤创建时用了”+“号
String str1="ab" + "cd" + "ef";
因为String
是final
的,不可更改,所以如理论上这句代码是这样执行的:首先创建三个String
对象,分别是ab
,cd
,ef
。然后将ab
和cd
拼接形成新对象abcd
,然后再与ef
拼接形成abcdef
。这样的效率极低。所以编译器帮我们做了优化,就直接变成
String str1="abcdef";
⑥循环比较大的时候创建
当循环比较大的时候,会用StringBuilder
进行优化