JVM的内存结构
内存结构组成部分:
- 程序计数器 PC Register
- 虚拟机栈 JVM Stacks
- 本地方法栈 Native Method Stacks
- 堆 Heap
- 方法区 Method Area
一个程序到cpu执行的过程:
java程序-----(编译)----->.class文件[二进制字节码]-----(解释器解释)----->机器码-----(可执行)----->cpu执行
程序计数器
-
作用: 记住下一条将要执行的指令的地址;
-
特点:
- 程序计数器是线程私有的,每一个线程都有自己独立的程序计数器;
- 程序计数器不存在内存溢出;
虚拟机栈(线程栈)
-
作用: 线程运行所需要的内存空间;
-
每个栈由多个栈帧(Frame)组成,对应着每次调用放法时所占用的内存;
栈帧:
- 每个方法运行时所需要的内存(方法参数,局部变量,返回地址);
-
每个线程只能有一个活动栈帧,对应着当前正在执行的方法;(根据栈的特性可知,该活动栈帧就是虚拟机栈顶部的栈帧)
垃圾回收是否涉及栈内存?
不涉及,栈帧运行结束后就自动弹出,虚拟机栈内存空间会自动释放不需要垃圾回收操作;
栈内存分配越大越好吗?
否; 栈内存分配得越大,一定程度上会减少同时运行的线程的数量,每个线程栈空间变大,但是总的内存空间不变,线程数量减少.
linux,macOs,Oracle Solaris系统和默认分配栈内存为1024KB(1M);
Windows根据虚拟机内存动态默认分配;
手动设置栈内存大小的方法: -Xss256k 设置为256k
方法内的局部变量是否是线程安全的?
完全属于某个方法私有的变量是安全的(该变量没有逃离方法的作用范围);
通过参数传入,或者作为返回值返回的引用对象需要考虑线程安全问题,因为除了该方法外的其他方法/线程可以访问到该引用对象,就有可能会同时对该变量进行操作.
**栈内存溢出: **
异常报错信息:
- java.lang.StackOverFlowError
产生原因:
- 栈帧过多导致栈内存溢出
- 例子: 没有合理的递归结束条件,递归无限的调用自己;
- 栈帧过大(比较少)
本地方法栈
不是由java代码编写的方法 (由C或者C++实现的native方法) 运行时所需要的栈空间就是本地方法栈.
Object中很多方法就是本地方法:
clone()
notify()
hashCode()
notifyAll()
wait()
堆
堆的定义:
- 通过new关键字创建的对象都会使用堆内存
堆的特点:
- 他是所有线程共享的,堆中对象都需要考虑线程安全问题
- 由垃圾回收机制
**堆内存溢出: **
异常报错信息:
- java.lang.OutOfMemoryError: Java heap space
设置堆内存大小(一般默认4G):
- -Xmx8m 更改为8m
方法区
定义:
- 存储类的结构的信息的内存区域, 如: 运行时常量池,类的成员变量,方法数据,成员方法和构造器,类加载器等;
- 运行时常量池: [StringTable详解]( (1条消息) StringTable详解_百里屠苏的博客-优快云博客_stringtable )
- jdk1.6以前运行时常量池包括 字符串常量池StringTable 和 其他组成部分;
- jdk1.7以后,字符串常量池StringTable不属于运行时常量池,StringTable单独放在堆中.
特点:
- 所有线程共享的区域
- 在虚拟机启动时开始运行
hotspot jdk1.6以前,方法区使用的是堆内存空间,叫做永久代;
jdk1.8后,方法区移到了系统内存中,不再放在JVM管理的内存中,叫做元空间,字符串常量池依然放在堆内存.
方法区内存溢出
设置方法区内存大小:
- 1.8以前的永久代: -XX:MaxPermSize=8m 设置最大内存为8m
- 1.8以后的元空间: -XX:MaxMetaspaceSize=8m 设置最大内存为8m
jdk1.8的元空间使用的时系统内存,系统内存较大, 所一不容易导致内存溢出.但是溢出的理论存在,将元空间的最大空间设置小一些的话可以造成溢出.
运行时常量池
常量池:
- 就是一张.class文件的信息表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量的信息;
运行时常量池:
- 当一个.class文件被加载时,他的常量池信息就会放入到运行时常量池,并且把里面的符号地址转换为真实地址;
-
class文件的信息被加载到运行时常量池的时候,文件中定义的对象(字符串对象等等)都只是一些符号,不是真正的对象.当指令真正执行到的时候才会转换成真正的对象.指令没执行到这些符号就不会变成对象.(可以理解为懒加载)
-
常量池中的字符串符号转换为真正的对象后,会在**StringTable(串池)**中找有没有这个字符串对象,如果发现串池中有,那么就直接使用串池中的对象,如果没有,就将该对象添加到串池中.
当指令真正执行到的时候才会转换成真正的对象.指令没执行到这些符号就不会变成对象.(可以理解为懒加载)**
- 常量池中的字符串符号转换为真正的对象后,会在**StringTable(串池)**中找有没有这个字符串对象,如果发现串池中有,那么就直接使用串池中的对象,如果没有,就将该对象添加到串池中.