1. JVM内存区域分配
-
堆
Java堆是各线程共享的内存区域,在JVM启动时创建,这块区域是JVM中最大的, 用于存储应用的对象和数组,也是GC主要的回收区,一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:新生代、老年代、永久代。
说明:
Jdk1.6及之前:常量池分配在永久代 。
Jdk1.7:有,但已经逐步“去永久代” 。
Jdk1.8及之后:无永久代,改用元空间代替(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)。 -
栈(线程栈)
Java栈是线程私有的,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致。基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
每个方法执行的时候都会创建一个栈帧,栈帧中主要存储3类数据:
局部变量表:输入参数和输出参数以及方法内的变量;
栈操作:记录出栈和入栈的操作;
栈帧数据:包括类文件、方法等等。 -
栈帧
一个方法对应一个栈帧内存空间 每个方法都有独立的栈帧内存空间,
栈数据结构: 先进后出销毁。
栈帧内部细节结构: 局部变量表、操作数栈、动态链接、方法出口
栈帧 就是每个方法需要的运行时内存空间 -
每个运行时所需要的内存,称作为虚拟机栈
-
每个栈有多个栈帧组成,对应着每次方法调用时占用的内存
-
每个线程只能有一个活动的栈,对应着当前正在执行的方法。
- 程序计数器
记录当前线程执行下一行指令的执行地址. - 本地方法栈
本地方法栈和JVM栈发挥的作用非常相似,也是线程私有的,区别是JVM栈为JVM执行Java方法(也就是字节码)服务,而本地方法栈为JVM使用到的Native方法服务。它的具体做法是在本地方法栈中登记native方法,在执行引擎执行时加载Native Liberies.有的虚拟机(比如Sun Hotpot)直接把两者合二为一。
也就是 java调用c语言代码 jni技术
2. 栈内存溢出
栈帧调用过多内存溢出 (方法递归调用)
/**
* 栈内存溢出
*
* -Xss256k
* @author tostyle
* @date 2022-05-17 9:15
*/
public class StackTest {
private static int count;
public static void main(String[] args) {
stack01();
}
private static void stack01() {
count++;
System.out.println("count:" + count);
stack01();
}
}
3.堆内存溢出
在申请内存的时候,内存不足 产生堆内存溢出
/**
*
* -Xmx8m
* @author tostyle
* @date 2022-05-17 9:20
*/
public class HeapTest {
public static void main(String[] args) {
int i = 0;
try {
ArrayList<String> strings = new ArrayList<>();
while (true) {
strings.add("mayikt");
i++;
}
} catch (Exception e) {
}
}
}
4.堆内存泄漏
java.lang.OutOfMemoryError: GC overhead limit exceeded,这种机制也会有一些问题,就是被占用的内存,经过多次长时间的GC操作都无法回收,导致可用内存越来越少,俗称内存泄露,JVM就会报java.lang.OutOfMemoryError: GC overhead limit exceeded错误。
/**
* 堆内存泄漏
* -Xmx3M -Xms3M
* @author tostyle
* @date 2022-05-17 9:25
*/
public class HashMapMemoryLeak {
public static void main(String[] args) {
HashMap<HashKey2, Integer> map = new HashMap<HashKey2, Integer>(1000);
int counter = 0;
while (true) {
//循环插入新对象 new出很多很多内存地址不等的对象但是
HashKey2 p = new HashKey2("mayikt", "644064779");
map.put(p, 1);
counter++;
if (counter % 1000 == 0) {
System.out.println("map size: " + map.size());
System.out.println("运行" + counter
+ "次后,可用内存剩余" + Runtime.getRuntime().freeMemory() / (1024 * 1024) + "MB");
}
}
}
}
class HashKey2 {
private final String id;
private String name;
public HashKey2(String name, String id) {
this.name = name;
this.id = id;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return id.hashCode();
}
/**
* hashMap 不管怎么new 多少次 只会key 只会引入一次 不会继续添加。
*
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof HashKey2) {
return name.equals(((HashKey2) obj).name) && id.equals(((HashKey2) obj).id);
}else {
return false;
}
}
}
5. 堆内存诊断工具
- Jps工具 - 查看当前系统有那些java进程
Jps
jmap -heap 进程id