初识System类
System类是从JDK1.0就被定义了的一个类,本篇所有解读都是基于OpenJdk8而来。System类由final修饰符修饰,所以它不能被继承扩展,而System类只有一个私有的无参构造函数,所以它不能被实例化,我们只能使用这个类里的静态方法。
public final class System {
//此处省略代码XX行
/* Don’t let anyone instantiate this class /
private System() {
}
//此处省略代码XX行
}
System类为我们提供了一些方便的工具,通过它我们可以很方便的做以下几个事儿:
- 使用System类使用标准输入流、标准输出流和标准错误输出流
- 使用System类访问外部定义的系统属性和环境变量
- 通过System类动态加载动态链接库
- 使用System类快速的复制部分数组元素
System类初始化
System类初始化可以分为两部分:本地方法初始化、VM调用初始化
什么是本地方法?
一个本地方法就是由Java调用的非Java语言实现的接口,并保存在动态链接库中
为什么要用本地方法?
首先,有些层次的任务用Java实现起来比较不易,或者当我们对程序执行的效率比较在意的时候,使用本地方法可能是更好的选择,如:与非Java环境进行交互、与底层系统或硬件交换信息
其次,JVM是一个解释器,它的一部分实现就是使用的C语言,它的运行也需要操作系统的支持,与操作系统的交互就是通过本地方法
System类本地方法初始化
System类中定义了许多本地方法,Java代码要调用一个本地方法需要2个步骤:
1、 通过System.loadLibrary()方法将包含本地方法的动态链接库加载进入内存中,如在Win32环境下执行System.loadLibrary("foo")就会把foo.dll加载进内存中
2、 当Java要调用本地方法时,JVM会在已加载进内存的动态链接库中定位并链接要调用的本地方法,然后才可以执行本地方法。比如要执行Foo.g()方法,JVM会在foo.dll动态链接库中定位并链接Java_Foo_g这个本地方法
显然步骤2有些低效,Java要调用本地方法是如果能省去定位链接岂不是更好吗?registerNatives()方法就是为了代替步骤2应运而生,在许多类里都能发现它的踪影,作用都是一样的,比如:Object类、Thread类等等。
/* register the natives via the static initializer.
*
* VM will invoke the initializeSystemClass method to complete
* the initialization for this class separated from clinit.
* Note that to use properties set by the VM, see the constraints
* described in the initializeSystemClass method.
*/
private static native void registerNatives();
static {
registerNatives();
}
registerNatives()方法在静态代码块中被调用,编译器会将静态字段和静态代码块重新整合进<clinit>()方法中,当类被加载时<clinit>()方法被调用,静态代码块得以执行。
registerNatives()方法让程序可以主动的去链接本地方法,这样JVM就不用去动态链接库中查找定位本地方法了,Java可以直接调用本地方法执行。
我们可以到Jdk源码里翻一下它的实现(不知道怎么找的看官别急,且听下回分解)
/* Only register the performance-critical methods */
static JNINativeMethod methods[] = {
//这里主动注册链接了三个方法,分别对应着System类里的System.currentTimeMillis()、System.nanoTime()和System.arrayCopy()三个方法
{"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis},
{"nanoTime", "()J", (void *)&JVM_NanoTime},
{"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};
#undef OBJ
JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
//主动注册链接操作就是在这里干的
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
System类VM初始化
System类中初始化主要通过initializeSystemClass()方法,关于initializeSystemClass方法的调用,javadoc原文有两段是这么说的
VM will invoke the initializeSystemClass method to complete
the initialization for this class separated from clinit.
Initialize the system class, Called after thread initialization.
也就是说initializeSystemClass方法是由VM来调用的,而且是在线程被初始化以后就调用了,我大概翻了一下hotspot JVM的实现,找到了这个方法被调用的地方
//这个方法负责调用System类的initializeSystemClass方法
static void call_initializeSystemClass(TRAPS) {
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_System(), true, CHECK);
instanceKlassHandle klass (THREAD, k);
JavaValue result(T_VOID);
JavaCalls::call_static(&result, klass, vmSymbols::initializeSystemClass_name(),
vmSymbols::void_method_signature(), CHECK);
}
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
//此处省略代码XX行
{
TraceTime timer("Initialize java.lang classes", TraceStartupTime);
if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}
initialize_class(vmSymbols::java_lang_String(), CHECK_0); //这里加载了java.lang.String类
initialize_class(vmSymbols::java_lang_System(), CHECK_0); //这里加载了java.lang.System类,然而还没有调用System类的initializeSystemClass方法
initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0); //这里加载了java.lang.ThreadGroup类
Handle thread_group = create_initial_thread_group(CHECK_0);
Universe::set_main_thread_group(thread_group());
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0); //这里加载了java.lang.Thread类
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
java_lang_Thread::set_thread_status(thread_object,
java_lang_Thread::RUNNABLE);
initialize_class(vmSymbols::java_lang_Class(), CHECK_0); //这里加载了java.lang.Class类
initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0); //这里加载了java.lang.reflect.Method类
initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0); //这里加载了java.lang.ref.Finalizer类
call_initializeSystemClass(CHECK_0); //这是调用了System类的initializeSystemClass方法
JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));
initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
}
//此处省略代码XX行
}
源码中调用System类的initializeSystemClass方法是在create_vm方法中,也就是JVM创建时,在加载了String、System、ThreadGroup、Thread、Class、Method、Finalizer这几个类之后,才调用了System类的initializeSystemClass方法,之后又加载了一些异常类。
initializeSystemClass方法完成了System类后续的初始化工作,主要有以下几个部分:
1)初始化外部定义的系统属性和环境变量
props = new Properties();
initProperties(props); // initialized by the VM
sun.misc.VM.saveAndRemoveProperties(props);
lineSeparator = props.getProperty("line.separator");
sun.misc.Version.init();
这些系统属性和环境变量一部分是通过本地方法设置的,另一部分是通过System.setProperty()方法设置的,initProperties()方法就是一个本地方法,它的实现在Jdk源码中的/src/share/native/java/lang/System.c文件中的Java_java_lang_System_initProperties方法。
初始化完成后我们将至少能通过System.getProperty()方法访问到以下属性:(这里以OpenJdk11的jshell环境举例)
| 欢迎使用 JShell -- 版本 11.0.4
| 要大致了解该版本, 请键入: /help intro
jshell> System.getProperty("java.version"); //Java版本号
$1 ==> "11.0.4"
jshell> System.getProperty("java.runtime.version"); //Java运行时版本号
$2 ==> "11.0.4+11-post-Ubuntu-1ubuntu218.04.3"
jshell> System.getProperty("java.runtime.name"); //Java运行时名称
$3 ==> "OpenJDK Runtime Environment"
jshell> System.getProperty("java.vendor"); //Java供应商
$4 ==> "Ubuntu"
jshell> System.getProperty("java.vendor.url"); //Java供应商主页
$5 ==> "https://ubuntu.com/"
jshell> System.getProperty("java.home"); //JDK安装目录
$6 ==> "/usr/lib/jvm/java-11-openjdk-amd64"
jshell> System.getProperty("java.class.version"); //Java Class版本号,55.0应该是对应着Jdk11
$7 ==> "55.0"
jshell> System.getProperty("java.class.path"); //环境变量类路径, ClassLoader加载类时使用
$8 ==> "."
jshell> System.getProperty("os.name"); //操作系统名称
$9 ==> "Linux"
jshell> System.getProperty("os.arch"); //操作系统架构
$10 ==> "amd64"
jshell> System.getProperty("file.separator"); //文件分隔符
$12 ==> "/"
jshell> System.getProperty("path.separator"); //路径分隔符
$13 ==> ":"
jshell> System.getProperty("line.separator"); //行分隔符
$14 ==> "\n"
jshell> System.getProperty("user.name"); //当前用户名
$15 ==> "mayun"
jshell> System.getProperty("user.home"); //当前用户home目录
$16 ==> "/home/mayun"
jshell> System.getProperty("user.dir"); //当前用户工作目录,默认文件保存路径
$17 ==> "/home/mayun"
2)初始化标准输入、输出流
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
由于initializeSystemClass()方法是在加载完System类以后就由JVM调用了,所以当我们使用System类时,System类的in、out、err三个字段已经被初始化完成了。setIn0、setOut0和setErr0三个方法也是本地方法,实现在Jdk源码的/src/share/native/java/lang/System.c文件中,对应着Java_java_lang_System_setIn0、Java_java_lang_System_setOut0和Java_java_lang_System_setErr0三个方法。
欢迎关注我的公众号:米爸笔记,获得独家整理的学习资源和日常干货推送
网友评论