一、程序计数器
二进制字节码(JVM指令)-> 解释器 -> 机器码 -> CPU
Program Counter Register 程序计数器(寄存器)
1.作用
记住下一条jvm指令的执行地址
2.特点
- 是线程私有的:每个线程都有自己的程序计数器
- 不会存在内存溢出
二、虚拟机栈
虚拟机栈:每个线程运行时所需要的内存
栈帧:每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
活动栈帧:对应着当前正在执行的那个方法,每个线程只能有一个
1.回收
垃圾回收不涉及栈内存,方法结束了自动回收
2.栈内存分配
-Xss1025k
linux默认1024kb
栈内存分配得越大,那线程数就会越少
3.方法内的局部变量是否线程安全
- 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
public class Demo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append(4);
sb.append(5);
sb.append(6);
new Thread(()->{
m2(sb);
}).start();
}
/**
* 线程安全
*/
public static void m1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
/**
* 线程不安全,参数是外部的
* @param sb
*/
public static void m2(StringBuilder sb) {
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
/**
* 线程不安全,返回值是外部的
* @return
*/
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
}
4.栈内存溢出
- 栈帧过多导致栈内存溢出
- 栈帧过大导致栈内存溢出
public class Demo1 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();
}
}
运行诊断:
- 用top定位哪个进程对cpu的占用过高
- 用ps命令进一步定位是哪个线程引起的cpu占用过高:
ps H -eo pid,tid,%cpu | grep 进程id
(线程编号为十进制) - 根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号:
jstack 进程id
(线程编号十六进制)
三、本地方法栈
给本地方法运行提供的内存空间,用native修饰的方法(C/C++编写)。
protected native Object clone() throws CloneNotSupportedException;
线程私有
四、堆
1.定义
通过 new 关键字,创建对象都会使用堆内存
特点:
- 线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
2.堆内存溢出
设置堆空间:-Xmx
设置为8m:-Xmx8m
/**
* 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
* -Xmx8m
*/
public class HeapTest {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a); // hello, hellohello, hellohellohellohello ...
a = a + a; // hellohellohellohello
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at jvm.heap.HeapTest.main(HeapTest.java:19)
27
3.堆内存诊断
- jps 工具:查看当前系统中有哪些 java 进程
- jmap 工具:查看堆内存占用情况
jmap - heap 进程id
- jconsole 工具:图形界面的,多功能的监测工具,可以连续监测
- jvisualvm
hongcaixia@hongcaixiadeMacBook-Pro visibility % jps
16784
8658 Launcher
8659 HeapMonitor
41622 Launcher
8711 Jps
67193 RemoteMavenServer36
67208 RemoteMavenServer36
28458 Launcher
91898 Launcher
5483 Launcher
67247 RemoteMavenServer36
67230 RemoteMavenServer36
hongcaixia@hongcaixiadeMacBook-Pro visibility % jmap - heap 8659
hongcaixia@hongcaixiadeMacBook-Pro heap % jconsole
堆dump:把堆这一刻的信息截取下来:
/**
* 演示查看对象个数 堆转储 dump
*/
public class HeapAnalysis {
public static void main(String[] args) throws InterruptedException {
List<Student> students = new ArrayList<>();
for (int i = 0; i < 200; i++) {
students.add(new Student());
// Student student = new Student();
}
Thread.sleep(1000000000L);
}
}
class Student {
private byte[] big = new byte[1024*1024];
}
五、方法区
1.定义
线程共享的,存储了与类结构相关的信息,包括类成员变量,方法数据,成员方法,构造器,运行时常量池等。
方法区在虚拟机启动时被创建,逻辑上是堆的组成部分;
方法区如果申请内存发现不够,也会导致内存溢出。
方法区是一种规范,具体的实现是永久代(使用的是JVM内存)和元空间(使用操作系统内存);不同厂商实现方式不同
2.结构
3.方法区内存溢出
- 1.8 以前会导致永久代内存溢出:java.lang.OutOfMemoryError: PermGen space
- 1.8 之后会导致元空间内存溢出:java.lang.OutOfMemoryError: Metaspace
设置元空间大小:-XX:MaxMetaspaceSize
-XX:MaxMetaspaceSize=8m
设置永久代大小:-XX:MaxPermSize=8m
/**
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
* -XX:MaxMetaspaceSize=8m
*/
public class MetaspaceTest extends ClassLoader { // 可以用来加载类的二进制字节码
public static void main(String[] args) {
int j = 0;
try {
MetaspaceTest test = new MetaspaceTest();
for (int i = 0; i < 10000; i++, j++) {
// ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
// 版本号, public, 类名, 包名, 父类, 接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回 byte[]
byte[] code = cw.toByteArray();
// 执行了类的加载
test.defineClass("Class" + i, code, 0, code.length); // Class 对象
}
} finally {
System.out.println(j);
}
}
}
3331
Exception in thread "main" java.lang.OutOfMemoryError: Compressed class space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
at jvm.metaspace.MetaspaceTest.main(MetaspaceTest.java:23)
/**
* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
* -XX:MaxPermSize=8m
*/
public class PermTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
PermTest test = new PermTest();
for (int i = 0; i < 20000; i++, j++) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
byte[] code = cw.toByteArray();
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.out.println(j);
}
}
}
六、运行时常量池
1.常量池
就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量(字符串,整数,布尔类型)等信息(给指令提供常量符号)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello,world");
}
}
hongcaixia@hongcaixiadeMacBook-Pro stringtable % javap -v HelloWorld.class
//=======================类的基本信息 start======================
Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/stringtable/HelloWorld.class
Last modified 2023-12-13; size 595 bytes
MD5 checksum b14eeec9676254a3a5f592e00cf1266c
Compiled from "HelloWorld.java"
public class jvm.stringtable.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//=======================类的基本信息 end=======================
//=======================常量池 start=======================
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello,world
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // jvm/stringtable/HelloWorld
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ljvm/stringtable/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 HelloWorld.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello,world
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 jvm/stringtable/HelloWorld
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
//=======================常量池 end=======================
//=======================类方法定义 start=======================
{
public jvm.stringtable.HelloWorld(); //类的无参构造
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/stringtable/HelloWorld;
public static void main(java.lang.String[]); //main方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
// ================虚拟机指令 start================
0: getstatic #2 // 获取一个静态变量 Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // 加载参数 String hello,world
5: invokevirtual #4 // 执行虚方法调用println Method java/io/PrintStream.println:(Ljava/lang/String;)V
// ================虚拟机指令 end================
8: return
LineNumberTable:
line 14: 0
line 15: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "HelloWorld.java"
hongcaixia@hongcaixiadeMacBook-Pro stringtable %
解析虚拟机指令:
0: getstatic #2 // 获取一个静态变量 Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // 加载参数 String hello,world
5: invokevirtual #4 // 执行虚方法调用println Method java/io/PrintStream.println:(Ljava/lang/String;)V
查表翻译:
-
#2
对应 Constant pool中的#2 = Fieldref #22.#23
-
#22
对应#22 = Class #29
-
#23
对应#23 = NameAndType #30:#31
-
#29
对应#29 = Utf8 java/lang/System
- 以此类推...
2.运行时常量池
运行时常量池,常量池是 *.class 文件中的,当该类被加载到内存时,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。