Обзор
java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
上面这段是《深入理解java虚拟机》中对类加载的描述,其实简单点说就是程序从最开始的.java文件到.Class文件,Class文件中包java虚拟机指令集和符号表以及其他辅助信息,而这些信息最终加载到虚拟机才能被使用。接下来我们就一起讨论这些Class文件如何被加载以及被加载后变成了什么。
жизненный цикл класса
Как показано на рисунке выше, описан жизненный цикл класса. Среди них пять действий загрузки, проверки, подготовки, инициализации и выгрузки существуют последовательно, и фаза синтаксического анализа может быть завершена после инициализации. Эти действия обычно чередуются и смешиваются друг с другом. Ниже мы в основном обсуждаем пять шагов загрузки, проверки, подготовки, синтаксического анализа и инициализации.нагрузка
-
когда загружать
1.预加载:在虚拟机启动的时候加载,加载的是JAVA_HOME/lib/下的rt.class下的.class文件,是java程序运行时经常要用到的一些类,比如java.lang.⁎以及 java.util.⁎等 2.运行时加载:虚拟机在用到一个.class文件时,首先会去内存中查找这个.class文件有没有被加载,没有被加载会根据这个类的全限定名去加载。
-
Что делает виртуальная машина на этапе загрузки
1. 通过一个类的全限定名获取定义此类的二进制字节流。 2. 将这个字节流所代表的静态存储结构转化为方法区的运行数据结构。 3. 在内存中生成一个唯一代表此类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口。(一般这个class对象会存储在堆中,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的。)
虚拟机对上述三点的要求并不算具体,例如第一条,根本没指明二进制字节流从哪里来,怎么来,包括以下几点:
从zip包中获取,这就是以后jar、ear、war格式的基础
从网络中获取,典型应用就是Applet
运行时计算生成,典型应用就是动态代理技术
由其他文件生成,典型应用就是JSP,即由JSP生成对应的.class文件
从数据库中读取,这种场景比较少见...
проверять
顾名思义,是对Class文件字节流的验证,而验证的目的则是为了确保当前的Class文件符合java虚拟机的要求,并且不会危害虚拟机自身的安全。通常主要包括以下几点验证内容:
1. 文件格式验证
其实就是验证字节流是否符合Class文件规范,符合规范通过验证才能保证输入的字节流能正确的被解析并存储到方法区。
2. 元数据验证
对类的元数据信息进行语义校验。
3. 字节码验证
最为复杂的校验阶段,校验程序语义是否符合规范,符合逻辑,对类的方法体进行校验。
4. 符号引用验证
发生在将符号引用转换为直接引用的时候,可以看做是对类自身以外(常量池中各种符号的应用)信息的匹配校验,如:符号引用中通过字符串描述的全限定名是否能找到对应的类;符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被 当前类访问...
Подготовить
正式为类变量分配内存并赋初值。
需要注意两点
1. 只为类变量,即被static修饰的变量分配内存,实例变量在实例初始化的时候会随对象一起分配在堆中。
2. 这个阶段赋初始值的变量指的是那些不被final修饰的static变量,比如”public static int value = 123;”,value在准备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如”public static final int value = 123;”就不一样了,在准备阶段,虚拟机就会给value赋值为123。
基本数据的零值如下表:
Разобрать
解析是虚拟机将常量池中的符号引用转换为直接引用的过程。
1. 符号引用
这个其实是属于编译原理方面的概念,符号引用包括了下面三类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
Вы можете видеть, что в пуле констант 22 элемента, то есть пул констант, а элементы с «Utf8» являются символическими ссылками. Например, № 2 имеет значение «com/xrq/test6/TestMain», что представляет собой полное имя класса; например, № 5 — это i, № 6 — это I, они представляют собой пару, указывающую, что переменная имеет тип Integer (int), имя называется i; #6 - D, #7 - d, то же самое верно, указывая на переменную типа Double (double), имя - d; #18, #19 - названия методов.
По сути, символические ссылки — это то же самое, что мы сказали выше, то есть описания классов, переменных и методов. Ссылки на символы не имеют ничего общего с расположением памяти виртуальной машины, и цель, на которую указывает ссылка, может быть не загружена в память.
2. 直接引用
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机示例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经存在在内存中了。
инициализация
开始真正的执行类中定义的java代码,初始化过程就是执行类构造器<clinit>()的过程,还记得之前的准备阶段是给类变量分配内存赋初值,这里就是将类变量赋予用户指定的值
。
虚拟机规范定义了“有且仅有”5中会触发初始化的场景:
1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果累没有进行初始化,则要先触发初始化;
2. 使用java.lang.reflect包中的方法对类进行反射调用的时候;
3. 初始化类时,若发现其父类还没有初始化,则先触发父类的初始化;
4. 虚拟机启动的时候,虚拟机会先初始化用户指定的包含main()方法的那个类
5. 当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
Модель родительского делегирования
介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在jvm中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
从jvm角度来看只存在两种类加载器
-
Начать загрузку классаBootstrap ClassLoader, который является частью самой виртуальной машины, используется для загрузки библиотек классов в каталог JAVA_HOME/lib/ или по пути, указанному параметром -Xbootclasspath и распознаваемому виртуальной машиной.
-
другие загрузчики классов: Реализовано языком Java, унаследованным от абстрактного класса ClassLoader:
-
загрузчик класса расширения(Расширение ClassLoader): отвечает за загрузку всех библиотек классов в каталог
\lib\ext или путь, указанный системной переменной java.ext.dirs. - загрузчик класса приложения(Приложение ClassLoader). Отвечая за загрузку указанной библиотеки классов в пути к классам пользователя (classpath), мы можем использовать этот загрузчик классов напрямую. В общем, если у нас нет пользовательского загрузчика классов, этот загрузчик используется по умолчанию.
-
загрузчик класса расширения(Расширение ClassLoader): отвечает за загрузку всех библиотек классов в каталог
Как работает модель родительского делегированияДа: если загрузчик класса получает запрос на загрузку класса, он не будет сначала загружать класс сам по себе, а делегирует запрос загрузчику родительского класса для завершения, загрузчик классов каждого слоя такой, поэтому все запросы на загрузку передаются к загрузчику классов запуска верхнего уровня, а дочерний загрузчик попытается загрузить класс только в том случае, если родитель не может выполнить запрос на загрузку (необходимый класс не найден в его области поиска). Вот хорошо известный пример использования родительской модели делегирования.
Хакеры настраивают класс java.lang.String, который имеет те же функции, что и системный класс String, но с небольшой модификацией определенной функции. Как и функция equals, эта функция часто используется, если в эту функцию хакеры добавляют какой-то «вирусный код». И добавил в JVM через пользовательский загрузчик классов. В это время, если нет родительской модели делегирования, JVM может ошибочно подумать, что класс java.lang.String, настроенный хакером, является классом String системы, что приведет к выполнению «кода вируса».
В модели родительского делегирования пользовательский класс java.lang.String хакера никогда не загружается в память. Поскольку самый верхний загрузчик классов сначала загружает системный класс java.lang.String, последний пользовательский загрузчик классов не может загрузить класс java.lang.String.
Может быть, вы думаете, что я заставляю пользовательский класс java.lang.String загружаться в пользовательский загрузчик классов, не лучше ли не вызывать родительский загрузчик?Действительно, это осуществимо. Однако в JVM при определении того, относится ли объект к определенному типу, если фактический тип объекта отличается от загрузчика классов сравниваемого типа, он вернет false.
Возьмем простой пример:
И ClassLoader1, и ClassLoader2 загружают класс java.lang.String, соответствующий объектам Class1 и Class2. Тогда объект Class1 не принадлежит типу java.lang.String, загруженному объектом ClassLoad2.
Как реализовать модель родительского делегирования
双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实ClassLoader类默认的loadClass方法已经帮我们写好了,我们无需去写。
Нарушение модели родительского делегирования
双亲委派模型并不是一个强制性约束,而是java设计者推荐给开发者的类加载器的实现方式,在一定条件下,为了完成某些操作,可以“破坏”模型。
1.重新loadClass方法
2.利用线程上下文加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的 setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承 一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序 类加载器。
3.为了实现热插拔,热部署,模块化,意思是添加一个功能或减去一个功能不用重启,只需要把这模块连同类加载器一起换掉就实现了代码的热替换。
Эта статья относится к «Глубокому пониманию виртуальной машины Java».
[Углубленный анализ механизма Java ClassLoader (уровень исходного кода)] (http://www.hollischuang.com/archives/199)
[Загрузка, связывание и инициализация классов Java] (http://www.hollischuang.com/archives/201)