从HotSpot虚拟机源码了解Java的访问控制修饰符
前面Ribbon源码分析文章,有读者留言提问:XX类是包私有的,重写不会报错吗?答案其实是XX类并非包私有,而是一个protected的静态内部类,所以重写不会报错。
关于Java访问控制修饰符的作用,笔者在初学Java时也是靠记,写多了代码自然也就能理解,但笔者很好奇底层的实现,所以也尝试从HotSpot虚拟机源码寻找答案,解答我多年来的疑惑。
类、字段、方法都有哪些访问控制修饰符?
私有<private>
、子类可访问<protected>
、公开public
、包私有<package>
,默认不加访问控制修饰符就是包私有。
访问范围 |
private |
package |
protected |
public |
---|---|---|---|---|
同一个类 |
可访问 |
可访问 |
可访问 |
可访问 |
同一包中的其他类 |
不可访问 |
可访问 |
可访问 |
可访问 |
不同包中的子类 |
不可访问 |
不可访问 |
可访问 |
可访问 |
不同包中的非子类 |
不可访问 |
不可访问 |
不可访问 |
可访问 |
包私有<package>
指的是只有同一个包下的类可访问,其它包下的类不可访问。
今天我们就深入java虚拟机去探究这些访问控制修饰符语意的实现。
InstanceKlass是HotSpot VM中对应class文件结构的数据结构,InstanceKlass对象是一个Java类被HotSpot VM加载后所生成的C++对象,被存于方法区。我们在Java代码中使用的Class对象实际是InstanceKlass的一个镜像。
Java支持使用"this."
、"suppor."
、"某个对象."
调用一个方法,或"某个类."
调用静态方法,在我们看来是调用某个类的静态方法或者对象的方法,但这在虚拟机中并不存在区别,都是一个方法调用。
调用静态方法和对象方法的区别只在于,调用对象的方法需要在方法参数传递一个"this"
引用,这是一个隐式参数,在编译器将Java代码编译成字节码时自动添加上。
而Java代码中使用"this."
、"suppor."
调用自身方法和父类方法的不同,仅仅只是生成方法调用字节码指令的操作数指向的Methodref
常量不同,方法的第一个隐式参数传递的对象都是同一个。Methodref常量指代一个方法的符号引用,包括类名、方法名、方法描述符。
我们知道,类加载过程包括加载、链接、初始化三个阶段,其中链接阶段又可细分为验证、准备和解析三个阶段。下面这张图有助于我们理解类加载的几个阶段,但并不准确。
《Java虚拟机规范》只是规定类加载需要完成的事情,而对顺序并没有严格的要求。
下图为笔者阅读HotSpot虚拟机类加载源码总结出的一张流程图,仅供参考。(如需要获取原图,可在公众号回复:"hotspot")
在HotSpot虚拟机中,链接阶段的准备阶段在加载阶段之后完成,链接阶段的验证也分多种验证,其中文件格式验证、元数据验证在加载阶段交叉完成,而字节码验证阶段则在类初始化之前才触发,解析阶段则在类加载完成之后。
引起类初始化的几条指令如new、getstatic、putstatic、invokestatic,虚拟机在执行这些指令时,先判断类是否已经初始化,未初始化则完成类的初始化,链接阶段会在类初始化阶之前触发。
链接阶段的解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,根据《Java虚拟机规范》规定,在ane-warray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、invokestatic、invokevirtual、ldc、mulianewarray、new、putfield、putstatic这些要求操作数指向常量池中的符号引用常量(如:CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Methodref_info)的指令执行之前,必须先对使用的符号引用进行解析。
符号引用以一组符号描述引用的目标,如CONSTANT_Class_info表示引用的类、CONSTANT_Field_info表示引用哪个类的哪个字段、CONSTANT_Methodref_info表示引用哪个类的哪个方法。
符号引用验证发生在解析阶段,符号引用验证包括:通过字符串描述的全限定名是否能找到对应的类、在指定的类中是否存在简单名称所描述的方法和字段、符号引用中的类、字段、方法的可访问性(<private>
、<protected>
、public
、<package>
)。
在HotSpot虚拟机的实现中,对于解释执行与动态调用(invokedynamic),解析阶段是在符号引用将要被使用前才去解析。
方法调用源码:javaCalls.cpp
; 链接解析源码:linkResolver.cpp
;
检查类、方法、字段可访问性对应源码:
// 检查类
LinkResolver::check_klass_accessability
// 检查方法
LinkResolver::check_method_accessability
// 检查字段
LinkResolver::check_field_accessability
这些方法调用最后都调用Reflection类的对应verify方法完成是否可访问的判断,例如Reflection::verify_field_access
方法。
Java虚拟机在解析class文件结构时、在字节码验证阶段,也会对访问控制修饰符进行验证。
例如,在解析class文件结构时,验证是否能够继承父类(Reflection::verify_class_access
):
类的访问修饰符决定了一个类是否可以被其它类访问。在解析class文件结构阶段,虚拟机可以验证当前类是否能够继承父类(父类的访问控制修饰符决定)、是否能够实现每个接口(接口的访问修饰符决定)。
在字节码验证阶段则验证当前类是否可以访问目标类的protected
修饰的方法或字段:
在字节码验证阶段,虚拟机会对类的每个方法中的每条字节码指令都会进行验证,但虚拟机在字节码验证阶段,只对getfield指令做了check_protected
验证。可见,字节码验证阶段没有做过多的访问控制验证。
笔者水平有限,如本文有表达错误的地方,还望读者指正。
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- thinkPHP3.2使用RBAC实现权限管理的实现
- Flutter以两种方式实现App主题切换的代码
- PHP中非常有用却鲜有人知的函数集锦
- PHP针对redis常用操作实例详解
- thinkPHP5使用Rabc实现权限管理
- PHP实现cookie跨域session共享的方法分析
- VS Code开发React-Native及Flutter 开启无线局域网安卓真机调试问题
- Laravel5.4简单实现app接口Api Token认证方法
- PHP生成zip压缩包的常用方法示例
- Android Studio用genymotion运行后小图标无法显示问题
- PHP7数组的底层实现示例
- 浅析Flutter AbsorbPointer 与 IgnorePointer的区别
- php用wangeditor3实现图片上传功能
- Flutter集成到已有iOS工程的方法步骤
- php的命名空间与自动加载实现方法