JVM内存结构
概述:
jvm: java virtual machine, 用于把我们写的那些不能直接被程序识别的java代码,翻译给操作系统,告诉他我们要做的是什么操作。
生命周期:
java程序开始执行的时候运行,程序结束后停止
机器上运行几个java程序,就会相应的有几个jvm进程
jvm中的线程分为两种: 守护线程和普通线程。守护线程是jvm自己使用的线程,比如垃圾回收。 普通线程一般指的就是java程序的线程,只要jvm中有普通线程在执行一般情况jvm就不会停止,除非强制调用exit();方法种植程序。
启动过程:
根据本地配置的环境变量找到jvm, java.exe 通过LoadJavaVm 来装入jvm文件,LoadLibrary装载jvm动态连接库,然后把JVM中的到处函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs 挂接到InvocationFunction 变量的CreateJavaVM和GetDafaultJavaVMInitArgs 函数指针变量上。JVM的装载工作完成。
运行java程序: jvm运行java程序的方式有两种,jar包和Class
运行jar的时候:java.exe调用GetMainClassName函数,该函数先活的JNIEnv实例然后调用JarFileJNIEnv类中的getMainfest(),从期返回的Mainifest对象中getArribittes(“Main-Class”)的值,即jar包中文件:META-INF/MANIFEST.MF指向的Main-Class的朱雷明作为运行的主类。之后main函数会调用java.c中LoadClass方法状态该主类(使用JNIEvn实例的FindClass)
运行Class的时候,main函数直接接调用Java.c中LoadClass方法装在该类
类加载器:
jvm默认提供了三个类加载器:
-
Bootstrap classLoader: 称之为启动类加载器,是最顶层的类加载器,负责加载JDK中的核心类库,jdk/lib目录下的jar. 如rt.jar,resource.jar, charset.jar等
-
Extension ClassLoader: 称之为扩展类加载器,负责加载java的扩展类库,默认加载$JAVA_HOME中jre/lib/*.jar, 或-Djava.ext.dirs指定目录下的jar包
-
APP ClassLoader:称之为系统类加载器,负责加载应用程序classPath目录下的所有jar和class文件。
除了java默认的三个类加载之外,我们还可以根据自身需要自定义ClassLoader, 自定义的类加载器必须继承ClassLoader类,除了Bootstrap ClassLoader,不是一个普通的java类,底层使用c++语言编写的,已经嵌入到jvm内核中,当jvm启动后,BootstrapClassLoader也随之启动,负责加载完核心类库后,并构造ExtensionClassLoader和 App CLassLoader
类加载的机制包括加载,连接(验证,准备,解析),初始化
方法区:
在jvm中,类型信息和类静态变量都保存在方法区中,类型信息是由类加载器在类加载的过程中从类文件中提取出来的,需要注意一点的是,常量池也存放于方法区中。
程序中所有的线程共享一个方法区,所以访问方法区的信息必须确保线程是安全的。如果有两个线程同时去加载一个类,那么只能有一个线程被允许去加载这个类,另一个必须等待。
方法区也是可以被垃圾回收,但是条件肺炎严苛,必须在该类没有任何引用的情况下。
类型信息包括:类型全名,类型的父类型全名,接口还是类,类型修饰符,父接口全名列表,类型的字段信息,类型的方法信息,所有的静态变量,指向类加载器的引用,指向Class的引用,基本类型常量池
堆:
当java创建一个类的对象或者数组时,都在堆中为新的对象分配内存,虚拟机中只有一个堆,程序中所有的线程都共享它。堆占用的控件是最多的,堆的存取类型为管道类型,先进先出。在程序运行中,可以动态分配堆内存的大小。
栈:
java栈中只保存基本数据类型和自定义对象的引用,注意只是对象的引用,而不是对象本身,对象本身保存在堆中。像String,Integer,等包装类是存放于堆中的。栈是先进后出类型的,栈内的数据在超出其作用域后,会被自动释放掉,不由JVM GC管理。每一个线程都包含一个栈区,每个栈中的数据都是私有的,其他栈不能访问。每个线程都会创建一个操作栈,每个栈又包含了若干个栈帧,每个栈帧对应着每个方法的每次调用,每个栈帧包含了三个部分:
局部变量区(方法内基本类型变量、变量对象指针),操作数栈区(存放方法执行过程中产生的中间结果),运行环境区(动态连接、正确的方法返回相关信息、异常捕捉)
本地方法栈:
本地方法栈的功能和jvm栈得非常类似,用于存储本地方法的局部变量表,本地方法的操作数栈等信息。本地方法栈是在程序调用或jvm调用本地方法接口(native)时候启用。本地方法都不是使用java语言编写的,比如使用C语言编写的本地方法,本地方法也不由jvm去运行,所有本地方法的运行不受jvm的管理。hotspot vm将本地方法栈和jvm栈合并了。
程序计数器:
在jvm概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器完成。jvm的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都有一个独立的程序计数器。程序计数器只占很小的一块内存空间。当线程正在执行一个java方法,程序计数器记录的是正在执行的jvm字节码指令的地址,如果正在执行的是一个native方法,那么程序计数器的值则为空(undefined). 程序计数器是唯一一个在jvm规范中没有规定任何oom的区域。
JVM执行引擎:
Java虚拟机相当于一台虚拟的“物理机”,这两种机器都有代码执行能力,其区别主要是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的。而JVM的执行引擎是自己实现的,因此程序员可以自行制定指令集和执行引擎的结构体系,因此能够执行那些不被硬件直接支持的指令集格式。
在JVM规范中制定了虚拟机字节码执行引擎的概念模型,这个模型称之为JVM执行引擎的统一外观。JVM实现中,可能会有两种的执行方式:解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码)。有些虚拟机只采用一种执行方式,有些则可能同时采用两种,甚至有可能包含几个不同级别的编译器执行引擎。
输入的是字节码文件、处理过程是等效字节码解析过程、输出的是执行结果。在这三点上每个JVM执行引擎都是一致的。
本地方法接口(JNI)
JAVA NATIVE INTERFACE: 提供了若干api实现java和其他语言的通信(主要是C和C++)
适用场景: 当我们有一些旧的库,已经使用C语言编写好了,如果要移植到java上来,非常浪费时间,而jni可以支持java程序与C语言编写的库进行交互,这要就不必要进行移植了。或者是与硬件,操作系统进行交互,提高程序的性能等,都可以使用JNI,需要注意的一点是需要保证本地代码能工作在任何java虚拟机环境。副作用:一旦使用JNI,java将失去两个优点,一个是不在跨平台,一个是程序不在绝对安全。
JAVA 常量池
jvm常量池也称之为运行时常量池,他是方法区的一部分,用于存放编译期间生成的各种字面量和符号引用。运行时常量池不要求一定只有在编译器产生的才能进入,运行期间也可以将常量放入池中,这种特性被开发人员利用的比较多的是String.intern()方法。
由“用于存放编译期间生成的各种字面量和符号引用” 这句话可见,常量池中存储的是对象的引用而不是对象的本身。
常量池的好处: 为了避免频繁的创建和销毁对象而影响系统性能,他也实现了对象的共享。
例如字符串常量池:在编译阶段就把所有的字符串文字放到一个常量池中。节省内存空间,节省运行时间。
- 点赞
- 收藏
- 关注作者
评论(0)