JavaJava成长之路

Java虚拟机学习记录(内存划分、垃圾回收、类加载等机制)

2020-03-31  本文已影响0人  Java_老男孩

一直以来觉得虚拟机是Java最难的一部分,涉及最底层的原理,学起来难度很大,而且工作中基本上用不到这些原理,所以对这部分“敬而远之”。现如今工作五年了,从Java基础到算法、数据结构、网络、数据库、设计模式都有涉猎,虚拟机部分在脑海里还是空空荡荡,连经常被谈起的垃圾回收机制都不了解,实在是惭愧。了解虚拟机通往高级Java程序员的必由之路,同时学好虚拟机也能提高我们代码的质量,知道对象是怎么创建的,放在哪里,怎么执行,怎么回收的?明白这些问题让我们在程序的世界里当一个“明白人”。

一、Java内存区域

学习java时都知道Java内存分为两大快堆和栈,堆存放对象实例和数组对象,栈存放基本数据类型和对象的引用,这样有点笼统,实际这里说的堆指的是图中左边的Java堆,栈指的是本地方法栈,更具体的应该是栈里面栈帧的局部变量表。

内存区域总共分两大块:左边的堆内存区域和右边的栈内存加计数器,左边的堆内存是线程共享的,只有一份;右边部分每个线程独立一份,随线程而生,随线程而灭,是线程运行的内存区域。

  1. Java堆:是程序中内存管理最大的一部分,主要存放Java中的对象的实例、数组,堆里面为了内存回收方便化分了老年代和新生代区域。

  2. 方法区:方法区也可以理解为常说的永久代,和堆类似,只是逻辑上存放的数据不同,主要存放被虚拟机加载的类信息、常量、静态变量、缓存的常量池等。既然是永久代,一般方法去的内存很少被回收,相对来说最稳定。

  3. 虚拟机栈:存放线程运行时的上下文信息,栈内部包括栈帧,每个栈帧代表一个方法调用,方法的调用体现在栈帧的入栈和出栈,每个栈帧内部都存在一个局部变量表,用于存放方法内的变量,包括基本数据类型和引用数据类型,引用数据类型时这里只存放引用,地址指向的是堆中的一块内存区域。

  4. 本地方法栈:与虚拟机栈类似,不同为这里存放的是本地方法调用的运行数据,在java中声明的native方法。

  5. 程序计数器:用于记录当前线程执行到那个位置,线程内执行流程的控制依赖程序计算器来完成。

二、垃圾回收与内存分配

虚拟机从加载程序到运行程序都要进行内存分配,分配的时候也伴随的内存的回收,当对象“已死”(无引用)的时候进行回收。

1、对象可回收的两种判断算法

如何判断对象已死呢,一般有两种方式:

引用计数算法和可达性分析算法各有利弊,引用计数算法实现起来简单,但是需要维护一个引用计数,更新的次数太频繁,而且引用计数表也需要占一定内存;可达性分析是相对更普遍的一种实现方法,在回收时再进行一次检查,不用每次引用发生变化时发生更新,缺点是实现起来更复杂,维护“GC Roots”的算法比较复杂。

2、垃圾收集算法

一般虚拟机实现都采用了分代的方法,把内存划分了老年代和新生代,老年代存放的是相对稳定的对象;新生代存放的是活跃的对象,短期需要回收的。针对这两类的特点分别作出不同的策略,提高回收的效率。

这三种算法中,标记清除和标记整理适合老年代,需要回收的对象占少部分的情况;标记复制算法适合新生代,每次绝大部分对象需要回收,只需要把小量存活的挪到另一块位置。

3、内存分配的几条策略

三、类加载的过程

程序通过new、静态方法、静态字段引用、子父类的引用、反射调用等方式会触发类的加载,把类的字节码加载到虚拟机。加载流程:

  1. 加载:类的字节码加载到虚拟机,通过类加载器加载到虚拟机,默认通过Java的引导类加载(Bootstrap),也可以通过自定义的类加载器加载,加载的不一定必须是一个本地文件,只要是符合要求的二进制字节码即可,可以来源于网络或数据库。

  2. 验证:验证字节码的正确性,是否是一个合格的字节码文件,保证虚拟机的运行安全。

  3. 准备:分配内存和初始化零值。

  4. 解析:符号引用替换成直接引用,符号引用是字面量的形式,前面已经分配了内存,这里替换成指向的内存地址。

  5. 初始化:类加载的最后一步,执行程序代码里的初始化,包括静态代码块,构造方法,默认字段值。

四、Java内存模型

Java内存模型是定义了程序中变量的访问规则。

每个线程都有一个工作内存,工作内存通过读写操作和主内存交互,达到变量的共享。

交互操作:

欢迎关注公众号:《老男孩的成长之路》,后台私信“资料”领取《Java面试宝典Plus》版

上一篇下一篇

猜你喜欢

热点阅读