java虚拟机

一、开始

 打算从静态代码开始说起。重点还是后面动态的过程

从问题出发,为什么可以实现“一次编写,到处运行”?

答案:平台/语言无关的字节码编译结果(.class文件)+虚拟机

二、Java对象的一生——出生

首先,西红柿炒鸡蛋的一生?

1. 看菜谱,把需要的食材放到一起(加载)

2. 看下食材有没有坏掉的?锅洗了没?盐、酱油是否有?(验证)

3. 把鸡蛋洗一下,西红柿也要洗一下切块,鸡蛋则可以打一下,完成最基本的备菜(准备)

4. 开始打蛋调味,西红柿呢,就进行切块(解析)

5. 先炒鸡蛋,再炒西红柿(初始化)

6. 吃饭

7. 光盘行动~~

1. 加载

获取一个类的二进制字节流,最终生成一个Class对象

  • 什么时候需要加载
    • new关键字、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类的时候,如果发现其父类未初始化
    • 当虚拟机启动时,用户需要指定一个要执行的主类
    • 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
  • 从哪获取二进制字节流
    • zip、jar、war
    • 网络,如Applet
    • 运行时计算生成,如动态代理技术,在java.lang.reflect.
    • Proxy中,用ProxyGenerator.generateProxyClass来为特定接口生成形式为“*$Proxy”的代理类的二进制字节流
  • 获取到的二进制流如何存储

方法区生成一个java.lang.Class对象,作为程序访问方法区中的这些类型数据的外部接口

  • 谁来加载——类加载器

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器

    • 为什么要放到外面实现:个人觉得主要是出于灵活性的考虑,减少对语言、平台等的限制。虚拟机只要定义规则就好了
    • 那么这么多加载器如何管理——双亲委派模型

    •  规则有问题吗?——破坏双亲委派模型

 JNDI:本身由启动类加载器加载,但是它需要去加载其他的资源,这些资源是用户定义的,无法被启动类加载器认识

程序动态性:代码热替换(HotSwap)、模块热部署(HotDeployment)

(OSGi)....这个还要再补充

2. 连接——验证

目的:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

从外往里看,可以看到有以下几个部分

  • 字节流

是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理

只有通过这个验证,才能进入方法区存储

  • 类的定义

如这个类是否有父类、字段是否与父类冲突、是否实现所有抽象方法等

  • 类的方法体
  • 符号引用

发生在虚拟机将符号引用转化为直接引用的过程

验证是否能找到引用对象,字段等是否可访问

3. 连接——准备

目的:为类(static)变量分配内存并设置类初始值

非final变量:0、false、null

final变量:程序中设置的值

4. 连接——解析

目的:将常量池内的符号引用替换为直接引用

符号引用:个人觉得可以看成是占位符

直接引用:通过直接引用可以找到引用对象的内存位置。可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄

5. 初始化

初始化阶段是执行类构造器<clinit>()方法的过程

<clinit>()方法是由编译器自动收集类中的所有类变量赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

三、Java对象的一生——离开

  • 什么时候可以离开——不再“”被使用“”时离开——怎么判断不再被使用?
    • 引用计数算法:增加一个引用则计数加1,引用减少时则减1
    • 可达性分析算法:从root结点(虚拟机栈(栈帧中的本地变量表);方法区中类静态属性;方法区中常量;本地方法栈中JNI(即一般说的Native方法))开始分析是否与某一对象可达
  • 什么时候让无用的对象离开
  • 如何离开

  举个例子:有一堆黄豆,要去除其中坏的去除。基于坏和好数量的不同,应该按照哪个少,把哪个捡出来

    • 标记-清除算法(坏的少):最简单的,有可以离开的对象就让它离开好了
      • 太粗暴,会留下一些空洞(碎片),这些碎片太小无法被再利用,太浪费了(空间利用角度的考虑)
    • 复制算法(坏的多):将一个空间分成两个部分AB,一开始只在A进行分配,清理时把要保留的对象复制到B
      • 把空间一分为二,如果资源并不富裕,这个方案可就不行了
    • 标记-整理算法(空间有限):直接移动要保留的对象(覆盖将移除的内存)到某一端,然后清理掉端边界外的内存
    • 分代收集算法:上面都挺好的,那么就做个结合,各取所需。
      • 新生代——复制,每次垃圾收集时都发现有大批对象死去,只有少量存活
      • 老年代——“标记—清理”或“标记—整理”,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用来进行回收

参考资料

1. 《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》

原文地址:https://www.cnblogs.com/coolqiyu/p/13340802.html