JVM 内存模型
- 程序计数器:较小的内存空间,线程所执行的字节码的行号指示器,线程私有,不会抛出内存溢出异常
- 虚拟机栈:方法在执行的同时都会创建一个栈帧(栈桢大小缺省为 1M,可用参数 –Xss 调整大小,例如 -Xss256k),进行压栈,执行完弹栈。线程私有,会有两种异常,StackOverflowError,OutOfMemoryError
- 本地方法栈:和虚拟机栈相似,不同的是本地方法栈为 Native 方法服务。
- 堆:存放对象实例,可细分为新生代和老年代,再细分可分为 Eden 空间、From Survivor 空间、To Survivor 空间。线程共享,抛出 OutOfMemoryError -Xms:堆的最小值;-Xmx:堆的最大值;-Xmn:新生代的大小;-XX:NewSize;新生代最小值;-XX:MaxNewSize:新生代最大值;
- 方法区:存储已被虚拟机加载的类信息、常量、静态变量,线程共享,抛出 OutOfMemoryError
不同版本 jdk 内存区域的变化
jdk1.6 运行时常量池在方法区中
jdk1.7 运行时常量池在堆中
jdk1.8 多出个元空间的概念
jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8 以后:-XX:MetaspaceSize;-XX:MaxMetaspaceSize
jdk1.8 以后大小就只受本机总内存的限制
如:-XX:MaxMetaspaceSize=3M 运行时常量池:方法区的一部分
栈上分配
虚拟机提供的一种优化技术,基本思想是,对于线程私有的对象,将它打散分配在栈上,而不分配在堆上,好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,可以提高性能 -server JVM 运行的模式之一, server 模式才能进行逃逸分析 -XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)-XX:+PrintGC:打印 GC 日志 -XX:+EliminateAllocations:标量替换(默认打开)-XX:-UseTLAB 关闭本地线程分配缓冲
对象的分配
1. 类加载
2. 分配内存:指针碰撞和空闲列表,为了并发安全,CAS 和 TLAB 解决,TLAB 是为每一个线程分配一块私有的区域。
3. 对内存里的变量进行初始化,int 为 0,boolean 默认 false 等
4. 设置对象的信息到对象头,包括类的元数据信息,对象的哈希码,对象的 GC 分代年龄等
5. 刚刚开始初始化对象的
内存布局
在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对对象的大小必须是 8 字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。
堆溢出
参数:-Xms5m -Xmx5m -XX:+PrintGC
出现 java.lang.OutOfMemoryError: GC overhead limit exceeded 一般是(某个循环里可能性最大)在不停的分配对象,但是分配的太多,把堆撑爆了。
出现 java.lang.OutOfMemoryError: Java heap space 一般是分配了巨型对象
栈溢出
参数:-Xss256k
java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了要考虑是否有无限递归。
虚拟机栈带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归 (循环来实现) 都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。