JVM
JVM内存
1.程序计数器 program counter register
每个线程都有一个程序计数器 指向方法区中的下一个将要执行的方法字节码 由执行引擎读取下一条指令
2.本地方法栈 native method stack
native method stack中登记native方法 在execution engine执行时加载native libraies
本地方法栈与虚拟机栈基本类似 区别在于虚拟机栈为虚拟机执行的java方法服务 而本地方法栈则是为native方法服务
3.方法区 method area
用于存储虚拟机加载的 静态变量 常量 类信息 运行时常量池
默认最小值为16MB 最大值为64MB 可通过-XX:PermSize
和-XX:MaxPermSize
参数设置
HotSpot虚拟机把GC分代收集扩展至方法区 或者说使用永久代永久代(Permanent Generation)来实现方法区
4.栈 JVM stack
每个方法被执行时 会创建一个’栈帧’用于存储局部变量表(包括参数) 操作栈 方法出口等信息
每个方法从调用到执行完整个过程 对应一个栈帧在虚拟机栈中从入栈到出栈的过程
局部变量表 存放编译器已知的八种基本类型数据和对象引用
其中long和double为64位长度 占用2个局部变量空间 其余数据类型占1个局部变量空间
局部变量表所需的内存空间在编译期间完成分配 在运行期间栈帧不会改变局部变量表的大小空间
栈的生命期是跟随线程的生命期 线程创建时创建 线程结束栈内存也就释放 为线程私有
5.堆 Java heap
所有的对象实例以及数组都要在堆上分配 堆是虚拟机所管理内存中最大一块
堆为所有线程共享 在虚拟机启动时创建 GC的主力区域
结构
- 新生代
Eden区 两个Survivor区 分别叫From和To 默认比例Eden占8 一个Survivor占1 - 年老代
- 永久代(HotSpot有)
新创建对象 小对象伊地园(Eden)区 大对象直接进入年老代 大对象是指 长字符串 数组等
在开发中应尽量避免使用大对象 大对象会导致年老代空间提前触发Full GC
Full GC速度一般较慢 且Full GC会导致Stop The World
年轻代中对象80%以上基本都是朝生夕死 年轻代垃圾回收使用复制算法
GC开始时 对象只会存在于Eden区和名为From区 To区是空的
进行时 Eden区中所有存活的对象会被复制到To区 而在From区中
仍存活的对象会根据他们的年龄值来决定去向 年龄达到年龄阈值的对象会晋升到年老代中
未达到阈值的对象会被复制到To区 GC完成后 Eden区和From区已被清空
此时 From和To会交换角色 新的To为上次GC前的From 新的From为上次GC前的To
名为To的Survivor区域不使用时一直为空
Minor GC会一直重复此过程 直到To区被填满 此时会将所有对象移动到年老代中
永久代 可回收条件
- 该类的实例都被回收
- 加载该类的ClassLoader已经被回收
- 该类不能通过反射访问到其方法 而且该类的Class对象没有被引用
JDK1.6及之前 常量池分配在永久代 默认大小是4m 会导致性能问题和OOM
JDK1.7 存储在永久代的部分数据已转移到Java heap或native heap
JDK8 HotSpot已经不再使用方法区作为永久代 永久代被移到与一个堆不相连的本地内存区域 元空间
元空间的最大可分配空间就是系统可用内存空间
- 直接内存 direct memory
直接内存并不是JVM管理的内存 直接内存为操作系统内存 如 共有4G内存 JVM占用1G
其余3G为直接内存
JDK中有一种基于通道和缓冲区的内存分配方式 由c语言实现的native函数库分配在直接内存中
用存储在JVM堆中的DirectByteBuffer来引用
直接内存受到本操作系统内存限制 可能会出现OutOfMemoryError异常