OpenJDK源码解析之System.out.println详解

 更新时间:2021年04月25日 16:02:58   作者:黄智霖-blog  
这篇文章主要介绍了OpenJDK源码解析之System.out.println详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下

一、前戏

可能不少小伙伴习惯在代码中使用sout打印一些信息,就像这样:

System.out.println("hello world!")

做为一位资深干码人,本着弘扬党求真务实的精神,必须得来看看这个sout有何玄机~~

首先看调用就知道,out是System类的一个公共静态成员变量,进入System.java中:

public final static PrintStream out = null;

嗯,不止是public,还是final的。不管,来找找out是在哪里赋值的。。。。。。日嘛找半天没找到?那就试试直接在类中搜索: out = ,结果如下:

在这里插入图片描述

完犊子,整个System类一共将近1300行的代码,只找到一个和out赋值相关的,还是啥子局部变量fdOut,看起来和out属性没有什么关联啊~ 往上滑滑看看这个是什么方法:

private static void initializeSystemClass() {
	......
	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方法,似乎一切都明了了~~

二、JVM源码分析

initializeSystemClass不是给我们调用的,这个方法会在vm线程初始化后被虚拟机调用。其定义在thread.cpp中:

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);
}

首先获取Klass(类元信息在虚拟机中的结构表示,就是一个c++中的类),在vmSymbols.hpp中找找java_lang_System对应的值:

 template(java_lang_System,                          "java/lang/System") 

可以看到代表的就是java.lang.System类,同时,initializeSystemClass_name也定义在vmSymbols.hpp中:

template(initializeSystemClass_name,                "initializeSystemClass")   

这里使用JavaCalls::call_static就是调用java.lang.System的静态方法initializeSystemClass。还要再啰嗦一句,call_initializeSystemClass是在何处调用的?我们回到thread.cpp中,进入Threads::create_vm方法,在其中找到了call_initializeSystemClass方法的调用:

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
	......
	 call_initializeSystemClass(CHECK_0);
	......
}

然后看看Threads::create_vm是在何处调用的,我们进入jni.cpp,找到JNI_CreateJavaVM方法:

_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
	......
	result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
	if (result == JNI_OK) {
		......
	} else {
		......	
	}
	......
}

现在已经知道了,JVM启动后会在初始化JVM的时候调用CreateJavaVM,进而调用initializeSystemClass方法。但是out属性是如何设置的呢?我们回到java.lang.System#initializeSystemClass方法:

private static void initializeSystemClass() {
	......
	FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
	setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
	......
}

private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
       if (enc != null) {
            try {
                return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
            } catch (UnsupportedEncodingException uee) {}
        }
        return new PrintStream(new BufferedOutputStream(fos, 128), true);
    }

这个方法中唯一和out相关的就是这个setOut0方法调用了,我们来看看这个方法:

private static native void setOut0(PrintStream out);

嗯,这个是一个native方法,没办法,又只有回到JVM源码了。怎么找呢?首选组装一下本地方法名,根据规则setOut0对应的本地方法应该叫:Java_java_lang_System_setOut0,我们到源码中找找,然后在System.c中找到了该方法

JNIEXPORT void JNICALL
Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream)
{
    jfieldID fid =
        (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;");
    if (fid == 0)
        return;
    (*env)->SetStaticObjectField(env,cla,fid,stream);
}

首先获取了java.io.PrintStread类型的out静态成员,嗯,这个就是我们java.lang.System类的静态成员out:

public final static PrintStream out;

然后将stream参数赋值给它,这个stream就是initializeSystemClass方法中通过newPrintStream方法创建的PrintStream 对象。到现在我们已经明白了,out原来是这样赋值的,真麻烦~

趁热打铁,弄明白了out,接下来看看println。既然out是PrintStream对象,那么到PrintStream中看看println方法:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

Java代码看起来是要亲切多了,但是这个synchronized是个什么鬼???

三、坑?

前面我们发现println方法竟然有个synchronized关键字,经常在项目中使用sout的小伙伴会不会感觉脑袋嗡嗡的?为什么要嗡嗡?不要忘记我们前面通过JVM源码跟踪的System.out的赋值过程,这个out可是单例!你个加了synchronized(this)的虚方法,还是单例的,如果在系统中进行并发使用,后果不用我多说吧?

那可能有人就要说了,那我不用println(“hello world!”)了嘛,我换成print总行了吧?

System.out.print("hello world!");
System.out.print("\n");

因为print方法好像没有加锁啊:

public void print(String s) {
        if (s == null) {
            s = "null";
        }
        write(s);
    }

那我们看看write方法,不好意思:

private void write(String s) {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.write(s);
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush && (s.indexOf('\n') >= 0))
                    out.flush();
            }
        }
		......
    }

四、总结

不知道有没有小伙伴经常在项目中使用System.out.println来输出"日志"?千万不要乱用哟,不然说不定哪天就被out了~~~

到此这篇关于OpenJDK源码解析之System.out.println详解的文章就介绍到这了,更多相关OpenJDK源码解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot启动时运行特定代码的多种方式小结

    SpringBoot启动时运行特定代码的多种方式小结

    SpringBoot提供了多种方式在应用程序启动时运行特定的代码,包括CommandLineRunner、ApplicationRunner、@PostConstruct、InitializingBean、事件机制和自定义注解等,下面就来具体介绍一下
    2025-01-01
  • Java代理模式实例分析

    Java代理模式实例分析

    这篇文章主要介绍了Java代理模式,结合实例形式对比分析了java代理模式的使用方法与相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • Spring Cloud  Eureka服务治理的实现

    Spring Cloud Eureka服务治理的实现

    服务治理是微服务框架中最为核心和基础的模块,它主要是用来实现各个微服务实例的自动化注册与发现。这篇文章主要介绍了Spring Cloud Eureka服务治理的实现,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • Java 客户端向服务端上传mp3文件数据的实例代码

    Java 客户端向服务端上传mp3文件数据的实例代码

    这篇文章主要介绍了Java 客户端向服务端上传mp3文件数据的实例代码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-09-09
  • Java的LinkedHashSet源码深入讲解

    Java的LinkedHashSet源码深入讲解

    这篇文章主要介绍了Java的LinkedHashSet源码深入讲解,LinkedHashSet是HashSet的子类,而由于HashSet实现了Set接口,因此LinkedHashSet也间接实现了Set类,LinkedHashSet类属于java.base模块,java.util包下,需要的朋友可以参考下
    2023-09-09
  • Java 代理(Proxy)的原理及应用

    Java 代理(Proxy)的原理及应用

    动态代理技术就是用来产生一个对象的代理对象的。 我们在开发中之所以要产生一个对象的代理对象,主要用于拦截对真实业务对象的访问。本文主要介绍了Java 代理的使用,感兴趣的可以了解一下
    2021-05-05
  • maven无法依赖spring-cloud-stater-zipkin的解决方案

    maven无法依赖spring-cloud-stater-zipkin的解决方案

    这篇文章主要介绍了maven无法依赖spring-cloud-stater-zipkin如何解决,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • Java 正则表达式详细介绍

    Java 正则表达式详细介绍

    本文主要介绍 Java 正则表达式的内容,这里整理了Java 正则表达式的相关资料,并详细介绍,附有代码示例,有兴趣的小伙伴可以参考下
    2016-09-09
  • rabbitmq的消息持久化处理开启,再关闭后,消费者启动报错问题

    rabbitmq的消息持久化处理开启,再关闭后,消费者启动报错问题

    这篇文章主要介绍了rabbitmq的消息持久化处理开启,再关闭后,消费者启动报错问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • SpringBoot整合Swagger Api自动生成文档的实现

    SpringBoot整合Swagger Api自动生成文档的实现

    本文主要介绍了SpringBoot整合Swagger Api自动生成文档的实,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06

最新评论