运行时数据区
运行时数据区也就是jvm在运行时产生的数据存放的区域,这块区域就是jvm的内存区域,也称为jvm的内存模型–JMM
程序计数器
作用:记住下一条jvm指令的执行地址
特点:
1.线程私有
2.唯一一个不会存在内存溢出
是一块就较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
处于线程独占区
如果线程执行的是java代码,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是native方法,这个计数器的值为undefined
此区域是唯一一个在java虚拟机规范中没有规定任何OOM情况的区域
虚拟机栈
1.每个线程运行时所需要的内存,称为虚拟机栈
2.每个栈有多个栈帧组成,对应着每次方法调用时所占用的内存
3.每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程栈(虚拟机栈):执行一个方法就会在线程栈中创建一个栈帧
栈帧:
1.局部变量表: 存放方法中的局部变量,存放编译期可知的各种基本数据类型,引用类型,returnAddres类型
2.操作数栈:用来存放方法中要操作的数据
3.动态链接:存放方法名和方法内容的映射关系,通过方法名找到方法内容
4.方法出口:记录方法执行完成后调用此方法的位置
问题辨析:
1.垃圾回收是否涉及栈内存
不涉及,
2.方法内的局部变量是否线程安全
如果方法内局部变量没有逃离方法的作用域,他是线程安全的。
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
栈内存溢出
1.栈帧过多导致栈内存溢出
2.栈帧过大导致栈内存溢出
线程运行诊断
案例1;cpu占用过多
nohup java com.yy.jvm.Demo01 &
# 第一步:使用 top 命令查看哪个进程占用CPU过高
top
# 第二步:查看某个进程中线程的CPU使用率, ps H -eo pid,tid,%CPU, | grep 进程ID
ps H -eo pid,tid,%CPU, | grep pid
# 第三步 列出某个进程下的所有线程, jstack 进程ID ,查看的线程都是16进制的
# 可以根据线程ID找到有问题的线程,进一步定位到问题代码的源代码行号
jstack pid
案例2:程序运行很长时间没有结果
jstack 进程ID
本地方法栈
线程私有, 本地方法产生的数据
Heap 堆
通过 new 关键字,创建对象都会使用堆内存
- 特点:
1.线程共享的,堆中对象都需要考虑线程安全的问题
2.有垃圾回收机制
堆内存诊断
1.jps工具
- 查看当前系统中有哪些java进程
2.jmap工具
- 查看堆内存占用情况(查看某个时刻的堆内存占用情况) -heap 进程ID
jmap -heap pid
3.jconsole工具
- 图形界面的,多功能的检测工具,可以连续监测
4.jvisualvm
案例
- 垃圾回收后,内存占用仍然很高
方法区
- 1.8以前会导致永久代内存溢出
- 1.8之后会导致元空间内存溢出
运行时常量池
- 常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名、参数类型、字面量等信息。
- 运行时常量池:常量池是*.class文件中的。当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号地址变为真实地址
StringTable
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是 StringBuilder(1.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
注:StringBuilder 的 toString() 的调用,实际上调用的是 new String() 的方法,不会在常量池中创建字符串对象(动态拼接的字符串对象不会放在字符串常量池)
package com.yy.demo;
// StringTable ["a","b","ab"] hash table结构,不能扩容
public class Demo_001 {
// 常量池中的信息,都会被加载到运行时常量池,这是 a b ab都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 “a” 字符串对象
// ldc #3 会把 b 符号变为 “b” 字符串对象
// ldc #4 会把 ab 符号变为 “ab” 字符串对象
public static void main(String[] args) {
String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1+s2; // new StringBuilder().append("a").append("b").toString() > new String("ab");
// System.out.println(s3 == s4); // false
String s5 = "a" +"b"; // javac 在编译期间的优化,结果已经在编译期确定为ab
}
}
package com.yy.demo;
public class Demo_003 {
public static void main(String[] args) {
// 堆 new String("a") new String("b") new String("ab");
// 堆区,new StringBuilder().append("a").append("b").toString() -> new String("ab")
String s = new String("a") + new String("b");
// 将这个 "字符串对象" 尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回
// ["a","b"]
String s2 = s.intern() ;
// ["a","b","ab"]
System.out.println(s == s2); // true
System.out.println(s == "ab"); // true
}
}
StringTable 位置
StringTable 垃圾回收
StringTable 性能调优
- 调整 -XX:StringTableSize=桶个数
- 考虑将字符串对象是否入池
直接内存
操作系统内存
- 常见于NIO操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受JVM内存回收管理
package com.yy.directmemory;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class Demo_001 {
public static String FROM = "E:\\images\\rhel-server-7.4-x86_64-dvd.iso";
public static String TO = "D:\\test\\rhel-server-7.4-x86_64-dvd_new.iso";
public static int _1MB = 1024 * 1024;
public static void main(String[] args) {
directBuffer();
io();
}
private static void directBuffer(){
Long start = System.nanoTime();
FileChannel from = null;
FileChannel to = null;
try {
from = new FileInputStream(FROM).getChannel();
to = new FileOutputStream(TO).getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1MB);
while (true){
int read = from.read(byteBuffer);
if(read == -1){
break;
}
byteBuffer.flip();
to.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(from != null){
from.close();
}
if(to != null){
to.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Long end = System.nanoTime();
System.out.println("directBuffer 用时:" + (end - start) / 10000_000.0);
}
private static void io() {
Long start = System.nanoTime();
FileInputStream from = null;
FileOutputStream to = null;
try{
from = new FileInputStream(FROM);
to = new FileOutputStream(TO);
byte[] buf = new byte[_1MB];
while (true){
int read = from.read(buf);
if(read == -1){
break;
};
to.write(buf,0,read);
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if(from != null){
from.close();
}
if(to != null){
to.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
long end = System.nanoTime();
System.out.println("IO 用时:" + (end - start) / 10000_000.0);
}
}
分配和回收原理
- 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
- ByteBuffer 的实现类内部,使用了Cleaner(虚引用)来检测 ByteBuffer 对象,一旦ByteBuffer对象被垃圾回收,那么就会由 ReferenceHandle 线程通过 Cleaner 的clean 方法调用 freeMemory 来释放直接内存。