目录
1.1.2 特点:是线程私有的【时间片】每个线程有自己的程序计数器
1.2.1 定义:Java Virtual Machine Stacks (Java 虚拟机栈)
0:引入
0.1:定义:
Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)
0.0.1 好处:
一次编写,到处运行
自动内存管理,垃圾回收功能
数组下标越界检查 多态
比较: jvm jre jdk
0.2 学习路线:
1: 内存结构
1.1 程序计数器
Program Counter Register 程序计数器(寄存器)
1.1.1 作用:是记住下一条jvm指令的执行地址
1.1.2 特点:是线程私有的【时间片】每个线程有自己的程序计数器
不会存在内存溢出
1.2 虚拟机栈
1.2.1 定义:Java Virtual Machine Stacks (Java 虚拟机栈)
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
(栈帧就是每个方法运行时 需要的内存:参数,局部变量,返回地址)
1.2.2 问题辨析:
1. 垃圾回收是否涉及栈内存?不会 --垃圾回收都是堆内存
2. 栈内存分配越大越好吗?
3. 方法内的局部变量是否线程安全?
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
//安全
public static void m1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//不安全
public static void m2(StringBuilder sb) {
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//不安全
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
1.2.3 栈内存溢出
栈帧过多导致栈内存溢出
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();
}
出现错误:
运行了22130次
java.lang.StackOverflowError
改变栈内存的大小
运行4173次就出现错误
栈帧过大导致栈内存溢出
1.2.4 线程运行诊断
案例1: cpu 占用过多
定位
1、用top定位哪个进程对cpu的占用过高
2、ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
3、 jstack 进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
案例2:程序运行很长时间没有结果
1.3 本地方法栈
c++/c编写的本地方法,本地方法使用的内存就是本地方法栈
1.4 堆
1.4.1 定义
Heap 堆
通过 new 关键字,创建对象都会使用堆内存
特点
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
1.4.2 堆内存溢出
例子:java.lang.OutOfMemoryError: Java heap space 运行了24次
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);
}
}
修改堆空间
运行17次就报错
1.4.2 堆内存诊断
/**
* 演示堆内存
*/
public class Demo1_4 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);
byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
System.out.println("2...");
Thread.sleep(20000);
array = null;
System.gc();
System.out.println("3...");
Thread.sleep(1000000L);
}
}
查看java进程
PS D:\wxxDownload\java\jvm> jps
26656 Demo1_4
30084 Launcher
22264
29148 Jps
34316 RemoteMavenServer36
查看堆内存的占用情况
PS D:\wxxDownload\java\jvm> jmap -heap 26656
经过垃圾回收之后
使用jconsole
还可以用
PS D:\wxxDownload\java\jvm> jvisualvm
1.5 方法区
1.5.1 定义:
Chapter 2. The Structure of the Java Virtual Machine (oracle.com)
1.5.2 组成
1.5.3 方法区内存溢出
1.5.4 运行时常量池
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
PS D:\wxxDownload\java\jvm\src\cn\itcast\jvm\t5> javac HelloWorld.java
PS D:\wxxDownload\java\jvm\src\cn\itcast\jvm\t5> javap -c -v -l -s HelloWorld.class
Classfile /D:/wxxDownload/java/jvm/src/cn/itcast/jvm/t5/HelloWorld.class
Last modified 2022-10-9; size 442 bytes
MD5 checksum 103606e24ec918e862312533fda15bbc
Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // hello world
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // cn/itcast/jvm/t5/HelloWorld
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 HelloWorld.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 hello world
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 cn/itcast/jvm/t5/HelloWorld
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public cn.itcast.jvm.t5.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 4: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
}
SourceFile: "HelloWorld.java"
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址
1.5.5 串池StringTable
特性:
位置: