`
javayestome
  • 浏览: 1050891 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论
阅读更多

JVM是我们每个 java 程序员的功底了。也许有很多程序员不清楚 JVM 的工作原理, 今天,在这里带着大家一起深入研究一下我们可爱的 JVM 。废话少说我们切入正题。

要了解JVM 首先必须了解类加载的过程

上图表示的就是我们的类的初始化过程。

下面我们对每个阶段进行深入的探索。

类的加载:

不要被“加载”唬掉,所谓类的加载其实就是将 .class文件读入到内存中。这时候有朋友要问了,我们写程序的时候没有加载过类啊,类是怎么进入内存的呢。这就牵扯到类的加载器了。

java 中可分为四类加载器

引导类加载器 Bootstrap Loader    用来加载 %java_home%lib 下的核心类库像 String Date 等等

扩展类加载器 Extension Loader   用来加载%java_home%lib/ext 下的扩展 api

系统类加载器 AppClassLoader   加载classpath 下面的类文件,我们的所有类文件默认都是由   它来加载的,怎么样,感觉亲切吧

用户自定义的类加载器

下面我们举例说明类的加载

public class A{

public static void main(String args[]){

B b=new B();

}

}

Public class B{

Public static String a="hello";

public static String b=getValue();

Static{

System.out.println("Hello World");

}

Public static void getValue(){

}

}

假如我们自己定义了一个名字为 的类,当我们启动虚拟机即( java A )的时候,会创建一个 JVM 实例。现在我们就看类 的加载过程。由于 B 是被 引用 的,所以 B 是由 A 的 类加载器进行加载。在这里我们不得不说一下类加载器是比较孝顺的孩子,为什么这么说呢,因为类加载器在加载类的时候采用双亲委托机制。简单说就是在加载类 的时候,加载器会调用父类加载器来加载,父类再调用父类,依次类推。这个说起来比较抽象,我们这里给出源代码来表示一下其中的加载过程

public Class loadClass(String name){

ClassLoader parent=this.getClassLoader().getParent();

Try{

Class c=findLoadedClass(name);

//如果这个类没有被加载

If(c!=null){

//如果有父类加载器

If(parent!=null)

parent.loadClass(name);

Else

BootstrapLoader.loadClass(name)

}catch(FileNotFoundException ex){

//如果父类加载器找不到,就调用自己的 findClass 查找

this.findClass(name);

}

//如果这个类已经被加载

Else

Return c;

}

这代码是我自己写的,是对源代码的简化表示,不要直接拷贝使用,如果想要知道详细内容,建议参源码。这段可以完全清晰地表示出类加载器的调用关系了。但是里面有个问题,相信各位都会发现了,就是 BootstrapLoader.loadClass(name).BootstrapLoader 为什么不创建实例呢?因为 BootstrapLoader 并不是用 java 写的,是一个本地方法( native ),也就是说是用 c/c++ 或者其他语言编写的方法。为什么要这么做呢?主要是因为我们的类加载器也是类,如果他们都是用 java 实现,那么他们如何加载?所以, sun 给了我们一个引导类加载器用来加载其他的类加载器,之后我们才能用这些类加载器加载我们的类文件。这里我们说一下他们的父子关系。

我们自定义的类加载器的父类加载器是  AppClassLoader

AppClassLoader的父类加载器是 Extension Loader

Extension Loader的父类加载器是  Bootstrap Loader

当我们加载类B 的时候,由于没有指定它的类加载器,默认由 AppClassLoader 进行加载,调用 loadClass ()方法, AppClassLoader 发现它的 parent 不是 null ,就会调用父类加载器 (Extension Loader) 加载, Extension Loader 发现它的父母是 null (因为 BootstrapLoader  不是 java 写的,所以不会被 Extension Loader 访问到)于是就调用 BootstrapLoader 来加载,由于我们的 B 类是在我们的 classpath 中,所以必然会产生 ClassNotFoundException  ,接着调用自己的 findClass 进行查找, ExtensionLoader 访问的是 %java_home%/lib/ext 下面的类,必然也无法找到我们的 B 。于是会在 AppClassLoader 中捕获到异常,然后接着调用 AppClassLoader findClass 进行加载,结果找到了。

终于啊,经过这么复杂的递归调用和冒泡查找后找到了我们的类 了。至于为什么要设计的这么复杂,直接加载不就完了吗,干嘛搞得这么难受。这主要是出于安全性的考虑。你想想,这个过程中总是由 Bootstrap 来加载核心类,假如你自己写了一个名字叫 String 的类,里面含有攻击性的代码,如果能加载成功,必然会导致其他依赖此类的类导致错误,整个 JVM 就会崩溃。然而这个类是无法加载到内存中的,因为类的加载总是由 BootstrapLoader 开始,当他发现已经加载了 String ,就不会再加载了,有效地保证了系统的安全性。类的加载过程基本就这样,下面贴出一段代码,自己实现的类加载器。

 

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

 

 

public class MyClassLoader extends ClassLoader{

private String path="F:\\JSP\\ClassLoaderDemo\\classes\\";

private String fileType=".class";

@Override

public Class  findClass(String name){

byte bytes[]=this.loadClassData(name);

return this.defineClass(name, bytes, 0, bytes.length);

}

//加载类数据,返回一个 byte 数组

public byte[] loadClassData(String name){

try {

FileInputStream fin=new FileInputStream(path+name+fileType);

ByteArrayOutputStream bout=new ByteArrayOutputStream();

int ch=0;

while((ch=fin.read())!=-1){

bout.write(ch);

}

return bout.toByteArray();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

}

//类加载器的测试类

public   class  TestLoader {

public   static   void  main(String argsp[]){

MyClassLoader loader= new  MyClassLoader();

try  {

//在指定的目录中加载 HelloWorld.class 文件

Class myclass=loader.loadClass( "HelloWorld" );

//加载完毕后进行实例化 , 这个过程包含了对类的解析

myclass.newInstance();

System. out .println(myclass.getName());

catch  (ClassNotFoundException e) {

e.printStackTrace();

catch  (InstantiationException e) {

e.printStackTrace();

catch  (IllegalAccessException e) {

e.printStackTrace();

}

}

}

这段代码可以直接拷贝运行.

我们大篇幅的讲述了类的加载过程,这是jvm 运行的第一步,建议各位读到这里的时候在脑海中回顾一下类的整个加载过程,以便于理解下面我们要说的类的链接。如果你觉得理解的没有问题,我们继续说一下类的连接阶段。

当我们将类加载到内存中的时候,我们就需要对他进行验证。这里我们先介绍一下JVM 是如何进行虚拟的。

想必大家都知道我们的CPU 是由控制器,运算器,存储器,输入输出设备组成的,这些是我们程序运行所必需的硬件环境。你可以这样认为,我们的 JVM 为我们的 java 程序虚拟出了完整的一套运行环境,他的控制器由我们的 JVM 直接担任,像垃圾处理了内存分配了等等,运算器当然就是我们的 cpu 了,存储器是 jvm 的运行数据区,输入输出设备就是硬件设备了。这里你可以发现,我们的 java 是不能直接与硬件进行交互的,底层功能的实现需要通过本地方法进行实现,这就是我们的 java 跨平台的原因。我们的 JVM 会根据硬件环境的不同(这里主要是指 CPU 的指令集的不同),将我们的 class 文件解释成 cpu 可以识别的指令码,这样,我们的 CPU 就能够运行我们的 java 程序了,这就是 java 的伟大之处。更确切的说使我们 JVM 的伟大之处了,呵呵。

这里,你只需要大概的了解一下JVM 的原理就 OK 了,之后我们会细细的讲解。

我们现在再说说类的连接阶段。

当我们把类加载到内存之后,我们如何保证他的正确性呢,或者说我们如何保证加载进来的二进制码是不是符合我们的Class 类型的结构呢?关于结构,要细细说来需要很大的篇幅,这里你只需要这样理解他 Class 就像是类的模板一样,它包含类的所有信息,包括访问控制符号( public private ,友好型)、是类还是接口,直接父类是谁,实现的接口有什么,以及字段信息,方法信息 ,还有一个常量池。你看看,这不就是我们类所包含的所有信息吗。他们按照一定的结构组织在内存中,我们把这样的一块内存结构称为 Class 。就是我们常说的类类型。

我们接着说,为了保证我们加载的二进制代码是Class 结构,所以我们需要进行校验,很多地方称为验证,我感觉称之为校验更为合适。我们的校验程序校验完毕,发现它是我们需要的 Class 结构的时候,就会通知 JVM 为我们 Class 在方法区域分配空间。

说道这里,我们又要说说我们JVM 的运行时数据区了,也就是他的存储结构,下面,我会用图示的方法来解释它。

 

这是我用 Excel表格画的,不太好看,凑合着用吧。

我们的JVM 将运行数据区分为如下几块

堆:用来存储我们创建的对象的地方

栈:JVM 会 为每个线程创建一个栈,用来存储局部变量和操作数,栈跟栈之间不能通信。存储单位是栈帧。我们每调用一个方法,就新建一个栈帧压入栈中。栈帧之间是屏蔽 的,这就是为什么一个方法无法访问另一个方法中的变量。栈帧由局部变量区(用数组实现),操作数栈(栈结构),帧数据(主要用来支持对类常量池的解析,方 法的正常返回,异常处理)

方法区:用来保存Class 类型数据

JVM的内存主要结构就这么多了。

好了,我们接着说,也许你现在对这张图还有很多疑问,稍后你就会明白了。我们的类现在已经通过验证了,校验器告诉我们它符合我们的Class 结 构,而且在方法区域为他分配了空间,我们非常高兴。下面就是关乎初始化问题了。有人会问,不对还有解析呢。呵呵,在写程序我们也知道了,这个阶段是可选 的,也就是说你可以让你的类加载后马上初始化,也可以加载完毕不进行初始化。在这里我们要让我们的类初始化,下面即使解析阶段了。

解析阶段的主要任务:将类变量的符号引用解析成直接地址引用。就拿我们的变量a 来说,他的值是 "hello", 这个东西在 Class 中只是一个符号而已。然而,我们的 Class 需要将所有的常量都存放在常量池中,所以 hello 会被存储到常量池中,然后提供一个入口地址给 a 就能直接引用它了。这里得好好地理解理解。

我们的方法b 引用的是一个方法,这个方法在 Class 中只是一个符号而已,这个方法的实际代码存放在一张表中,这张表我们成为方法表。我们的 b 就指向了方法表的一个引用。

解析完毕之后,就要初始化了,初始化很简单,就是执行静态代码块中的内容了。整个加载到此已经完毕,想必大家已经很清楚了吧。

然而,我们的JVM 的任务才刚刚开始。

下面我们说一下对象的创建吧,想必这个问题在很多人看来都是很不解的。那么我们马上开始吧。

对象的实例是什么呢?在内存中的样子是什么呢。

如果你知道了方法区中的东西,对于对象也就不难理解了。对象就像一种结构,其中存储了指向方法的引用,实例变量,一个指向类常量池的引用(这就是为什么实例可以访问类变量和类方法)。这些数据按照一定的结构(就像Class 结构一样,只是简单很多)存储在我们的堆区,这就是我们耳熟能详的对象。当我们 new 的时候, JVM 就会按照上面的过程,在堆区为我们构造一个这样的数据结构,然后将块数据的引用存储到栈里面。稍后我们会细细讲解栈的结构

说到堆,我们不得不说JVM 的内存管理机制,或者堆空间的垃圾处理机制。假如你自己写了一个 JVM ,你肯定会碰到这样一个问题,我们不断的在堆里面创建对象,再大的内存也有耗尽的时候,那我们如何进行垃圾处理呢。以前,在 JDK1 的时候采用的是对整个堆空间进行扫描,查找不再被使用的对象将其回收,可想而知这种策略是多么的低效。后来,我们聪明的 java 工程师给我们提供了这样的存储结构,他们将堆分为了两大部分,新生区和永久区。

2
3
分享到:
评论
1 楼 eric_shi 2011-02-12  
图呢 lz

相关推荐

    深入JVM内核—原理、诊断与优化视频教程-3.常用JVM配置参数

    本教程——“深入JVM内核—原理、诊断与优化视频教程”,将重点讲解这些关键点,帮助开发者提升技术水平,更好地解决实际问题。 首先,我们来探讨JVM的内核原理。JVM主要由类加载子系统、运行时数据区、执行引擎、...

    从 0 开始带你成为JVM实战高手.txt

    从零开始学习并掌握Java虚拟机(JVM)的相关知识对于任何希望深入理解Java程序运行机制的开发者来说都是一项重要技能。本篇文章将基于提供的文件标题、描述以及部分链接信息来构建一系列关于JVM的核心知识点。 ### ...

    深入jvm 内核-原理,诊断于优化视频教程

    ### 深入JVM内核:原理、诊断与优化 #### 一、JVM基础知识 **1.1 JVM概念** Java虚拟机(Java Virtual Machine,简称JVM)是一种用于执行Java字节码的虚拟机。它为Java程序提供了一个运行环境,能够独立于硬件平台...

    深入JVM内核—原理、诊断与优化

    《深入JVM内核—原理、诊断与优化》是一份深度探索Java虚拟机(JVM)的视频教程,旨在帮助开发者全面理解JVM的工作机制,掌握性能诊断技巧,并能进行有效的优化。本教程覆盖了从基础到高级的JVM主题,不仅适用于Java...

    深入JVM内核—原理、诊断与优化视频教程-2.JVM运行机制

    【JVM运行机制详解】 Java虚拟机(JVM)是Java平台的核心组成部分,它负责执行...在《深入JVM内核—原理、诊断与优化》视频教程中,会详细讲解这些内容,并结合实际案例进行演示和实践,帮助开发者成为JVM领域的专家。

    从 0 开始带你成为JVM实战高手【完整版】

    ### 从 0 开始带你成为JVM实战高手【完整版】 #### 一、引言 随着Java技术栈在企业级应用中的广泛采用,深入理解Java虚拟机(JVM)对于提升开发效率、优化系统性能至关重要。本教程旨在通过96个章节的内容,帮助...

    深入jvm内核-原理、诊断与优化教学视频

    1、java虚拟机的基本介绍。 2、字节码的执行 3、常用的jvm参数配置 4、算法和种类 5、gc参数配置 6、类加载器 7、性能监控工具 8、jvm堆栈分析

    深入jvm虚拟机

    本书讲述了Java虚拟机一运行 所有Java程序的抽象计算机,还讲了几种与虚拟机密切相关的核心Java API。本书通过分析讲解、可运行的示例、参考资料和applet (它作为文中所述概念的交互式例示),提供了Java技术的深人...

    深入理解JVM.rar

    每个使用Java的开发者都知道Java字节码是在JRE中运行,而JVM则是JRE中的核心组成部分,承担分析和执行Java字节码的工作,而Java程序员通常并不需要深入了解JVM运行情况就可以开发出大型应用和类库。尽管如此,如果你...

    深入理解JVM&G1; GC

    《深入理解JVM & G1 GC》一书深入剖析了Java虚拟机(JVM)的工作原理,特别是针对垃圾收集器(GC)中的G1(Garbage-First)算法进行了详尽的探讨。JVM是Java程序运行的基础,它负责解析、编译、执行Java代码,并管理...

    深入理解jvm虚拟机

    标题提到的“深入理解JVM虚拟机”,意味着要对JVM的内部机制进行深入的研究和理解。其核心知识点涵盖了如下几个方面: 1. JVM架构组成:JVM主要由类加载器(Class Loader)、运行时数据区(Runtime Data Area)、...

    深入JVM概要 JVM详解

    深入JVM概要 JVM详解 本文将详细介绍Java虚拟机(JVM)的内部机理和实现原理,从类型的生命周期、方法区、常量池、类加载器、垃圾收集器、栈和局部变量等方面对JVM进行深入解析。 类型的生命周期 类型的生命周期...

    JVM 完整深入解析.pdf

    Java虚拟机(JVM)是运行Java程序的关键平台,其内部结构和内存管理机制非常复杂且精细。它将内存分为若干个不同的数据区域,以便管理Java程序的运行时数据。下面将详细介绍JVM中的运行时数据区,以及Java内存模型...

    深入JVM内核—原理、诊断与优化视频教程

    ### 深入JVM内核—原理、诊断与优化 #### 一、JVM基础知识 ##### 1.1 JVM概念 Java虚拟机(Java Virtual Machine,简称JVM)是运行Java字节码的虚拟机环境。它为Java程序提供了一个独立于硬件平台的抽象计算机环境...

    深入理解JVM.

    深入理解JVM,首先要明白Java技术的组成部分,包括Java编程语言、Java类文件格式、Java虚拟机(JVM)和Java应用程序接口(Java API)。这些组件共同构成了Java平台,使得Java程序能够实现跨平台运行。 Java虚拟机是...

    深入解析jvm深入解析jvm

    深入解析jvm深入解析jvm深入解析jvm深入解析jvm深入解析jvm深入解析jvm

    圣思园张龙 深入理解jvm

    根据提供的文件信息,“圣思园张龙 深入理解jvm”,我们可以推断出这份资料主要关注于Java虚拟机(JVM)的深入理解和实践应用。JVM是Java开发环境中非常核心的一个组成部分,它不仅为Java程序提供了运行时环境,还负责...

    [转载]深入理解JVM

    ### 深入理解JVM #### 一、Java技术与Java虚拟机 Java不仅仅是一种编程语言,更是一项综合性的技术。它主要包括四个关键组成部分: 1. **Java编程语言**:这是一种面向对象的编程语言,提供了丰富的类库支持,...

Global site tag (gtag.js) - Google Analytics