您的位置:首页 > 编程语言 > Java开发

Java JVM:内存结构与参数设置

2016-11-24 09:38 204 查看


如图,jvm内存区域分为 pc寄存器,jvm虚拟机栈,本地方法栈,jvm方法区,jvm方法堆,其中pc寄存器,jvm方法栈,本地方法栈属于线程私有,每个线程启动时都会创建一个pc寄存器,jvm虚拟机栈,本地方法栈。

pc寄存器:存放下一条指令在方法中的偏移量。也可以看作是线程所执行的字节码的行号指示器,字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的指令。

jvm虚拟机栈: jvm虚拟机栈主要由栈帧来组成。栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,栈帧存储了方法的局部变量表,操作数栈,动态连接和方法的返回地址等信息。

操作数栈:



对应的字节码如下:



释义:从上图中我们可以看出例如byte x = 12;在jvm中对应这两个指令

bipush,bipush指令用于将一个byte作为一个整型数字插入到操作数栈中,这里将12插入到操作栈。

istore_1,istore_1指令从操作数栈中弹出结果,并把他存储到局部变量区索引为1的位置。

是istore_<n>指令组中的一个,用于将一个整型数字存储到局部变量表中,<n>代表的是局部变量变量表中的存储位置,

同时只能为0,1,2,3;超过3了就只能使用istore指令。

注意:操作栈本身不是用来存储数据的,而是用局部变量表来储存。局部变量通过索引寻址。

动态链接库:java是一种动态链接的语言,编译时如果发现其他类方法的调用或对其他类字段的引用的语句,

记录进class文件中的只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

class文件中的“常量”内容很丰富,这些常量集中在class中的一个区域存放,一个接一个,这里就是常量池。

在jvm中,类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载,验证,准备,解析,

初始化,使用和卸载7个阶段。而解析阶段即是虚拟机常量池的符号引用替换为直接引用的过程。

符号引用包含:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符,如:CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info,符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。

在java中,一个java类将会被编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替

直接引用包含:直接指向目标的指针,相对偏移量,一个能间接定位到目标的句柄。直接引用是和虚拟机的布局相关的,

同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经被加载到内存中了。

每一个方法从调用开始到执行完成的过程,就对应一个栈帧在虚拟机里面从入栈到出栈的过程。

当出现无限递归这种情况下,栈帧就可能会过多的创建,从而导致栈内存溢出。

无限递归():

public class Test {
int num=1;
public void testStack(){
num++;
this.testStack();
}

public static void main(String[] args){
Test  t  = new Test ();
t.testStack();
}
}


通常情况下的“无限递归”:

private void recursion (Path path) {
FileStatus[] children = fs.listStatus (path);
for(FileStatus child : children){
if(child.isDir()){
recursion(child.getPath());
}
else{
…… //执行文件处理代码
}
}
}


当目录的层次很深,每一层递归累积起来逐渐将jvm的栈空间撑爆。

修改后的代码:
Stackpathstack = new Stack();
for(pathstack.push(fs.getFileStatus(path));  !pathstack.empty();){
FileStatus cur = pathstack.pop();
FileStatus[] children = fs.listStatus(cur.getPath());
for(int i = 0; i < children.length; i++) {
final FileStatus child = children[i];
if (child.isDir()) {
pathstack.push(child);
}
else {
…… //执行文件处理代码
}
}
}
本地方法栈:主要用来支持native方法,记录native方法的调用状态。可以把native方法看成java调用非java方法的接口。
主要用于允许java和其他语言,比如c语言进行交互。

jvm方法区:主要存储已经加载的类的信息,比如构造函数的信息,方法的信息,常量的信息。

Class对象提供的getXXX()方法取得的类的信息就是从jvm方法区中得到。
ps:jvm方法区是永久代的一个子集,常量池也是放在jvm方法区中。

jvm堆:主要目的是用来存放数组和对象。同时,jvm堆也是内存溢出和垃圾回收的主要区域。

JDK7堆内存结构:
堆内存结构图



如图,在jvm堆中,又分为新生代,老年代,新生代,又分为一个eden区域和两个Survior区域。默认比例为8:1:1,也就是说可用内存为90%(其中一个survior为空)。

JDK8 内存结构变动

在JDK8中,最主要的就是元空间代替了永久代(Pergen Space),由于上面结构图中的jvm方法区是永久代的子集那么就是说这部分会没有了,取而代之的是元空间(Metaspace)。
主要的意义在于:Metaspace使用本地内存来存储类元数据信息,Metaspace的内存大小可以动态增长,仅受限于本地内存大小。

Java 堆内存和非堆内存参数

-XX:Persize:设置非堆内存初始值,默认为1/64。

-XX:MaxPersize:设置非堆内存最大值,默认为1/4.

-Xss:设置每个线程占用堆内存大小,现在默认为1M,以前为256K。设置线程越小,堆内存大小不变,可以创造的线程数越多(当然有一个限度)。但是这个值也需要经过严格的测试再设置。

-Xms:JVM堆初始分配内存。默认为物理内存的1/64,当默认堆内存的空余空间小于40%的时候,这个堆内存就会自动增长到-Xmx指定的最大堆分配内存。

-Xmx :JVM的最大堆分配内存。默认为物理内存的1/4,当空余内存大于70%的时候,该堆内存又会自动减少到-Xms指定的内存。

-Xmn:指定新生代的内存大小。当堆大小不变的情况下,新生代越大,老生代越小,默认新生代和老年代的比例为1:2,而且这个比例会严重影响系统性能,sun推荐为新生代占整个堆内存3/8。

-XX:SurvivorRatio:新生代中,又分为eden区域和两个Survivor区域。默认比例为8:1:1,也就是说,可以用的内存为90%。该参数用来设置eden和Survivor的比值,默认为8:1。

-XX:NewRatio:年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)

Ps:自己在调垃圾收集器的时候用到的一些常用参数:

-XX:+UseConcMarkSweepGC :CMS 垃圾收集器

-XX:+UseParNewGC :ParNew 垃圾收集器

-XX:-UseSerialGC :串行垃圾回收器(新生代)

-XX:+PrintGCDetails :打印详细日志

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log :打印详细日志到一个文件中










92d6
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java jvm 内存 结构