你知道 Java 类是如何被加载的吗?
前言
Java 类是如何被加载的
我们首先要清楚的是,Java 类何时会被加载?《深入理解 Java 虚拟机》给出的答案是:
- 遇到 new、getstatic、putstatic 等指令时。
- 对类进行反射调用的时候。
- 初始化某个类的子类的时候。
- 虚拟机启动时会先加载设置的程序主类。
- 使用 JDK 1.7 的动态语言支持的时候。
利用 ClassLoader 加载类很简单,直接调用 ClassLoder 的 loadClass()方法即可,我相信大家都会,但是还是要举个例子:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");
}
}
JVM 默认用于加载用户程序的 ClassLoader 为 AppClassLoader,不过无论是什么ClassLoader,它的根父类都是 java.lang.ClassLoader。在上面那个例子中,loadClass()方法最终会调用到 ClassLoader.definClass1()中,这是一个 Native 方法。
static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
jclass cls,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd,
jstring source)
{
......
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
......
return result;
}
Java_java_lang_ClassLoader_defineClass1 主要是调用了JVM_DefineClassWithSource()加载类,跟着源码往下走,会发现最终调用的是 jvm.cpp 中的 jvm_define_class_common()方法。
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
TRAPS) {
......
ClassFileStream st((u1*)buf, len, source, ClassFileStream::verify);
Handle class_loader (THREAD, JNIHandles::resolve(loader));
if (UsePerfData) {
is_lock_held_by_thread(class_loader,
ClassLoader::sync_JVMDefineClassLockFreeCounter(),
THREAD);
}
Handle protection_domain (THREAD, JNIHandles::resolve(pd));
Klass* k = SystemDictionary::resolve_from_stream(class_name,
class_loader,
protection_domain,
&st,
CHECK_NULL);
......
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
class InstanceKlass: public Klass {
protected:
Annotations* _annotations;
......
ConstantPool* _constants;
......
Array<jushort>* _inner_classes;
......
Array<Method*>* _methods;
Array<Method*>* _default_methods;
......
Array<u2>* _fields;
}
bool DoObjectLock = true;
if (is_parallelCapable(class_loader)) {
DoObjectLock = false;
}
ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL);
Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
check_loader_lock_contention(lockObject, THREAD);
ObjectLocker ol(lockObject, THREAD, DoObjectLock);
2:解析文件流,生成 InstanceKlass。
InstanceKlass* k = NULL;
k = KlassFactory::create_from_stream(st,
class_name,
loader_data,
protection_domain,
NULL, // host_klass
NULL, // cp_patches
CHECK_NULL);
3:利用 SystemDictionary 注册生成的 Klass 。
我们来看看注册的代码:
if (is_parallelCapable(class_loader)) {
InstanceKlass* defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);
if (!HAS_PENDING_EXCEPTION && defined_k != k) {
// If a parallel capable class loader already defined this class, register 'k' for cleanup.
assert(defined_k != NULL, "Should have a klass if there's no exception");
loader_data->add_to_deallocate_list(k);
k = defined_k;
}
} else {
define_instance_class(k, THREAD);
}
ClassFileParser parser(stream,
name,
loader_data,
protection_domain,
host_klass,
cp_patches,
ClassFileParser::BROADCAST, // publicity level
CHECK_NULL);
InstanceKlass* result = parser.create_instance_klass(old_stream != stream, CHECK_NULL);
InstanceKlass* const ik =
InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);
(2):分析 Class 文件,填充 InstanceKlass 内存区域
fill_instance_klass(ik, changed_by_loadhook, CHECK_NULL);
内存分配代码如下:
const int size = InstanceKlass::size(parser.vtable_size(),
parser.itable_size(),
nonstatic_oop_map_size(parser.total_oop_map_count()),
parser.is_interface(),
parser.is_anonymous(),
should_store_fingerprint(parser.is_anonymous()));
ClassLoaderData* loader_data = parser.loader_data();
InstanceKlass* ik;
ik = new (loader_data, size, THREAD) InstanceKlass(parser, InstanceKlass::_misc_kind_other);
这里首先计算了InstanceKlass在内存中的大小,要知道,这个大小在Class 文件编译后就被确定了。
然后便 new 了一个新的 InstanceKlass 对象。这里并不是简单的在堆上分配内存,要注意的是 Klass 对 new 操作符进行了重载:
void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
return Metaspace::allocate(loader_data, word_size, MetaspaceObj::ClassType, THREAD);
}
MetaspaceObj::Type type, TRAPS) {
......
MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;
......
MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
......
return result;
}
void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loadhook, TRAPS) {
.....
ik->set_class_loader_data(_loader_data);
ik->set_nonstatic_field_size(_field_info->nonstatic_field_size);
ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields);
ik->set_static_oop_field_count(_fac->count[STATIC_OOP]);
ik->set_name(_class_name);
......
java_lang_Class::create_mirror(ik,
Handle(THREAD, _loader_data->class_loader()),
module_handle,
_protection_domain,
CHECK);
}
到这儿,Class 文件已经完成了华丽的转身,由冷冰冰的二进制文件,变成了内存中充满生命力的 InstanceKlass。
public class CustomClassloader extends URLClassLoader {
public CustomClassloader(URL[] urls) {
super(urls);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("com.wangxiandeng")) {
return findClass(name);
}
return super.loadClass(name, resolve);
}
}
public class Test {
public static void main(String[] args) throws Exception {
URL url[] = new URL[1];
url[0] = Thread.currentThread().getContextClassLoader().getResource("");
CustomClassloader customClassloader = new CustomClassloader(url);
Class clazz = customClassloader.loadClass("com.wangxiandeng.Student");
Student student = (Student) clazz.newInstance();
}
}
Exception in thread "main" java.lang.ClassCastException:
com.wangxiandeng.Student cannot be cast to com.wangxiandeng.Student
为什么呢?
因为实例化的 Student 对象所属的 InstanceKlass 是由 CustomClassLoader 加载生成的,而我们要强转的类型 Student.Class 对应的 InstanceKlass 是由系统默认的 ClassLoader 生成的,所以本质上它们就是两个毫无关联的 InstanceKlass,当然不能强转。
有同学问到:为什么“强转的类型 Student.Class 对应的 InstanceKlass 是由系统默认的 ClassLoader 生成的”?
其实很简单,我们反编译下字节码:
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=5, args_size=1
0: iconst_1
1: anewarray #2 // class java/net/URL
4: astore_1
5: aload_1
6: iconst_0
7: invokestatic #3 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
10: invokevirtual #4 // Method java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader;
13: ldc #5 // String
15: invokevirtual #6 // Method java/lang/ClassLoader.getResource:(Ljava/lang/String;)Ljava/net/URL;
18: aastore
19: new #7 // class com/wangxiandeng/classloader/CustomClassloader
22: dup
23: aload_1
24: invokespecial #8 // Method com/wangxiandeng/classloader/CustomClassloader."<init>":([Ljava/net/URL;)V
27: astore_2
28: aload_2
29: ldc #9 // String com.wangxiandeng.Student
31: invokevirtual #10 // Method com/wangxiandeng/classloader/CustomClassloader.loadClass:(Ljava/lang/String;)Ljava/lang/Class;
34: astore_3
35: aload_3
36: invokevirtual #11 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
39: checkcast #12 // class com/wangxiandeng/Student
42: astore 4
44: return
CASE(_checkcast):
if (STACK_OBJECT(-1) != NULL) {
VERIFY_OOP(STACK_OBJECT(-1));
// 拿到 checkcast 指令后的操作数,本例子中即 Student.Class 在常量池中的索引:#12
u2 index = Bytes::get_Java_u2(pc+1);
// 如果常量池还没有解析,先进行解析,即将常量池中的符号引用替换成直接引用,
//此时就会触发Student.Class 的加载
if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
}
// 获取上一步系统加载的Student.Class 对应的 InstanceKlass
Klass* klassOf = (Klass*) METHOD->constants()->resolved_klass_at(index);
// 获取要强转的对象的实际类型,即我们自己手动加载的Student.Class 对应的 InstanceKlass
Klass* objKlass = STACK_OBJECT(-1)->klass(); // ebx
// 现在就比较简单了,直接看看上面的两个InstanceKlass指针内容是否相同
// 不同的情况下则判断是否存在继承关系
if (objKlass != klassOf && !objKlass->is_subtype_of(klassOf)) {
// Decrement counter at checkcast.
BI_PROFILE_SUBTYPECHECK_FAILED(objKlass);
ResourceMark rm(THREAD);
char* message = SharedRuntime::generate_class_cast_message(
objKlass, klassOf);
VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(), message, note_classCheck_trap);
}
// Profile checkcast with null_seen and receiver.
BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/false, objKlass);
} else {
// Profile checkcast with null_seen and receiver.
BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/true, NULL);
}
public class Test {
public static void main(String[] args) throws Exception {
URL url[] = new URL[1];
url[0] = Thread.currentThread().getContextClassLoader().getResource("");
final CustomClassloader customClassloader = new CustomClassloader(url);
Thread.currentThread().setContextClassLoader(customClassloader);
Class clazz = customClassloader.loadClass("com.wangxiandeng.ClassTest");
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test");
method.invoke(object);
}
}
public class ClassTest {
public void test() throws Exception{
Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.wangxiandeng.Student");
Student student = (Student) clazz.newInstance();
System.out.print(student.getClass().getClassLoader());
}
}
总结
作者:汪先登
来源:https://zhuanlan.zhihu.com/p/60328095
2、
3、
4、
5、
点击「阅读原文」和栈长学更多~
你知道 Java 类是如何被加载的吗?的更多相关文章
- Java 类中各成分加载顺序 和 内存中的存放位置
参加一个笔试,有一个关于类的静态代码块.构造代码块.构造函数的执行顺序的问题.不太清楚,网上百度了一下.在这里记录一下. 一.什么时候会加载类?使用到类中的内容时加载:有三种情况1.创建对象:new ...
- java类到底是如何加载并初始化的?
Java虚拟机如何把编译好的.class文件加载到虚拟机里面?加载之后如何初始化类?静态类变量和实例类变量的初始化过程是否相同,分别是如何初始化的呢?这篇文章就 是解决上面3个问题的. 若有不正之处, ...
- java类的编译、加载和执行
一.java类的编译流程 这里主要讲的是从java文件到class文件 下图是java类编译的详细步骤: 1.词法分析:将java源代码的字符流转变为标记(Token)的集合,Token是编译过程中的 ...
- (转)java类到底是如何加载并初始化的?
Java虚拟机如何把编译好的.class文件加载到虚拟机里面?加载之后如何初始化类?静态类变量和实例类变量的初始化过程是否相同,分别是如何初始化的呢?这篇文章就 是解决上面3个问题的. 若有不正之处, ...
- java类在何时被加载
我们接着上一章的代码继续来了解一下java类是在什么时候加载的.在开始验证之前,我们现在IDEA做如下配置. -XX:+TraceClassLoading 监控类的加载 我们新建了一个TestCont ...
- Java类的5个加载步骤
类加载的五个过程分为: 加载 验证 准备 解析 初始化 1 加载 完成三件事: 通过类的全限定名来获取定义此类的二进制字节流 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 在内存中生成 ...
- java类中属性的加载顺序,以及内存分配情况介绍
看下面例子及说明: /** 假如有外部类调用了该类,代码为:new StaticTest(); 那么下面是类属性的加载顺序 */ public class StaticTest{ public int ...
- Java类实例化时候的加载顺序
面试试题中经常考到此问题,现在做进一步的总结: public class Student { public Student(String name){ System.out.println(name) ...
- java 类属性、方法加载的顺序
1.静态变量 2.静态代码块 3.局部代码块 4.构造函数 5.普通代码块 6.静态方法 7.普通方法 8.普通属性
随机推荐
- 关于AES加密CryptoJS
import * as CryptoJS from 'crypto-js'; let AuthTokenKey = "XXX"; //AES密钥let AuthTokenIv = ...
- angular-seed — AngularJS种子项目
AngularJS Seed 是典型 AngularJS web 应用的应用骨架,可以快速启动你的 AngularJS webapp 项目和这些项目的开发环境. AngularJS Seed 包括一个 ...
- guava中Multimap、Multiset使用
guava中的Multimap接口 Multimap和java.util.Map接口没有任何继承关系.同Map一样,也是放键值对,但是Multimap的值是一个集合.同样支持泛型,假如键值对的key的 ...
- 把数据存储到 XML 文件
通常,我们在数据库中存储数据.不过,如果希望数据的可移植性更强,我们可以把数据存储 XML 文件中. 创建并保存 XML 文件 如果数据要被传送到非 Windows 平台上的应用程序,那么把数据保存在 ...
- IdentityServer4
序言 IdentityServer4能做什么 Identity Server 4(以下简称ID4)是一个基于oauth2和OpenID的身份认证组件,基于这个组件可以快速开发自己的身份认证网站,支持单 ...
- 【BZOJ3811/UOJ36】 玛里苟斯
Description 魔法之龙玛里苟斯最近在为加基森拍卖师的削弱而感到伤心,于是他想了一道数学题. S 是一个可重集合,S={a1,a2,…,an}. 等概率随机取 S 的一个子集 A={ai1,… ...
- window.location.href 兼容性问题 (ie 浏览器下设置失效)
window.location.href 兼容性问题 (ie 下设置失效) window.location.href = "../index.html" (ie 浏览器失效) wi ...
- c++结构体、共用体和枚举
结构体类型 c++中的结构体成员既可以是数据,也可以是函数 c语言中定义结构体变量必须加struct(这也是很多时候和typedef),但是在c++里面,可以不加 结构体和类的不同在于,结构体中的变量 ...
- 追加环境变量到Path
@echo off setlocal enabledelayedexpansion ::使用方法: :: "C:\WINDOWS" :: "C:\jar" SE ...
- 项目三、文件上传器v1.1
/** * 自定义文件上传工具 v1.1 * @param url 路径 */ function fileUploader(url) { var _date = new Date(); //日期 th ...