深入理解JAVA虚拟机(概念泛谈)

来源:互联网 发布:淘宝卖守门员手套 编辑:程序博客网 时间:2024/06/14 17:00

Java虚拟机在执行java程序的过程中,会把它所管理的内存分为若干不同的数据区,如下图所示:
jvm运行时数据区

程序计数器:跟计算机中的程序计数器类似,在jvm中,则是以线程的维度来理解。可以看作是当前线程所执行的字节码的行号指示器。它属于线程私有。

虚拟机栈:也是线程私有,生命周期与线程相同。每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储变量表、操作数栈、动态链接、方法出口等。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

网上经常看到的说法,jvm内存区分为堆内存和栈内存,这里的栈就是虚拟机栈,但是jvm内部的划分远比这复杂。
虚拟机栈中局部变量表主要存放:基本数据类型(int long byte等) 对象引用等信息。
局部变量表所需的内存空间会在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,方法运行期间是不会改变局部变量表的大小。
所以,如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常,jdk5.0之后的默认-xss参数是1m,不过有些应用还是配置的256k,因此会经常出现该异常,只需要修改应用服务器的jvm参数 -xss 1m即可;如果配置已经足够,还是出现该异常,就需要判断代码里面是否有递归调用了。

本地方法和本地方法栈:与虚拟机栈类似,区别是本地方法栈是虚拟机使用到的native方法服务。

java堆: jvm所管理的内存当中最大的一块。是被所有线程共享的一块内存区域,在虚拟机启动时创建。 堆中主要存放的是对象实例,几乎所有的对象实例都在这里分配内存。
因此,堆 是垃圾收集器管理的主要区域,从内存回收的角度来说,因为收集器一般都是基于分代收集算法,堆可以分为新生代、老年代;young主要包括Eden、From Survivor(S0)、To Survivor(S1)等。

方法区:与堆一样,是各个线程共享的内存区域,主要用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
我们常见网上可以看到它的另外一个名词:Perm区,也就是持久代、或者永久代。
按照本人的理解,它应该是非堆,应该与heap区别开来。
但是其实,Perm区不等于方法区,只不过是HotSpot虚拟机的设计团队选择把GC分代收集扩展到方法区,这样GC就可以像回收heap区一样来管理方法区。但是很多其他虚拟机比如IBM I9是不存在永久代的概念的。
所以,我们最好还是以方法区来理解,在jdk1.7中,已经把原本放在永久代的字符串常量池移出。
jvm对方法区的限制比较松,不需要连续的内存,大小可以扩展,比如通常的服务器设置: -XX:PermSize=256m -XX:MaxPermSize=512m ;另外,还可以选择不实现垃圾收集。
一般正常的应用,perm区是不会增长的,笔者也遇到过一些奇葩的现象;比如使用Xstream转xml的时候,Xstream版本过低,每一次创建对象,都会重新加载该类到perm区,导致perm区持续增长,增长到maxPermSize时,也会进行fullgc,但是一半fullgc很难把perm区降下来,因此最后会抛出permgen space异常。

对象的创建: 从我们平常使用的角度来说,创建对象无非就是一个关键词 new(普通java对象,不包括数组和Class对象等)
通常,jvm遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化。如果没有,那必须先执行类加载过程。(会在后面的章节中介绍)
检查完之后,jvm给新生对象分配内存。
接下来,jvm要给对象进行必要的一些设置,比如这个对象是哪个类的实例,如何找到类的元数据,对象的GC分代年龄等,这些都是存放在对象头中。
对象在内存中存储的布局可以分为三块:对象头、实例数据和对齐填充。

对象的访问有两种方式:通过句柄访问对象和通过直接指针;
它们的差别是:前者引用存储的是对象的句柄地址,句柄中包含对象实例数据和类型数据各自的具体地址信息。而后者引用存储的是对象地址,该地址中包括对象实例数据和指向对象类型数据的指针。
目前就Sun HotSpot而言,是使用直接指针访问对象。它的好处就是速度更快,节省了一次指针定位的时间开销。

0 0
原创粉丝点击