翻译

Java虚拟机


java 虚拟机

运行时数据区域

线程独有: 程序计数器,Java虚拟机栈,本地方法栈

线程共享: 堆和元空间(方法区)


程序计数器

记录正在执行的虚拟机字节码指令的地址


Java虚拟机栈

每个Java方法在运行或者执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,常量池等信息。从方法调用到执行完成的过程,对应一个栈帧在Java虚拟机栈中入栈或者出栈的过程(可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小,在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M)。


本地方法栈

本地方法与Java虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。

  1. 所有对象都在堆这里分配内存,这里是垃圾收集的主要区域。现在的垃圾收集器基本上采用分代收集算法。主要根据不同类型的对象进行不同的垃圾回收算法。一般堆分为两块:新生代与老年代。

  2. 可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。


方法区

用于存放已经被加载的类信息,常量,静态变量,编译器编译后的代码和其他的数据(jvm规范),运行时常量池是方法区的一部分。

判断一个对象是否可被回收

  1. 引用计数算法 为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
  2. 可达性分析算法 以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般包含以下内容:

  • 虚拟机栈中局部变量表中引用的对象
  • 本地方法栈中 JNI 中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象

垃圾收集算法

  • 标记-清除 在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
  • 标记-整理 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 优点:不会产生内存碎片。 不足:需要移动大量对象,处理效率比较低。。
  • 复制算法
  1. 将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
  2. 现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。

  • 分代收集 现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 一般将堆分为新生代和老年代。 新生代使用:复制算法 老年代使用:标记 - 清除 或者 标记 - 整理 算法

垃圾收集器

1.Serial:它是单线程的收集器,只会使用一个线程进行垃圾收集工作。 2.ParNew收集器:它是 Serial 收集器的多线程版本。 3.CMS 收集器:CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法

内存分配与回收策略

  • Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。

  • Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。

内存分配策略

1.对象优先在 Eden 分配:大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。 2.大对象直接进入老年代:大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。 经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。-XX:PretenureSizeThreshold,大于此值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间的大量内存复制。 3.长期存活的对象进入老年代:为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。

类加载机制

类的生命周期

  • 加载 加载过程完成以下三件事:

  • 通过类的完全限定名称获取定义该类的二进制字节流。

  • 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。

  • 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。

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

  • 准备 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。

实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。

  • 解析 将常量池的符号引用替换为直接引用的过程。
  • 初始化 初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 () 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
  • 使用
  • 卸载

类加载器

启动类加载器(Bootstrap ClassLoader)

此类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。


扩展类加载器(Extension ClassLoader)

它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。


应用程序类加载器

这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。


双亲委派

应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。

  • 工作过程:一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
  • 好处:使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
学习
  • 转载者:keke(联系作者)
  • 发表时间:2020-05-12 00:37
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 本篇文章为转载
  • 评论