JVM内存结构之线程共享区域
线程共享区域
JDK 1.6 vs JDK 1.8内存结构图(来源于黑马程序员):
堆(Heap):
- 定义以及注意事项
通过new关键字创建实例,都将使用堆内存用于存储该实例;
堆是线程共享的,1因此堆中实例都需要考虑线程安全问题;
堆中存在垃圾回收机制,将堆中不再被引用的实例视为垃圾进行回收以达到释放空闲内存的目的;
- 堆内存溢出问题
当不断产生新对象并一直被引用,当达到一定的数量后将导致堆内存被耗尽从而使得堆内存溢出;
- 堆内存诊断相关工具
jps工具:查看当前系统中的Java进程
jmap工具:查看堆内存占用情况
jconsole工具:包含多功能,图形化界面,实现了连续检测
方法区(Method Area):
- 定义
JDK 1.8虚拟机规范对方法区的定义:方法区是指所有虚拟机线程共享的区域,存储了与类结构相关的信息:成员变量,方法数据,成员方法和构造器方法代码等,还有运行时常量池等。
当虚拟机启动时被创建,逻辑上是堆的一个组成部分,在概念上定义了方法区(具体实现上取决于不同的厂商)。
(相关概念:PermGen即永久代和Metaspace元空间)当内存不足以支持当前进程的方法区时将会抛出OutOfMemoryError,在1.8之前会导致永久代内存溢出(当使用Spring,MyBatis等框架技术时容易导致这种情况),1.8以及之后则会导致元空间内存溢出。
- 运行时常量池
常量池的概念:是一张表,虚拟机指令根据这张常量表找到要执行的类目,方法吗,参数类型,字面量等信息;
运行时常量池则是*.class文件中的常量池,当该类被加载,其常量信息就会放入运行时常量池,并把里面的符号地址(个人理解为表中地址)变为真实地址。
(重点)字符串池(StringTable),数据结构为HashTable
*注:实际上把字符串池StringTable放在与堆Heap和方法区Method Area同一级貌似并不合适,但由于在1.6和1.8中StringTable在内存结构中有所变动,并且这个概念设计较多重点,所以笔者单独将其提取出来作为于前两者同级进行总结;
-
字符串池是JDK 1.7后存在于堆中的一块区域,而之前则是存在于方法区中(由永久代PermGen实现);
-
字符串声明、新建实例等相关问题
2.1 直接声明
//直接声明字符串常量
String str1="Hello, world!";
// 使用字符串常量进行拼接,经过“javac的编译期优化”
// 通俗的讲,它使得编译器认为"Hello,","World!"都是常量而不会再变化,结果以及在编译期间确定,因此会将结果放到StringTable中
String str2="Hello,"+"World!";
// 没有经过new执行,因此将会放入常量池
System.out.println(str3==str5);
// 上述行将输出true,具体原因为见下文
这样所声明出的字符串会被放入字符串池中;
2.2 而经过
// 实例化对象
String str=new String("Hello,World!");
// 使用变量进行拼接,此时编译器识别Hello和World为变量,因此将通过StringBuilder的方式对其进行实例化对象
String Hello="Hello,";
String World="world!";
// 通过StringBuilder进行动态拼接
StringBuilder strb=Hello+World;// 底层使用的是new StringBuilder().append("Hello,")+("World");
所创建出来的字符串实例则会放入堆(Heap)中;
总结:
- 字符串池中的字符串仅仅是符号,直到第一次被使用才会新建出相应的实例
- 字符串池的结构为HashTable
- 相同的字符串常量只存在一份(这也是上述str1==str2的值为true的原因)
- 在JDK 1.8中,字符串
变量拼接的原理是使用new StringBuilder() - 字符串
常量连接的原理是javac使用编译期优化 - 可以使用下文的“intern()”主动将没放入串池中的字符串“对象”放入串池
String str1=new String("Hello,World!");
String str2=str1.intern();
String str3="Hello,World!";
System.out.println(str1==str3);// str1实例位于堆中,而str3常量位于串池,因此容易知道此处输出为:false
System.out.println(str2==str3);
// intern()方法将尝试将字符串“实例”放入池中:
// 1.8中,池中没有则直接放入并返回一个String,否则不放入;
// 1.6中,池中没有将不放入,有则复制“实例”再放入;
// 结合上下文可以很容易的得出该语句输出为:true
-
StringTable调优
3.1 思路1:调整StringTable中的HashTable中的桶个数,减少底层的Hash碰撞,提高性能;
3.2 思路2:考虑是否将重复的字符串实例入池,减少重复的字符串实例以提高性能;
浙公网安备 33010602011771号