一、前戏

可能不少小伙伴习惯在代码中使用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源码解析内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!