转载请注明作者与出处
程序计数器
线程私有
因为物理cpu并不多,所以jvm是对java里面的线程进行不停的切换执行,因为切换的执行速度太快,所以我们看到是并发执行。所以jvm在切换线程执行后,如果要切换回原来的线程,它需要记住这个线程的执行位置,下一条指令是什么。所以每一个线程都有一个独立的程序计数器,它是线程私有的。
数据内容
程序计数器保存了每个对象的引用数量,但是也不仅仅是对象的引用,它保存了一个线程中一系列需要执行的字节码指令的内存地址,包括循环,异常等
native方法
如果当前正在执行的是native方法,那么它在程序计数器里面的值是空(undefined
)。
java栈
线程私有
java栈保存的是执行每一个方法的内容,所以每执行一个方法,都会创建一个栈帧(StackFrame),保存局部变量,操作数栈,动态链接,方法的进出信息等,直到一个方法调用完成,就意味着一个栈帧从进去到出来的过程,所以它也是线程私有的。
数据内容
java栈帧中,保存了当前局部的基本数据类型(boolean,byte,char,short,int,float,long,double),以及对象引用。
对象引用,这里指的是定义的那些对象,但是值得注意的是,这里保存的是引用,而不是具体的内容,当我们new一个对象时,jvm会把创建的引用放在栈里面,但是对象本身,是存在堆里面的,而引用只是保存了对象在堆里面的内存地址,这是因为栈内存很小,但是栈读取数据快,所以存储了引用,而我们开辟出来的对象,或者申请的内存是放在堆里面的。
局部变量所需要的内存,在一开始就是确定的,jvm会按照变量类型计算。因为当进入一个栈帧时,所需要的内存是确定的,直到出栈,这里面的内存不会发生任何变化。
栈异常
jvm中对于栈规则了两种异常。
- 当java类中的方法进入次数太多时,会导致栈的层次越来越深,如果请求的栈深度,超出了jvm虚拟机所允许的深度,就会抛出
*Error
异常。(当前绝大部分虚拟机都是可以动态调整栈深度的,所以一般不会出现这个问题,但是也不排除,因为jvm规范中也允许固定长度的栈深度) - 另一方面,如果扩展栈深度时,无法申请到足够的内存,就会抛出
OutOfMemoryError
异常。
所以当我们遍历文件夹的时候,最好不要用递归,因为可能出现栈溢出的异常。
本地方法栈
本地方法栈所起的作用和java栈的作用几乎一致,只不过本地方法栈中,保存的是native方法的栈信息,但是虚拟机规范中,对于native方法的实现语言,实现类型,数据结构并没有明确规定,各种虚拟机可以*实现它,比如Sun HotSpot虚拟机就把java栈和本地方法栈合二为一了。
本地方法栈也有着*Error
和OutOfMemoryError
java堆
堆是所有线程共享的,它是jvm管理内存的最大的一块区域,也是java程序员所能操控的内存区域。
数据内容
java程序员所能操控的内存,虽然对于程序员来说没有感知,但实际上全部是在堆里面操作,比如我们new出来的对象,以及数组,其实都是存放在堆内存里面的。
垃圾回收
java堆是gc回收内存的主要区域,因为现在的内存回收算法基本都是采用分代算法,所以还可以分为新生代和老生代,这样的分配是为了更快的找出需要回收的内存,提高gc效率。甚至还可以更往细分Eden,From Survivor,To Survivor等。
空间大小
java堆里面的内存可以是物理上不连续的内存,只要是逻辑上连续就可以,一般主流虚拟机,都是可以在启动的时候,根据启动参数指定内存大小(-Xms -Xmx),如果在使用内存时,jvm无法再申请新的堆内存,就会抛出OutOfMemoryError
异常。
方法区
方法区是所有线程共享的区域,方法区也叫永久代,因为它永远不会被gc回收。
数据内容
用于存储虚拟机加载的类信息,常量,静态变量等数据,这些数据是在类加载器加载时候完成的,所以虽然说new出来的对象是存在堆里面的,但是如果这个对象是常量,那么在类加载器加载这个类的时候,就会把这些静态变量存储到方法区里面去。
异常信息
同样的,方法区的内存无法满足内存的根本需求时,抛出OutOfMemoryError
异常
堆外内存(直接内存)
堆外内存是一块独立的内存,值得注意的是,它是由java程序员完全操纵的一个内存,意味着,程序员需要显式的申请内存,以及手动释放内存,因为它不由gc管理。
它的优点是因为直接操作内存,在某些应用场景中,可以避免内存的复制,以及回收再创建,可以提升内存的利用率。
它的缺点就是需要手动释放内存,而不是交给gc来处理,所以使用不当,很容易抛出OutOfMemoryError
异常。
堆外内存不受到堆内存的限制,也就是不受到-Xmx的限制,但是还是受到物理内存的限制,如果超出物理内存,就就会抛出
OutOfMemoryError