1、程序计数器(Program Counter Register)
1.1 作用:是记住下一条jvm指令的执行地址
1.2 特点:
1)线程私有,随着线程创建而创建,销毁而销毁
2)不会存在内存溢出
3)是一块较小的内存空间
2、虚拟机栈(JVM stacks)
2.1 栈与栈帧
栈:线程运行需要的内存空间,一个线程对应一个栈,一个栈由多个栈帧组成,每个线程只能有一个活动栈帧,对应着当前正在运行的方法。
栈帧:每个方法运行时需要的内存,用于装载方法的参数、变量、返回值等信息。
2.2 问题辨析
1)垃圾回收不涉及栈内存,因为栈会自动弹出栈帧释放内存。垃圾回收只会设计堆内存的内容
2)栈内存不是越大越好,当栈内存加大之后,可分配的栈数量会变少从而导致多线程运行的数量变少。栈内存的值只是为了可以多次调用方法。
3)如果方法内的局部变量没有逃离方法的作用范围,那么他是线程安全的
4)如果局部变量引用了对象,并且该对象逃离了方法的作用范围(方法参数,返回值等),则需要考虑线程安全问题
2.3 栈内存溢出
1)栈帧过多导致栈内存溢出 --> StackOverflowError
2)栈帧过大导致----
2.4 线程运行诊断
1)cpu占用过多
用top命令定位哪个进程对cpu使用高
使用ps命令进一步定位是哪个线程引起的
使用jstack找到问题的代码行
2)长时间未有结果
使用jstack查看问题代码行
3、本地方法栈
调用本地的方法接口
4、堆
4.1概念:
通过new关键字创建对象都会使用堆内存
堆中有垃圾回收机制
都需要考虑线程安全问题
4.2
堆内存溢出 OutOfMemoryError : Java heap space
4.3 堆内存诊断
1)jps工具
使用jps工具查看当前有那些Java进程
2)jmap工具
使用命令:
jmap -heap 进程id
可以查看进程的详情信息
3)jconsole工具
使用命令
jconsole打开图形化界面
4)jvisualvm工具
使用命令jvisualvm打开
5、方法区
运行时常量池
.class的二进制字节码中包含了:类的基本信息,常量池,类方法定义,虚拟机指令等
常量池中保存着虚拟机指令中的信息,常量池可认为是一张常量表,当类被加载时,它的常量池信息就会被放入运行时常量池(内存中)
常量池最初是存在字节码文件中,当被运行时就会被加载到内存中,也就是称为运行时常量池
6、StringTable(字符串常量池 )[jdk1.8]
Java代码:
当未执行时,a只是常量池中的符号,还没Java字符串对象
红线:要去常量池的2号位置加载一个信息(把a符号变为"a"字符串对象)
然后把“a”对象视为一个key,去StringTable中找是否有取值相同的Key,没有就放入串池中,如果有就使用串池中的对象
StringTable是 hashTable结构,不能扩容
绿线:存入到1号局部变量中去
LocalVariableTable是方法运行的局部变量表
代码:
13行可以看出是使用StringBuilder
第16行是从表中的1号位加载信息
21和24行使用StringBuilder的append方法
整个String s4 = s1 +s2 可以看作是:new StringBuilder().append("a").append("b").toString(),相当于是new String("ab"),放入堆中的,并没有放入串池中
代码:
第6行是String s3 = "ab",第11行是String s4 = "a" + "b"
String s4 = "a" + "b"是直接去字符串常量池中找"ab",所以s4和s3是相同的
这是javac在编译期间的优化,结果已经在编译器确定为ab
而之前的 是不确定的,因为s1和s2是可以更改的
代码:
①new String("a") 时,先将"a"放入串池中,再通过取出"a"并在堆中new一个String对象
②s由有两个不确定的字符串拼接而成,所以只会在堆中创建"ab",并不会在串池中放入"ab"
③intern()方法可以将当前字符串对象放入串池中,如果有则不会放入,没有则放入串池,并把串池中的对象地址返回
④如果是在jdk1.6中,intern()方法则会将s拷贝一份,将这份复制后的放入串池中,所以放入串池中的s和原来的s是不同的
在1.6的时候,StringTable在方法区的永久代中,之后的在堆内存中
垃圾回收机制
当串池中的对象太多时,会回收没被使用的字符串从而节省空间
性能调优
StringTableSize的桶大小影响速度
使用串池可以有效地节约内存的使用。当有重复的字符串时可以直接从串池中获取。