Java虚拟机

  • 什么是Java
  • jdk与jre
  • Java虚拟机
  • 跨平台
  • 跨语言
  • JVM的启动
  • JVM总体结构
  • JVM的垃圾回收机制
  • 垃圾回收算法
  • 内存泄漏
  • 内存溢出


什么是Java

语言 作为世界上最流行的编程语言,Java 可以编写 C/S、 B/S 模式的软件。语法由 Java Language Specification 规定。
平台 作为一个开发平台, 以 JVM 为基础, 除了 Java 外还有 Groovy, Scala, Kotlin, JRuby,Clojure, Jython 等语言。 虚拟机内部机制由 Java Virtual Machine Specification 规定。
文化 作为一种文化,Java 几乎成为可“开源”的代名词
生态 作为生态,Java 拥有世界最多的技术拥护者和开源社区的支持。从桌面应用软件、嵌入式开发到企业级应用、后台服务、中间件都可以看到 Java 的身影

jdk与jre

JRE: Java Runtime Environment 顾名思义是 java 运行时环境,包含了 java 虚拟机,java 基础类库。是使用 java 语言编写的程序运行所需要的软件环境,是提供给想运行 java 程序的用户使用的。
JDK:Java Development Kit 顾名思义是 java 开发工具包,是程序员使用 java 语言编写 java 程序所需的开发工具包,是提供给程序员使用的。

JDK 包含了 JRE ,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具:jconsole,jvisualvm 等工具软件,还包含了 java 程序编写所需的文档和 demo 例子程序。
如果你需要运行 java 程序,只需安装 JRE 就可以了。如果你需要编写 java 程序,需要安装 JDK。

Java虚拟机

任何 Java 的技术问题,都可以追溯到 JVM

衡量 Java 技术水平,能看懂多少 JVM 知识

Java 程序员的内功

java虚拟机什么时候启动 java虚拟机开源吗_编程语言


Java 虚拟机(Java Virtual Machine 简称 JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,它是 Java 最具吸引力的特性之一。

跨平台

●C语言、汇编语言等古老的语言不能跨平台

●代码编译之后会和 CPU、操作系统等底层产生关联

java虚拟机什么时候启动 java虚拟机开源吗_java_02

Java 虚拟机屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

跨语言

java虚拟机什么时候启动 java虚拟机开源吗_jvm_03

JVM的启动

java虚拟机什么时候启动 java虚拟机开源吗_编程语言_04

  1. 执行 JVM 启动器 /bin/java.exe
  2. 启动器加载 /jre/bin/server/jvm.dIl
    ①jvm.dIl 检 查 OS、获取 JVM 参数
    ②根据 vm 参数向 OS 申请分配内存、创建线程等
  3. BootstrapClassloader 加载 /jre/lib/rt.jar 等核心类库
  4. 启动 main 线程,加载 main 方法 Java 类。
    ①启动器退出,虛拟机初始化完成。
  5. Java main 方法开始执行。

JVM总体结构

java虚拟机什么时候启动 java虚拟机开源吗_编程语言_05


方法区

所有类级别数据将被存储在这里,包括静态变量。每个JVM只有一个方法区,它是一个共享的资源。


所有的对象和它们相应的实例变量以及数组将被存储在这里。每个JVM同样只有一个堆区。由于方法区和堆区的内存由多个线程共享,所以存储的数据不是线程安全的。

JVM栈

对每个线程会单独创建一个运行时栈。对每个函数呼叫会在栈内存生成一个栈帧(Stack Frame)。所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源。

java虚拟机什么时候启动 java虚拟机开源吗_jvm_06


程序计数器

每个线程都有一个单独的程序计数器来保存当前执行指令的地址,一旦该指令被执行,程序计数器会被更新至下条指令的地址。

本地方法栈
本地方法栈保存本地方法信息。对每一个线程,将创建一个单独的本地方法栈。

JVM的垃圾回收机制

1. 引用计数法
堆中每个对象(不是引用)都有一个引用计数器。当一个对象被创建并初始化赋值后,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1(a = b, b被引用,则b引用的对象计数+1)。当引用失效时(一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时),计数器值就减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。

优点:引用计数收集器执行简单,判定效率高,交织在程序运行中。对程序不被长时间打断的实时环境比较有利(OC的内存管理使用该算法)。

缺点: 难以检测出对象之间的循环引用。同时,引用计数器增加了程序执行的开销。所以Java语言并没有选择这种算法进行垃圾回收。

2. 根搜索算法
首先了解一个概念:根集(Root Set)
所谓根集(Root Set)就是正在执行的Java程序可以访问的引用变量(注意:不是对象)的集合(包括局部变量、参数、类变量),程序可以使用引用变量访问对象的属性和调用对象的方法。

这种算法的基本思路:
(1)通过一系列名为“GC Roots”的对象作为起始点,寻找对应的引用节点。
(2)找到这些引用节点后,从这些节点开始向下继续寻找它们的引用节点。
(3)重复(2)。
(4)搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。

Java和C#中都是采用根搜索算法来判定对象是否存活的。

垃圾回收算法

1. 标记-清除算法
标记—清除算法是最基础的收集算法,为了解决引用计数法的问题而提出。它使用了根集的概念,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。

优点:不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效。
缺点:(1)标记和清除过程的效率都不高。(2)标记清除后会产生大量不连续的内存碎片。虽然空闲区域的大小是足够的,但却可能没有一个单一区域能够满足这次分配所需的大小,因此本次分配还是会失败(在Java中就是一次OutOfMemoryError)不得不触发另一次垃圾收集动作。

java虚拟机什么时候启动 java虚拟机开源吗_jdk_07

2. 标记-整理算法
该算法标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表。

优点:
	(1)经过整理之后,新对象的分配只需要通过指针碰撞便能完成(Pointer Bumping),相当简单。
	(2)使用这种方法空闲区域的位置是始终可知的,也不会再有碎片的问题了。
	缺点:GC暂停的时间会增长,因为你需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址。

java虚拟机什么时候启动 java虚拟机开源吗_编程语言_08

3. 分段复制算法
该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次清理掉。

复制算法比较适合于新生代(短生存期的对象),在老年代(长生存期的对象)中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如标记—整理算法。一种典型的基于Coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象区和空闲区,在对象区与空闲区的切换过程中,程序暂停执行。

优点:
	 (1)标记阶段和复制阶段可以同时进行。
	 (2)每次只对一块内存进行回收,运行高效。
	 (3)只需移动栈顶指针,按顺序分配内存即可,实现简单。
	 (4)内存回收时不用考虑内存碎片的出现(得活动对象所占的内存空间之间没有空闲间隔)。

	缺点:需要一块能容纳下所有存活对象的额外的内存空间。因此,可一次性分配的最大内存缩小了一半。

java虚拟机什么时候启动 java虚拟机开源吗_java虚拟机什么时候启动_09


4. 分代收集算法

java虚拟机什么时候启动 java虚拟机开源吗_编程语言_10

内存泄漏

本该被回收的内存,被遗漏了,导致一直占用内存

示例:
Set<Test1> set = new HashSet<>();
Test1 test1 = new Test1("唐僧","男",18);
Test1 test2 = new Test1("猪八戒","男",48);
Test1 test3 = new Test1("白骨精","女",26);
set.add(test1);
set.add(test2);
set.add(test3);//现在set里面有3个对象

test3.setAge(18);//修改test3的年龄, 此时test3的hashcode改变
set.remove(test3);//此时remove不掉, 造成内存泄漏
set.add(test3);//重新添加, 成功, 现在set里面有4个对象

内存溢出

泄漏积累得太多,物理内存不足,导致程序运行需要创建新的对象无处存放,发生内存溢出的错误