5.1 程序计数器
5.1.1定义
Program Counter Register程序计数器(寄存器)
-
作用:用来记住下一条JVM指令执行地址,物理上用寄存器实现
-
特点:
-
是线程私有的。因为会切换线程,每一个线程需要记住自己执行到哪一行了
-
不会存在内存溢出
-
5.2 虚拟机栈
5.2.1 定义
-
栈:线程运行需要的内存空间
-
每个线程运行时所需要的内存,称为虚拟机栈
-
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
-
-
栈帧:每个方法运行时需要的内存(如参数、局部变量、返回地址等)
-
问题解析:
-
垃圾回收是否涉及栈内存?
答:不需要。因为栈内存里是栈帧内存,完成后就弹出,因此不需要回收
-
栈内存越大越好吗?
答:不是。栈越大,能容纳的线程越少。可能会影响线程的数量。
-
方法内的局部变量是否线程安全?
答:是。如果是私有的,则需要考虑线程安全,是共享的,则不需要考虑线程安全。
是每个线程的局部变量,是每个线程私有的。
如果方法内局部变量没有逃离方法的作用范围,那么就是线程安全的,反之则不安全(如果是引用对象或者返回值)
-
5.2.2 栈内存溢出
-
栈帧过多导致栈内存溢出(报的错是:StackOverDFlowError)
-
栈帧过大导致栈内存溢出
5.2.3 线程运行诊断
-
案例一:CPU占用过多
定位:
-
用top定位哪个进程堆cpu占用过高
-
ps H -eo pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高)
-
jstack + 进程id:
-
可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号
-
-
-
程序运行很长时间没有结果
可能出现了死锁
5.3 本地方法栈
英文:Native Method Stack:给本地方法的运行提供接口,如Object类中的clone()方法,是用native修饰的
5.4 Java堆
程序计数器、虚拟机栈、本地方法栈是私有的,Java堆是公有的
5.4.1 定义
-
Heap 堆,通过new关键字,创建对象都会使用堆内存
-
特点:
-
它是线程共享的,堆中对象都需要考虑线程安全的问题
-
有垃圾回收机制
-
5.4.2 堆内存溢出
错误:OutOfMemoryError:Java heap space
5.4.3 堆内存诊断
-
jps工具
-
查看当前系统中有哪些Java进程
-
-
jmap工具
-
查看堆内存占用情况 jmap -heap + 进程id
-
-
jconsole工具
-
图形界面的,多功能的监测工具,可以连续监测
-
案例:用垃圾回收后,内存占用仍然很高
堆转储,用dump功能抓取目前占用内存最高的线程
5.5 方法区
5.5.1 方法区内存溢出
-
1.8以前会导致永久代内存溢出
Java.lang.OutOfMemoryError:PerGen space
-
1.8以后会导致原空间方法溢出
Java.lang.OutOfMemoryError:Metaspace
5.5.2 运行时常量池
-
常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
-
运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1+s2; String s5 = "a"+"b";
过程:
-
常量池中的信息,都会被加载到运行时常量池中,此时 a b ab都是常量池中的符号,还没有变成Java字符串对象
-
ldc #2 会把a符号变成“a"字符串对象(先在stringtable里找,发现没有,则在stringtable里创建“a”对象)
-
只有在使用时才会创建,且此时的StringTable是个hashtable结构,["a","b","ab" ]不能扩容
说明不是一开始就创建了,而是边执行边创建,发现stringtable里没有,才会创建。(长度是java.lang.String的长度)
-
在第四步,过程是 new Stringbuilder().append("a").append("b").toString() new String("ab"),因为new了所以在堆里,故s3 != s4;(s3在StringTable里,而s4在堆里)
-
s5执行时,javac在编译期间的游湖啊,结果已经在编译期确定为ab,不会再变化;而s4因为是引用变量,不确定会不会变化
5.5.3 StringTable特性
-
常量池中的字符串仅是符号,第一次用到时才变为对象
-
利用串池的机制,来避免重复创建字符串对象
-
字符串变量拼接的原理是StringBuileder
-
字符串常量拼接的原理是编译期优化
-
可以使用intern方法,主动将串池中还没有的字符串对象放入串池
String x = "ab"; String s = new String("a") + new String("b")//new String("ab");此时s仅存在于堆中 String s2 = s.intern();//将这个字符串对象尝试放入串池,如果有则不会放入,没有则放入串池,会把串池中的对象返回 (s2 == x)?// 是的,因为串池中已经有了,所以就是x (s == x)? //不是,因为是两个东西
5.5.4 StringTable位置
-
在1.8以后,就放入了heap中,因为放在永久代中,只有在父辈被回收的时候才能被回收,但是串池用的地方很频繁,容易造成内存不够
5.5.5 StringTable性能调优
-
调整 -Xx:StringTableSize=桶个数
因为StringTable底层是个哈希表,因此桶越多,越不容易出现Hash碰撞,性能越快
-
考虑将字符串对象是否入池
5.6 直接内存(directBuffer)
Direct Memory
-
常见于NIO操作时,用于数据缓冲区
-
分配回收成本较高,但读写性能高
-
不受JVM内存回收管理
-
使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用unsafe.freeMemory(base)方法
-
ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHander线程通过Cleaner的clean方法调用freeMemory来释放直接内存