JVM的内存结构主要分为:
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 堆
- 方法区
下面先开始学习程序计数器部分
程序计数器(Program Counter Register)
JVM 指令,java程序的源文件,被编译为.class字节码文件,在字节码文件中,java的源文件被编译为一条条的JVM指令,JVM指令在所有 *** 作系统下是通用的。
左侧是JVM指令,右侧为对应的Java源代码
JVM指令的执行流程,JVM 指令不能直接被CPU执行,JVM指令经过解释器,变成机器码,机器码再由CPU 进行执行。
程序计数器的作用就是记住当前JVM指令的下一条指令的内存地址。
在计算机中,用一个寄存器,来实现程序计数器的功能。
程序计数器的特点:
- 程序计数器是线程所私有的,每一个线程都有对应的程序计数器
- 程序计数器是唯一一个不会存在内存溢出的区
虚拟机栈
在数据结构中,栈的特点就是,先进后出,后进先出。
每个线程运行时所需要的内存,称为虚拟机栈,在线程运行的时候,存放线程运行时的数据。
每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存,每个栈帧存放着方法中的变量,参数,返回地址等数据信息。
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
常见问题:
垃圾回收是否涉及栈内存?
不涉及,因为栈的内存是由栈帧组成,栈帧就是一次次的方法调用,在方法执行结束之后,栈帧会自动的释放内存。
栈内存分配越大越好吗?
不是越大越好, 因为物理内存是有限的,栈内存越大,则可以并行的线程数就越少,栈内存越大,则每个线程所能进行的递归调用的层数就越深,栈内存大并不能带来性能上的提升。
方法内的局部变量是否线程安全?
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
栈内存的溢出
- 由于栈中的栈帧过多,而导致内存溢出,例如在递归调用中, 未正确的设置结束条件,造成栈溢出(常见)
- 由于栈中的栈帧过大,从而导致内存溢出 ( 不太常见 )
本地方法栈
本地方法概念:指不是用 java代码写的方法,由于java 的语言特性,java 不能对 *** 作系统的底层进行 *** 作,所以需要间接调用 C/C++ 编写的底层方法,与 *** 作系统底层进行 *** 作。
本地方法栈就是在运行本地方法时为本地方法创建的栈内存结构。
在 java 的Object类中,大量的调用了本地方法。
加上 native关键字 后的函数的实现不是用java写的
public native int hashCode(); public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException;
堆
通过new 关键字声明的对象都会被放入堆内存中。
堆是线程所共享的内存区域,堆中的对象都需要考虑线程安全问题。
堆中有垃圾回收机制,在堆中不再被引用的对象,内存将会被回收。
堆内存溢出:在堆内存中存放的对象过多,超过了堆中的内存大小,将会使得堆内存溢出。
方法区
方法区与堆类似,方法区是所有线程共享的内存区域。
方法区类似于传统语言的编译代码存储区,或类似于 *** 作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括在类和实例初始化和接口初始化中使用的特殊方法,方法区是在虚拟机启动时创建的。
在不同厂商,不同版本的JDK中,对方法区的实现也是不同的。
在JDK1.6中,方法区是由一个被称为永久代所实现的。
永久代是在堆中存储的,由JVM所管理的内存空间。
永久代中包含Class的信息,ClassLoader的信息,还有字符串常量池等。
在JDK1.8中,方法区是由一个被称为元空间所实现的。
元空间已经不占用堆的内存了,元空间不是由JVM所管理的内存空间,直接由 *** 作系统来管理。
在元空间中,同样的包含Class的信息,ClassLoader的信息,以及常量池的信息等。
不过在元空间中,已经不进行存储字符串常量池了,字符串常量池存放在堆内存中。
方法区的内存溢出:
在方法区中存放着类的信息,如果加载的类过多,则有可能造成方法区内存的溢出
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)