JVM内存模型
程序计数器: 较小的内存空间, 线程所执行的字节码的行号指示器,线程私有,不会抛出内存溢出异常
虚拟机栈: 方法在执行的同时都会创建一个栈帧(栈桢大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k),进行压栈,执行完弹栈。线程私有,会有两种异常, StackOverflowError, OutOfMemoryError
本地方法栈:和虚拟机栈相似, 不同的是本地方法栈为Native方法服务。
堆: 存放对象实例, 可细分为新生代和老年代,再细分可分为Eden空间、From Survivor空间、To Survivor空间。线程共享,抛出OutOfMemoryError -Xms:堆的最小值;-Xmx:堆的最大值;-Xmn:新生代的大小;-XX:NewSize;新生代最小值;-XX:MaxNewSize:新生代最大值;方法区: 存储已被虚拟机加载的类信息、常量、静态变量,线程共享, 抛出OutOfMemoryError
jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本机总内存的限制
如:-XX:MaxMetaspaceSize=3M运行时常量池:方法区的一部分
不同版本jdk内存区域的变化
jdk1.6运行时常量池在方法区中
jdk1.7运行时常量池在堆中
jdk1.8多出个元空间的概念,
栈上分配
虚拟机提供的一种优化技术,基本思想是,对于线程私有的对象,将它打散分配在栈上,而不分配在堆上,好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,可以提高性能-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 一般的方法调用是很难出现的,如果出现了要考虑是否有无限递归。
虚拟机栈带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。