JVM学习笔记(一)--初步接触JVM

Java平台无关性的实现

Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。它屏蔽了不同操作系统版本之间的差异,只需要选择不同位数和版本的虚拟机,Java代码即可运行在对对应的操作系统

JVM中内存的划分

在这里插入图片描述
Java源文件经过jdk的编译,生成字节码文件,即class文件,然后通过类加载器Class Loader,将其加载到运行时数据区,即JVM中,进行一系列的操作。

  • 方法区:各个线程共享的内存区域,用于存储已经被类加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 程序计数器:一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
  • 堆内存:是Java虚拟机所管理的内存中最大的一块,也是垃圾回收的主要场所。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • Java栈:栈内存中主要保存局部变量、基本数据类型变量以及堆内存中某个对象的引用变量。每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。栈中的栈帧随着方法的进入和退出有条不紊的执行着出栈和入栈的操作。
  • 本地方法栈:主要是为JVM提供使用native 方法的服务。

内存分配与垃圾回收

在这里插入图片描述
JVM的内存可以分为堆内存和非堆内存。堆内存分为年轻代和老年代。年轻代又可以进一步划分为一个Eden(伊甸)区和两个Survivor(幸存)区组成

对象创建过程中的内存分配

一般通过new指令来创建对象,当虚拟机遇见一条new指令,会去检查这个指令的参数是否能在常量池中定位到某个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。如果没有,那么会执行类加载过程。通过执行类的加载,验证,准备,解析,初始化步骤,完成了类的加载,这个时候会为该对象进行内存分配,也就是把一块确定大小的内存从Java堆中划分出来,在分配的内存上完成对象的创建工作。

内存分配的两种方式

  • 指针碰撞方式:假设Java堆中的内存是绝对规整的,用过的内存在一边,未使用的内存在另一边,中间有一个指示指针,那么所有的内存分配就是把那个指针向空闲空间那边挪动一段与对象大小相等的距离
  • 空闲列表方式:如果Java堆内存中不是规整的,已使用和未使用的内存相互交错,那么虚拟机就必须维护一个列表用来记录哪块内存是可用的,在分配的时候找到一块足够大的空间分配对象实例,并且需要更新列表上的记录。

垃圾回收

JVM如何判定一个对象是否应该被回收

判断一个对象是否应该被回收,主要是看其是否还有引用。判断对象是否存在引用关系的方法包括引用计数法以及root根搜索方法。

  • 引用计数法:原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只需要收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。
  • root根搜索方法:root搜索方法的基本思路就是通过一系列可以做为root的对象作为起始点,从这些节点开始向下搜索。当一个对象到root节点没有任何引用链接时,则证明此对象是可以被回收的。以下对象会被认为是root对象:
  1. 栈内存中的引用对象
  2. 方法区中静态引用和常量引用指向的对象
  3. 被启动类加载的类和创建的对象

垃圾回收算法

标记清除算法

标记-清除算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,并且会产生内存碎片。

复制算法

复制算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。复制算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

标记整理算法

标记-整理(Mark-Compact)算法不直接对可回收对象进行清理,而是让所有可用的对象都向一端移动。然后直接清理掉边界以外的内存。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/bob_man/article/details/105574083