Java 虚拟机简介

Java 虚拟机简介

本文是阅读《深刻理解 Java 虚拟机:JVM 高级特性与最佳实践》的笔记java

推荐学习资料:linux

  • 《The Java Virtual Machine Specification, Java SE 7 Edition》
  • http:// hllvm. group. iteye. com/,高级语言虚拟机圈子

概述

Java 技术体系 4 平台

  • Java card
  • Java ME(Micro Edition),手机、PDA 等,对 Java API 有所精简
  • Java SE(Standard Edition),桌面级应用
  • Java EE(Enterprise Edition),除桌面功能外提供企业级功能,如 ERP、CRM等,Java EE 之前称为 J2EE

从 JDK 1.5 之后,官方就再也不使用这种命名方式,而是使用 JDK 五、JDK 6 这种形式编程

Java 虚拟机实现

  • Sun HotSpot VM
    • Hot 指的是热点代码探测,例如一段被频繁调用的函数将会触发编译器优化以提升这段代码的执行速度
  • Sun Mobile-Embedded VM/Meta-Circular VM,用于嵌入式平台
  • BEA JRockit/IBM J9 VMAzul VM/BEA Liquid VMMicrosoft JVM(因侵权而放弃),等等

混合语言

Clojure、Jruby、Groovy 等语言都基于 Java 虚拟机,且各有特色,分别适用于不一样的场景windows

其余 Java 特性

  • Fork/Join 多线程模型
  • 函数式编程,Java 8,lambda
  • OpenJDK 是 sun 2006 年把 Java 开源而造成的项目。OpenJDK 对 windows 的支持并不友好

JVM 内存

graph TB A[JVM memory] A --> B[线程间共享<br/><执行引擎>] style B fill:#f9f A --> C[线程私有<br/><本地库接口>] style C fill:#f90 B --> D[方法区<br/>常量池<br><小字符串等>] B --> E[堆] C --> F[虚拟机栈] C --> G[本地方法栈] C --> H[程序计数器] A --> I[直接内存区<br/>非JVM管理] style I fill:#690 J[部分 JVM 实现不区分] F --> J G --> J E --> K[指针碰撞<br/>空闲列表<br/>TLAB] E --> L[堆溢出<br/>栈溢出] D --> M[溢出<br/>CGLib&SJP]
  • 线程私有
    • 程序计数器
      • 与 CPU 的程序计数器相似,不过在 Java 中这个计数器可能指向的是字节码位置(不一样实现可能不一样)
      • 不一样线程都有本身的程序计数器
      • 不会抛出 OutOfMemoryError 异常
    • Java 虚拟机栈
      • 与 OS 中进程的栈概念类似,每一个 Java 方法都有本身的栈帧用于保存局部变量、动态连接等信息,例如基本数据类型(boolean、byte、char等)、对象引用和 returnAddress
      • 编译时已知的各类基本类型数据保存在栈中的局部变量表中,局部变量表的大小在编译时肯定
      • StackOverflowError or OutOfMemoryError,Java 容许固定长度的虚拟机栈
    • 本地方法栈
      • 与 Java 虚拟机栈的功能相似,不过这个栈由 Java 中所调用的 Native 方法使用
      • 有些 JVM 将本地方法栈和虚拟机栈合并,Java 方法和 Native 方法都使用相同的栈空间
      • A native method is a Java method whose implementation is provided by non-java code
  • 线程共享
    • Java 堆(GC 堆)
      • 虚拟机启动时建立,保存绝大部分对象实例与数组。随着 JIT 和逃逸分析等技术的发展对象未必必定在堆上建立
      • Java 堆是垃圾搜集器管理的主要区域
      • Java 虚拟机规范中规定堆能够位于物理上不连续的内存空间,不过主流的实现都按照可扩展来实现
    • 方法区
      • 保存已经被虚拟机加载的类信息、常量、静态变量、JIT 编译后的代码等书籍
      • 方法区的内存回收是必要的,虽然不多使用,有些 BUG (如内存泄漏)是由于方法区没有回收内存
      • HotSpot 虚拟机称之为永久代,由于此虚拟机将 GC 扩展到了方法区,其余虚拟机不存在这个概念
      • 运行时常量池
        • 存储编译时生成的字面值和符号引用,Java 中常量不必定非要是编译期才能产生,因此这个区域是动态的
        • Java String 中的 intern (native 方法)方法会尝试将字符串放到常量池中并返回对常量池的引用,JDK 1.6 和 JDK 1.7 之间的实现有区别
      • 直接内存
        • 为了不拷贝数据,Java 提供直接操做非虚拟机管理的内存区域,这些内存被称为直接内存

JVM 内存 & OS 内存

  • 32位 OS 给每一个进程可用的内存空间是有限的, windows/linux 为 2GB(1.9GB)
  • JVM 提供了设定 Java 堆(-Xms/-Xmx)、方法区、每一个线程栈帧(-Xss)内存大小的机制

对象的建立(HotSpot 为例)

graph LR A(new obj) --> B[方法区检查] B -->|YES| C[calloc] B -->|NO| D[load] D --> C C --> E[构造句柄] E --> F[构造对象]

虚拟机内存概念

指针碰撞

假设 Java 堆中内存是绝对规整的,全部用过的内存都放在一边, 空闲的内存放在另外一边, 中间放着一个指针做为分界点的指示器, 那所分配内存就仅仅是把那个指针向空闲空间那边挪动 一段与对象大小相等的距离,这种分配方式称为“ 指针碰撞”( Bump the Pointer)数组

空闲列表

若是 Java 堆中的内存并非规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞 了,虚拟机就必须 维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的录, 这种分配方式称为“ 空闲列表”( Free List)ruby

TLAB

用于多线程对象的建立,多线程同时在堆上建立对象时不管使用指针碰撞仍是空闲列表,都会涉及同步问题多线程

每一个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲( Thread Local Allocation Buffer, TLAB)。哪一个线程要分配内存, 就在哪一个线程的 TLAB 上分配, 只有 TLAB 用完并分配新的 TLAB 时,才须要同步锁定框架

可使用 -XX:+/-UseTLAB来指定虚拟机是否使用TLABjvm

对象存储

对象头

HotSpot 对象头由两部分组成:jsp

  • 运行时数据,例如哈希码( HashCode)、 GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这些数据通常保存在固定的 32bits 或者 64bits 中
  • 对象类型指针,对应于 C++ 中的 vptr
实例数据 & 对齐填充

实例数据就是 Java 对象的有效信息,例如类中的成员变量等

HotSpot 规定对象的起始地址必须是 8 字节的整数倍,这个和 C/C++ 中内存对齐的概念是类似的

对象访问

对象访问分两类,使用句柄和直接指针,以下图所示

使用句柄的好处是便于垃圾回收与内存整理,只要修改句柄中对象的指针就能够移动对象

使用直接指针的好处是速度快,其比句柄方法少了一次指针定位的开销

graph LR A[<b>stack</b><br>obj handle refer] A --> B[<b>handle in heap</b><br/># pointer to obj data<br/># pointer to obj type infos] B --> C[<b>heap</b><br/>obj data] B --> D[<b>methods area</b><br/>obj type infos] E[<b>stack</b><br>obj refer] E --> F[<b>heap</b><br/>obj data<br/>pointer to type infos] F --> G[<b>methods area</b><br/>obj type infos]

溢出实例

堆溢出
import java.util.*;
// args: Args:-Xms20m-Xmx20m-XX:+HeapDumpOnOutOfMemoryError
public class test {
    static class OOMObject {
    }
    
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

使用工具(Eclipse memory analyzer 分析 dump 文件)判断是内存泄漏仍是内存溢出,也可使用工具查看对象到 GC Roots 的引用链,从而定位对象的来源

栈溢出

两种异常

  • StackOverflowError,栈溢出
  • OutOfMemoryError,JVM 在扩展栈时没法申请到足够的内存空间
// VM Args:-Xss128k
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++; // 递归时记录溢出时 栈帧个数
        stackLeak(); // 每一个函数调用都有本身的栈帧,用于保存返回地址等信息
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println(" stack length:" + oom.stackLength);
            System.out.println(e);
            System.exit(0);
        }
    }
}
String.intern() 简介

String.intern() 是一个 Native 方法,它的做用是:若是字符串常量池中已经包含一个等于此 String 对象的字符串,则返回表明池中这个字符串的 String 对象;不然,将此 String 对象包含的字符串添加到常量池中,而且返回此 String 对象的引用

JDK 6 和 JDK 7 中 intern 的实现有必定区别。JDK 6 将字符串对象保存在常量池中;JDK 7 将字符串对象保存在堆中而将字符串引用保存在常量池中

方法区溢出 & CGlib & JSP

可修改字节码以生成新的类,Spring、Hibernate 等框架都基于此类字节码技术

运行时使用 CGLIB 能够建立大量的类从而出现方法区溢出的异常

方法区溢出的另外一种场景就是包含大量 JSP 文件的场景,JSP 须要被编译为 Java 类才能运行

相关文章
相关标签/搜索