Java_jvm 墨蓝 2022-03-15 16:56 82阅读 0赞 ## **1.Java虚拟机** ## :实际计算机上通过软件模拟实现的机器。有自己的硬件,如处理器,堆栈,寄存器,还有相应的指令系统。 实现跨平台特性;把目标代码编译成字节码; *应用程序层: Java程序层 Java平台层: Java虚拟机 操作系统层: Windows UNIX/Linux RTOS 硬件层: x86 SPARC MIPS/ PPC* **JVM生命周期:** 一个运行的JVM有着一个清晰的任务:执行Java程序。程序开始执行时,它才运行,程序结束时,它就停止。每个Java程序会单独运行一个Java虚拟机。 通过命令启动JVM:java \*\*\* (类名)。JVM总是开始于一个main()方法,这个方法必须时公有public,返回void,直接接收一个字符串数组。在程序执行时,必须给JVM指明这个包含有main()方法的类名。 `public static void main (Strings, args[])` main()方法是程序的起点,它被执行的线程初始化为程序的初始线程。程序中其他的线程都由它来启动。 Java中的线程分为两种:守护线程(daemon) 和普通线程 (non-daemon)。守护线程时JVM自己使用的线程,比如负责垃圾收集的线程。也可以把自己的线程设置为守护线程。包含main()方法的初始线程不是守护线程。 只要JVM中还有普通的线程在执行,JVM不会停止。如果有足够的权限,就可以调用exit() 方法中止程序。 **JVM的体系结构:** JVM的规范中定义了一系列的**子系统,内存区域,数据类型**和使用指南。 每个JVM都一个类加载子系统,负责加载程序中的类型(类class 和接口 interface),并赋予唯一的名字。每一个JVM都有一个执行引擎,负责执行被加载类中包含的指令。 **JVM中的数据类型:** 原始数据类型:(primitive types) 四类八种 引用数据类型:(reference types) JVM中存在一个Java语言中不能使用的原始数据类型-----返回值类型(return value).这种类型被用来实现Java程序中的 “ finally classes"。 引用类型可能被创建为:**类类型(class types) ,接口类型(interface types),数组类型(array type)**。它们都引用被动态创建的对象。当引用类型引用null时,说明没有引用任何对象。 ## **JVM内存区域:** ## *线程共享:方法区(Method Area) / 堆(Heap) 线程私有:虚拟机栈(VM Stack)/ 本地方法栈(Native Method Stack)/程序计数器(Program Counter Register)* * \*\*\*\*程序计数器:\*\*\*\*JVM将这个计数看成当前线程执行某条字节码的行数,会根据计数器的值来选取需要执行的操作语句。这个属于线程私有,不可共享,如果共享会导致计数混乱,无法准确的执行当前线程需要执行的语句。 该区域不会出现任何OutOfMemoryError的情况。 * \*\*虚拟机栈:\*\*即栈内存。Java中每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果 虚拟机栈可以动态扩展(当前大部分的JVM都可以动态扩展,只不过JVM虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。 * **本地方法栈**:用于执行本地方法,抛出异常的情况和虚拟机栈一样。而虚拟机栈用来执行Java方法。 * **堆**:是JVM中内存最大,线程共享的一块区域。唯一的目的是存储对象实例。这里也是垃圾收集器主要收集的区域。由于现代垃圾收集器采用的是分代收集算法,所以Java堆也分为新生代和老年代。 可以通过-Xmx(JVM最大可用内存)和-Xms(JVM初始内存)来调整对内存,如果扩大值无法继续扩展时,会出现OutOfMemoryError的错误。 * **方法 区**:JVM中内存共享的一片区域,用来存储类信息,常量,静态变量,class文件。垃圾收集器也会堆这部分区域进行回收,比如常量池的清理和类型的卸载。 方法区内存不够用时,也会抛出OutOfMemoryError错误。 ## JVM类加载机制 ## :虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。 Java语言里,类的加载和连接过程实在程序运行期间完成的。 类的生命周期:加载 验证 准备 解析 初始化 使用 卸载 * **加载**:通过一个类的全限定名来获取此类的二进制字节码。将这个字节码所代表的静态存储结构转化为方法区的运行时数据结构。 在Java堆中生成一个代表这个类的class对象,作为方法区这些数据的访问入口。 * **验证**:虚拟机规范:验证输入的字节流是否扶额Class文件的存储格式,否则抛出一个java.lang.VerityError异常。 * **文件格式验证**:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。经过这个阶段的验证,字节流进入内存的方法区进行存储。 * **元数据验证**:对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。 * **字节码验证**:进行数据流和控制流分析,对类的方法体进行校验分析,保证被校验的类的方法在运行时不会做出损害虚拟机安全的行为。 * **符号引用验证**:发生在虚拟机将符号引用转化为直接引用的时候(解析阶段),对常量池中的各种符号引用的信息进行进行匹配性的校验。 * **准备**:准备阶段时正式为类变量分配内存并设置类变量的初始值(各数据类型的零值)的阶段,这些内存将在方法区中进行分配。但是如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量值就会初始化为ConstantValue属性指定的值。`public static final int value =122;` * **解析**:虚拟机将常量池内地符号引用替换为直接引用的过程。 * **符号引用**:以一组符号来描述所引用的目标,符号可以时任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。 * **直接引用**:可以是直接指向目标的指针,相对偏移量或者一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。 * **初始化**:()方法:由编译器自动收集类中所有变量的赋值动作和静态语句块中语句合并产生,收集的顺序是由语句在源文件中出现的顺序决定的。该方法与实例构造器()不同,不需要显示调用父类构造器。()方法对于类和接口来说不是必须的。执行接口的()不需要先执行父接口的()方法。虚拟机会保证一个类的的方法在多线程环境中被正确的加锁和同步。 * **类的主动引用**:遇到new ,getstatic, putstatic, invokestatic这四条字节码指令时(使用new实例化对象的时候,读取或设置一个类的静态字段,调用一个类的静态方法)。使用java.lang.reflet包的方法对类进行反射调用的时候。当初始化一个类的时候,如果发现其父类没有进行初始化,则需要先触发其父类的初始化。当虚拟机启动时,虚拟机会初始化主类(包含main方法的那个类)。 * **类的被动引用**:通过子类引用父类的静态字段,不会导致子类初始化(对于静态字段,只有直接定义这个字段的类才会被初始化)。通过数组定义类应用类:`ClassA [] array = new ClassA [10]`。触发了一个名为classA的类的初始化,它时一个由虚拟机自动生成的,直接继承与Object的类,创建动作由字节码指令newarry触发。常量会在编译编译阶段存入调用类的常量池。 ## 垃圾回收 ## :当一个对象没有引用指向它时,这个对象就成为无用的内存(垃圾),就必须进行回收,以便于后续其他对象的内存分配。 * **引用计数算法**:实现简单,判断效率也很高,大部分情况下都是个不错的算法。但Java语言中没有选用引用计数算法来管理内存,其中最主要一个原因时它很难解决对象之间相互循环应用的问题。`ObjA.obj = ObjB ; ObjB.obj = ObjA` * **可达性分析算法(根搜索算法)** 主流的商用程序语言中(Java和C\#),都是可达性分析判断对象是否存活的。根搜索算法时从离散数学的图论中引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,如果一个节点与GC ROOT之间没有引用链存在,则该节点视为垃圾回收的对象。 Java语言里,可作为GC ROOTS对象的包括如下几种:虚拟机栈(栈帧中本地变量表)中引用的对象;方法区中的类静态属性引用的对象;方法区中的常量引用对象;本地方法栈中JNI的引用对象。 **对象引用-强引用**:只要引用存在,垃圾回收器永远不会回收。`Object obj = new Object();` obj对象后面new Object有一个强引用,只有obj这个引用被释放后,对象才会被释放。 对象引用-软引用:非必须引用,内存溢出之前进行回收。 Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; sf.get(); **软引用**主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真实的来源查询这些数据。 对象引用-弱引用:在第二次垃圾回收时回收,可以通过如下代码实现 Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null; wf.get(); //有时返回null wf.isEnQueued(); **弱引用**主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的siEnQueued方法返回对象是否被垃圾回收。 虚引用(幽灵/幻影引用):在垃圾回收时回收,无法通过引用取到对象值, Object obj = new Object(); PhantomReference<Object>pf = new PhantomReference<Object>(obj); obj=null; pf.get();// 永远返回null pf.isEnQueued(); **分代垃圾回收的提出: 在Java代码中,Java语言没有显示的提供分配内存和删除内存的方法。一些开发人员将引用对象设置为null或者调用System.gc()来释放内存。 在Java中,由于开发人员没有在代码中显示的删除内存,所以垃圾收集器回去发现不需要(垃圾)的对象,然后删除它们,释放内存。 分代垃圾收集器时基于以下两个假设而创建的:绝大多数对象在短时间内变得不可达 / 只有少量年老对象引用年轻对象。** **年轻代和老年代:** * 年轻代:新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数年轻对象在年轻代中创建,然后消失。当对象从这块区域消失时,我们说发生了一次“minor GC"。 * 老年代:没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说”major GC“ (或 ”full GC ”) 发生了。 年轻代共有3块空间,1块为Eden区,2块为Survivor区。各个空间执行顺序如下: 绝大多数的新创建的对象分配在Eden区。/ 在Eden区发生一次GC后,存活的对象移到其中一个Survivor区。/一旦一个Survivor区已满,存活的对象移动到另一个Survivor区。然后之前那个空间已满Survivor区将置为空,没有任何数据。经过多次重复的这样的步骤后依旧存活的对象将被移到老年代。 ## 典型垃圾收集算法: ## * Mark-Sweep (标记-清除)算法:标记阶端标记出所有需要被回收的对象,清除阶段回收被标记对象所占用的空间。实现容易,但易产生内存碎片。 * Copying (复制)算法:将内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活者的对象复制到另外一块上面,然后在把已使用的内存空间一次清理掉,避免内存碎片。但可用内存缩小了一半。 * Mark-Compact(标记-整理)算法:完成标记后,将存活对象都向一端移动,然后清理掉端边界以外的内存。 * Generational Collection (分代收集)算法:目前大部分JVM的垃圾收集器采用的算法。 核心思想:根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代和新生代,老年代的特点时每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最合适的收集算法。 目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说要复制的操作次数比较少,但是实际中并不是按照1:1的比例来划分新生代空间,一般将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。 由于老年代的特点是每次回收少量对象,一般使用Mark-Conpact算法。 注意:在堆区之外还有一个代就是永久代(Permanent Generaion ),它用来存储class类,常量,方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。 ## 典型垃圾回收器: ## * Serial / Serial Old 最基本,最古老的收集器,是一个单线程收集器,并且在它进行垃圾收集是,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法,Serial Old收集器是针对老年代的收集器,采用Mark-Compact算法。优点是实现简单高效,但缺点是会给用户带来停顿。 * ParNew : Serial收集器的多线程版本,使用多个线程进行垃圾收集。 Parallel Scavenge : 新生代多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个不同,它主要是为达到一个可控的吞吐量。 Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。 * CMS (Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用Mark-Sweep算法。 * G1收集器:当今收集器计数发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU,多核环境。因此是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。 Java虚拟机的概念与用途 Java虚拟机的内存分配与工作过程 内存垃圾回收的原理
还没有评论,来说两句吧...