java虚拟机(jvm)的内存结构是作为中高级java开发者来说必须要知道的,因为只有了解了jvm的结构,才能更好的知道java代码在虚拟机里是怎么存放的,以及怎么执行的;对解决内存溢出等问题提供基础。
1.程序计数器
程序计数器只占一小块的内存区,它的存在和线程有很大关系,也就是说每一个线程都有自己的程序计数器,用于记录线程下一条要执行的指令,各个计数器互不影响,独立工作。如果当前线程正在执行一个Native方法,则程序计数器为空,只有正在执行java线程,程序计数器才不为空。
2.java虚拟机栈
java虚拟机栈也是线程私有的内存空间,它和java线程在同一时间创建,它保存方法的局部变量,部分结果,并参与方法的调用和返回。虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中存放了方法的局部变量表,操作数栈,动态连接方法和返回地址等信息。每一个方法的调用都伴随着战阵的入栈操作,每一个方法的返回都是出栈的操作,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,所需要的栈空间大小也会比较多。
方法的嵌套调用也会使局部变量表空间变大,因此,栈的大小会影响方法嵌套调用的次数,栈越大,所允许的嵌套调用次数越多。方法还未结束时,方法中的变量也会失效(超过变量的作用域就会失效),在这个函数体内,没有足够多的变量复用该变量所占的字,那么在整个函数体中,该失效变量引用的这块内存区域是不会被回收的。当方法结束时,该方法的栈帧就会被销毁,局部变量表也会被销毁,局部变量表中的变量所引用的内存空间就会被回收。
虚拟机栈会抛出两种异常,一种是java.lang.StackOverflowError,产生原因是线程在计算过程中,请求的栈深度大于最大可用的栈深度,也就是在单线程中,线程的栈容量太小,解决方法是调大xss参数的值。另一种是java.lang.OutOfMemoryError: unable to create new native thread,原因是多线程情况下创建了太多的线程,每个线程分配的栈容量越大越容易报错,解决方法是调小堆容量,调小栈的容量,减少线程的数量。
3.本地方法栈
本地方栈用于管理本地方法的调用,本地方法不是java实现的,而是使用C实现的,在HotSpot虚拟机中,不区分本地方法栈和虚拟机栈。
4.java堆
java堆是线程共享的,java堆分为新生代和老年代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存的足够长,老年对象就会被移入老年代。新生代又可进一步细分为一个eden区和两个S区,大部分对象刚创建是会放在eden区。S0和S1为survior空间,也就是说存放其中的对象至少经过一次垃圾回收,并且还活着,如果在两个S区的对象到了指定年龄仍未被回收,则有机会进入老年代。
由于不断创建对象实例,当对象的数量超出堆的最大容量限制之后,会报内存溢出异常java.lang.OutOfMemoryError: Java heap space,解决方法是先判断是内存泄漏还是内存溢出,如果是内存泄漏,查看GCroot引用链,找出为什么没有被回收的原因。如果是内存溢出,根据xmx和xms配置的参数对比物理内存是否可以调大,当fullGC次数达到一定量,并且平均时间达到一定比例时,可能会报java.lang.OutOfMemoryError: Java heap space:GC over head limit exceeded。
5.方法区
在HotSpot中方法区也叫永久代,与堆类似,也是被jvm中所有的线程共享,是一块独立于java堆的内存空间,方法区主要保存的信息是类的元数据,包括类的类型信息,常量池,域信息,方法信息。方法区中的对象也是可以被回收的,一个是对常量池的回收,另一个是永久区对类元数据的回收。在 HotSpot 虚拟机中,很多人都把方法区成为永久代
,默认最小值为16MB,最大值为64MB。其实只在 HotSpot 才存在方法区,在其他的虚拟机没有方法区这一个说法的。本文是采用 Hotspot,所以把方法区介绍了。
方法区的异常一般有以下几个,一是
java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
该错误是由于代码在运行时创建了大量的常量,超出了常量池的最大容量。解决方法是通过修改-XX:PermSize和-XX:MaxPermSize参数来修改方法区大小,从而修改常量池大小;
另一个是
java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass(Native Method)
该错误是由于classLoader动态加载了大量的类信息,超出方法区上限。解决方法是通过修改-XX:PermSize和-XX:MaxPermSize参数来修改方法区大小,从而修改常量池大小;