一,JVM 结构

如何从java中读取虚拟机中的文件_方法区

JVM 主要由类加载子系统, 运行时数据区(内存空间),执行引擎,以及本地方法接口等组成。其中运行数据区又包括 方法区,堆,Java栈,本地方法栈,程序计数器。有些区域随着虚拟机启动而存在,有些区域依赖用户线程的启动而建立,线程的结束而销毁。

1,类加载子系统 Class Loader

负责加载编译好的 .class 文件,并装入内存,使JVM 可以实例化或者以其他方法使用加载后的类。JVM的类加载子系统支持在运行时的动态加载。

类加载子系统 工作原理

类加载分为装载、链接、初始化三步。

  • 装载
    通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM。当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。
    在内存中,ClassLoader实例和类的实例都位于堆中,类的其他信息位于方法区中。
    装载过程采用了一种“双亲委派模型”的方式。
  • 链接
    链接的任务是把二进制的类型信息合并到JVM 运行状态中去。
  • 初始化
    初始化类中的静态变量,并执行类中的static 代码,构造函数。
    JVM规范严格定义了何时需要对类进行初始化:
    a、通过new关键字、反射、clone、反序列化机制实例化对象时
    b、调用类的静态方法时。
    c、使用类的静态字段或对其赋值时。
    d、通过反射调用类的方法时。
    e、初始化该类的子类时(初始化子类前其父类必须已经被初始化)。
    f、JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。

2,运行数据区(内存)

2.1 **Java 栈**

Java栈是随线程的启动而建立,线程的结束而销毁。栈是线程私有,一个线程对应一个栈,每个栈由许多栈帧组成的.

栈帧

一个方法对应一个栈帧,线程调用某个方法时,虚拟机压入一个新的栈帧到该线程的Java栈中,当该方法返回时,这个栈帧被弹出并抛弃。

存储数据

Java栈帧的主要中存储了方法局部变量,操作数栈,动态连接,以及方法返回地址。
关于栈帧的大小: 编译期就就已经决定了局部变量表的大小,操作栈数的深度,所以栈帧的大小是固定的,不受程序影响。

如何从java中读取虚拟机中的文件_方法区_02

java栈中的异常
  • StackOverFlowError
    当线程请求的栈深度大于虚拟机所允许的深度时,会抛出 StackOverFlowError。
  • OOM
    虚拟机支持动态扩展时,如果无法申请到足够的内存,会抛出OOM异常

2.2**本地方法栈**

在Sun JDK中,本地方法栈和Java栈是同一个。

2.3方法区

是线程共享的。

存储数据



方法区存储

类元数据、常量、静态变量

方法区中对于每个类存储了以下数据:


a.类及其父类的全限定名(java.lang.Object没有父类)


b.类的类型(Class or Interface)


c.访问修饰符(public, abstract, final)


d.实现的接口的全限定名的列表


e.

常量池


f.字段信息


g.方法信息


h.

静态变量


i.ClassLoader引用


j.Class引用

异常



OOM


当方法区无法满足内存分配需求时,会抛出OOM异常

Hotspot 将其称为永久代(Permanent Generation),但是这部分区域也需要GC。运行时常量池(Runtime Constant Pool)是方法区的一部分,用来存放符号引用,常量,直接引用等。JDK 1.7开始,字符串常量池从方法区中移出

2.4堆 Heap



存储数据




线程共享的。用于存放对象实例。也被称为”GC堆”,因为 堆是垃圾收集器的主要管理区域。


异常

在堆内存的管理上,Sun JDK从1.2版本开始引入了分代管理的方式。
分为年轻代(Young Generation),老年代(Old Generation)。这种分代方式大大改善了垃圾收集的效率。

  • 新生代:每次GC 时,这块区域的对象都有大批死去,因此称为新生代。新生代可以继续分为伊甸园(Eden)和两个存活区(survivor space) 。
  • 老年代:每次GC 时,这块区域的对象存活率较高。

如何从java中读取虚拟机中的文件_Java_03


最后堆中的需要等GC 来清理,栈中的直接销毁了。

3,执行引擎

执行引擎是 JVM 执行Java字节码的核心。

二,堆,栈,方法区

上面看了JVM的体系结构,现在对比了解JVM 内存中的3个区:堆heap,栈 stack,方法区 method。

堆区:
1. 全部是对象,每个对象都包含与之对应的 class 信息。(class的目的是得到操作指令)。不存放基本类型和数据引用,只存放对象本身.

栈区:
2. Java栈是由许多栈帧组成的。每个线程包含一个栈区。
3. 栈中只保存基础数据类型的对象和自定义对象的引用(对象都放在堆区)
4. 每个栈中的数据都是私有的,其他栈不能访问。
5. 栈分为3 个部分:基本类型变量区,执行环境上下文,操作指令区(存放操作指令)

方法区:
1. 线程共享,方法区包含所有的 class 和 static 变量
2. 方法区中包含的都是在整个程序中永远唯一的元素,如 class ,static 变量。

为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个非常简单的小程序。

public class Demo {  //运行时,JVM 把demo 的信息都放入方法区

    public static void main(String[] args) {   //mian 方法本身放入方法区

        //test1 是引用,所以放到栈区,Sample 是自定义的对象,放到堆中
        Sample s1=new Sample("测试1");
        Sample s2=new Sample("测试2");

        s1.printName();
        s2.printName();
    }
}

 class Sample{    //运行时,JVM 把sample 的信息都放入方法区

     /* 范例名称 */

    // new  Sample 实例后,name 引用放入到栈区里,name 对象放入堆里
    private String name; 

    /* 构造方法 */

    public Sample(String name){
        this.name=name;
    }

    /* 输出 */
    public void printName(){  // print 方法本身放入方法区里
        System.out.println(name);
    }
}

发出指令:java Demo ,系统收到指令,启动一个Java 虚拟机进程,这个进程首先从classpath中找到Demo.class 文件,读取文件中的二进制数据,然后把Demo 类的类信息存放到运行时数据区。这一过程就是Demo 类的加载过程。

接着,Java 虚拟机定位到方法区中 Demo类的Main() 方法,开始执行它的指令:
第一条:Sample test1=new Sample("测试1");

就是让虚拟机创建一个Sample 实例,并且使用 test1 引用这个实例。貌似小case一桩哦,就让我们来跟踪一下Java虚拟机,看看它究竟是怎么来执行这个任务的:

1、
Java虚拟机一看,不就是建立一个Sample实例吗,简单,于是就直奔方法区而去,先找到Sample类的类型信息再说。结果呢,嘿嘿,没找到@@,这会儿的方法区里还没有Sample类呢。可Java虚拟机也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类,把Sample类的类型信息存放在方法区里。

2、 好啦,资料找到了,下面就开始干活啦。Java虚拟机做的第一件事情就是在堆区中为一个新的Sample实例分配内存,
这个Sample实例持有着指向方法区的Sample类的类型信息的引用。这里所说的引用,实际上指的是Sample类的类型信息在方法区中的内存地址,其实,就是有点类似于C语言里的指针啦~~,而这个地址呢,就存放了在Sample实例的数据区里。

3、
在JAVA虚拟机进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!位于“=”前的Test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,它被会添加到了执行* main() 方法的主线程*的 JAVA 方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用。

OK,到这里为止呢,JAVA虚拟机就完成了这个简单语句的执行任务。参考我们的行动向导图,我们终于初步摸清了JAVA虚拟机的一点点底细了

接下来,JAVA虚拟机将继续执行后续指令,在堆区里继续创建另一个Sample实例,然后依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令.——-printName 方法在方法区???不太理解(类的所有信息都存储在方法区中。)

如何从java中读取虚拟机中的文件_Java_04