Java代码是如何运行起来的?
Java文件->编译器->字节码->JVM->机器码 Java程序,先经过Javac编译成.class文件,如何JVM将其加载到‘元数据’区,执行引擎通过混合模式执行这些字节码,执行时,会翻译成操作系统相关的函数,JVM作为.class文件的黑盒存在,输入字节码,调用操作系统函数
JVM内存管理
JVM内存区域划分(运行时数据区)
- JVM堆中的数据是共享的,这是占用内存最大的一块区域
- 执行引擎:可以执行字节码的模块
程序计数器
-
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器工作时,通过改变程序计数器的值来选取下一条需要执行的字节码指令 -
Java虚拟机多线程是通过线程轮流切换,分配处理器执行时间的方式(时间片轮转)来实现的,在某一个时刻,一个处理器只能执行一条线程中的指令,因此,为了不同线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,且各线程的程序计数器之间互不影响,独立存储,因此,程序计数器属于线程私有的内存 -
若线程执行的是Java方法,则程序计数器中记录的是正在执行的虚拟机字节码指令的地址 -
若线程执行的是native方法,则程序计数器的值为空(undefined)
- native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。
- .程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。
Java虚拟机栈
- Java内存管理和线程息息相关,每个Java方法调用时,都会创建一个栈帧,入栈,完成相应调用后,出栈,所有栈帧出栈后,线程结束,因此,Java虚拟机栈也是线程私有的,生命周期于线程相同
(注意:栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的虚拟机栈的栈元素。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。)
栈帧包含的四个区域: 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法
局部变量表
变量槽
- 局部变量表以变量槽为最小单位
- 比如,64为长度的long和double类型的数据占用两个变量槽,其余的数据类型占用一个变量槽
- 一个变量槽有多大(占用多少比特),这取决于具体的虚拟机实现
本地方法栈
- 本地方法栈和虚拟机栈的结构和作用非常相似,区别:虚拟机栈为虚拟机执行Java方法(字节码服务),本地方法栈为虚拟机使用到的native方法服务
- 本地方法栈也会在栈深度溢出或者栈拓展失败时抛出StackOverflowError异常和OutOfMemoryError异常
Java堆
- Java堆是虚拟机管理内存中最大的一块,所有线程共享,在虚拟机启动时创建
- 用于存放对象实例
- Java堆是垃圾收集器管理的内存区域,也称GC堆
- 从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率
- Java堆可以处于物理上不连续的内存空间中,但在逻辑上应被视为连续的
- Java堆可以被实现成固定大小,也可以是可拓展的
- 当Java堆中没有内存完成实例分配,并且堆也无法再拓展时,Java虚拟机会抛出OutOfMemoryError异常
- Java堆中会划分**新生代 ( Young )、老年代 ( Old )**区域
方法区
- 方法区是各个线程共享的内存区域
- 用于储存已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等数据
- .也称静态区,包含所有的class和static变量。方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
- 方法区不需要连续的内存,可以选择固定大小或者可拓展,可以选择不是先垃圾收集(垃圾收集行为在该区域比较少出现,该区域的内存回收目标主要是针对常量池的回收和堆类型的卸载)
- 若方法区无法满足新的内存分配需求时,将抛出OutOfMermoryError异常
运行时常量池
- 运行时常量池是方法区的一部分
- Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到常量池中
- 具备动态性
- Java语言不要求常量一定只有编译器才能产生,也就是说,并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中
- 由于运行时常量池是方法区的一部分,因此,常量池再申请到内存时会抛出OutOfMemoryError异常
直接内存
- 直接内存不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域,但这部分区域被频繁使用过,并且也可能导致OutOfMemoryError异常
- 在jdk1.4中新加入了NIO(New Input/Output)类,引入了基于通道于缓冲区的I/O方式,可以使用native函数库直接分配堆外内存,如何通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作,这样在一些场景中可以提高性能
- 本机直接内存的分配不会收到Java堆大小的影响,但还是会收到本机总内存大小以及处理器寻址空间的限制
- 直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢
- 直接内存使用于需要大内存空间且频繁访问的场合,不适用于频繁申请释放内存的场合
|