导言:java是基于一门虚拟机的语言,所以了解并且熟知虚拟机运行原理非常重要。
先整体看一张java虚拟机技术图:
一、堆
1、方法区,Method Area,主要存放已被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。
又称为永久代。
比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息,
如果生成大量的动态类,造成堆内存不足,则会抛出OutOfMemoryError异常。
又比如tomcat把jsp编译成servlet类的时候,也会造成这种情况。
2、运行时的常量池:是方法区的一部分。用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。常量池有动态性,可以添加数据。比如string常量池,imtern()。其实就是享元模式的一种实现。
运行时常量池溢出:比如一直往常量池加入数据,就会引起OutOfMemoryError异常
3、java堆,java heap。是java虚拟机所管理的内存中最大的一块。是所有线程共享的一块内存区域,在虚拟机启动时创建。存放对象实例,数组。而垃圾收集器管理的主要区域也是这个区域,也被称为GC堆。
分为新生代和老年代。
当对象大量生成造成内存不足时,也会抛出异常。
二、栈
1、虚拟机栈,是线程私有的,与线程的生命周期相同。描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存放局部变量表(基本类型、对象引用)、操作数栈动态链表、方法出口等信息。
如果一个线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常, 比如递归调用。
如果线程生成数量过多,无法申请足够多的内存时,则会抛出OutOfMemoryError异常。比如tomcat请求数量非常多时,设置最大请求数。
2、本地方法栈,Native Method Stack。也就是调用虚拟机使用到的Native方法服务。也会抛出以上两种异常。
3、程序计算器。当前线程所执行的字节码的行号指示器。
三、直接内存
并不是虚拟机运行时数据区的一部分。JDK1.4引入的nio,可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DiectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中提高性能,因为避免了java堆和Native堆中来复制数据。
四、小结
虚拟机内运行时数据区整体就是上面所述,所有java对象分配回收等一系列的操作都在上述数据区,我们自己也根据虚拟机规范写一个java虚拟机,也要遵循以上原则。