<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JVM系列(二):jvm是如何加載java代碼的?

          共 21434字,需瀏覽 43分鐘

           ·

          2021-02-22 08:39

          走過路過不要錯過

          點(diǎn)擊藍(lán)字關(guān)注我們


          上一篇粗略講了下jvm的啟動過程,但很多路子還沒跑通。其中非常核心的,加載vm的過程。這個可以在hotspot中找到端倪。但jvm啟動,又是如何載入java代碼呢。


          1. JavaMain加載流程

          我們知道,java中入口是在main方法中,可以在命令行中指定main類,或者jar包中指定的manifest.xml中指定的main類。在java.c中,我們可以看到一個JavaMain方法,不知從何而來,但很像是直接加載java入口的方法。

          // share/bin/java.c// 加載 main 函數(shù)類// 通過引入 JavaMain(), 接入java方法// #define JNICALL __stdcallint JNICALLJavaMain(void * _args){    JavaMainArgs *args = (JavaMainArgs *)_args;    int argc = args->argc;    char **argv = args->argv;    int mode = args->mode;    char *what = args->what;    // 一些jvm的調(diào)用實(shí)例,在之前的步驟中,通過加載相應(yīng)動態(tài)鏈接方法,保存起來的    /**      * ifn->CreateJavaVM =     *   (void *)GetProcAddress(handle, "JNI_CreateJavaVM");     * ifn->GetDefaultJavaVMInitArgs =     *   (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs");     */    InvocationFunctions ifn = args->ifn;    JavaVM *vm = 0;    JNIEnv *env = 0;    jclass mainClass = NULL;    jclass appClass = NULL; // actual application class being launched    jmethodID mainID;    jobjectArray mainArgs;    int ret = 0;    jlong start, end;    // collector    RegisterThread();    /* Initialize the virtual machine */    start = CounterGet();    // 初始化jvm,失敗則退出    if (!InitializeJVM(&vm, &env, &ifn)) {        JLI_ReportErrorMessage(JVM_ERROR1);        exit(1);    }    // jvm檢查完畢,如果只是一些展示類請求,則展示信息后,退出jvm    if (showSettings != NULL) {        ShowSettings(env, showSettings);        /**         * 宏是神奇的操作,此處 *env 直接引用#define CHECK_EXCEPTION_LEAVE(CEL_return_value) \    do { \        if ((*env)->ExceptionOccurred(env)) { \            JLI_ReportExceptionDescription(env); \            ret = (CEL_return_value); \            LEAVE(); \        } \    } while (JNI_FALSE)         */        CHECK_EXCEPTION_LEAVE(1);    }    // 調(diào)用 LEAVE() 方法的目的在于主動銷毀jvm線程    // 且退出當(dāng)前方法調(diào)用,即 LEAVE() 后方法不再被執(zhí)行/* * Always detach the main thread so that it appears to have ended when * the application's main method exits.  This will invoke the * uncaught exception handler machinery if main threw an * exception.  An uncaught exception handler cannot change the * launcher's return code except by calling System.exit. * * Wait for all non-daemon threads to end, then destroy the VM. * This will actually create a trivial new Java waiter thread * named "DestroyJavaVM", but this will be seen as a different * thread from the one that executed main, even though they are * the same C thread.  This allows mainThread.join() and * mainThread.isAlive() to work as expected. */    /**     *     * #define LEAVE() \    do { \        if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \            JLI_ReportErrorMessage(JVM_ERROR2); \            ret = 1; \        } \        if (JNI_TRUE) { \            (*vm)->DestroyJavaVM(vm); \            return ret; \        } \    } while (JNI_FALSE)     */    if (printVersion || showVersion) {        PrintJavaVersion(env, showVersion);        CHECK_EXCEPTION_LEAVE(0);        if (printVersion) {            LEAVE();        }    }    /* If the user specified neither a class name nor a JAR file */    if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {        PrintUsage(env, printXUsage);        CHECK_EXCEPTION_LEAVE(1);        LEAVE();    }    // 釋放內(nèi)存    FreeKnownVMs();  /* after last possible PrintUsage() */    if (JLI_IsTraceLauncher()) {        end = CounterGet();        JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",               (long)(jint)Counter2Micros(end-start));    }    /* At this stage, argc/argv have the application's arguments */    if (JLI_IsTraceLauncher()){        int i;        printf("%s is '%s'\n", launchModeNames[mode], what);        printf("App's argc is %d\n", argc);        for (i=0; i < argc; i++) {            printf("    argv[%2d] = '%s'\n", i, argv[i]);        }    }    ret = 1;    /*     * Get the application's main class.     *     * See bugid 5030265.  The Main-Class name has already been parsed     * from the manifest, but not parsed properly for UTF-8 support.     * Hence the code here ignores the value previously extracted and     * uses the pre-existing code to reextract the value.  This is     * possibly an end of release cycle expedient.  However, it has     * also been discovered that passing some character sets through     * the environment has "strange" behavior on some variants of     * Windows.  Hence, maybe the manifest parsing code local to the     * launcher should never be enhanced.     *     * Hence, future work should either:     *     1)   Correct the local parsing code and verify that the     *          Main-Class attribute gets properly passed through     *          all environments,     *     2)   Remove the vestages of maintaining main_class through     *          the environment (and remove these comments).     *     * This method also correctly handles launching existing JavaFX     * applications that may or may not have a Main-Class manifest entry.     */    // 加載 main 指定的class類    mainClass = LoadMainClass(env, mode, what);    CHECK_EXCEPTION_NULL_LEAVE(mainClass);    /*     * In some cases when launching an application that needs a helper, e.g., a     * JavaFX application with no main method, the mainClass will not be the     * applications own main class but rather a helper class. To keep things     * consistent in the UI we need to track and report the application main class.     */    appClass = GetApplicationClass(env);    NULL_CHECK_RETURN_VALUE(appClass, -1);    /*     * PostJVMInit uses the class name as the application name for GUI purposes,     * for example, on OSX this sets the application name in the menu bar for     * both SWT and JavaFX. So we'll pass the actual application class here     * instead of mainClass as that may be a launcher or helper class instead     * of the application class.     */    // 加載main() 方法前執(zhí)行初始化    PostJVMInit(env, appClass, vm);    CHECK_EXCEPTION_LEAVE(1);    /*     * The LoadMainClass not only loads the main class, it will also ensure     * that the main method's signature is correct, therefore further checking     * is not required. The main method is invoked here so that extraneous java     * stacks are not in the application stack trace.     */    // 獲取main()方法id, main(String[] args)    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",                                       "([Ljava/lang/String;)V");    CHECK_EXCEPTION_NULL_LEAVE(mainID);    /* Build platform specific argument array */    // 構(gòu)建args[] 參數(shù)    mainArgs = CreateApplicationArgs(env, argv, argc);    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);    /* Invoke main method. */    // 調(diào)用java實(shí)現(xiàn)的main()方法    // XX:: 重要實(shí)現(xiàn)    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);    /*     * The launcher's exit code (in the absence of calls to     * System.exit) will be non-zero if main threw an exception.     */    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;    LEAVE();}/* * Loads a class and verifies that the main class is present and it is ok to * call it for more details refer to the java implementation. */static jclassLoadMainClass(JNIEnv *env, int mode, char *name){    jmethodID mid;    jstring str;    jobject result;    jlong start, end;    jclass cls = GetLauncherHelperClass(env);    NULL_CHECK0(cls);    if (JLI_IsTraceLauncher()) {        start = CounterGet();    }    // checkAndLoadMain(String) 方法作為中間main()調(diào)用    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,                "checkAndLoadMain",                "(ZILjava/lang/String;)Ljava/lang/Class;"));    str = NewPlatformString(env, name);    CHECK_JNI_RETURN_0(        result = (*env)->CallStaticObjectMethod(            env, cls, mid, USE_STDERR, mode, str));    if (JLI_IsTraceLauncher()) {        end   = CounterGet();        printf("%ld micro seconds to load main class\n",               (long)(jint)Counter2Micros(end-start));        printf("----%s----\n", JLDEBUG_ENV_ENTRY);    }    return (jclass)result;}    
          // 初始化jvm, 主要是調(diào)用 CreateJavaVM() 方法,進(jìn)行創(chuàng)建jvm操作/* * Initializes the Java Virtual Machine. Also frees options array when * finished. */static jbooleanInitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn){ JavaVMInitArgs args; jint r; memset(&args, 0, sizeof(args)); args.version = JNI_VERSION_1_2; args.nOptions = numOptions; args.options = options; args.ignoreUnrecognized = JNI_FALSE; if (JLI_IsTraceLauncher()) { int i = 0; printf("JavaVM args:\n "); printf("version 0x%08lx, ", (long)args.version); printf("ignoreUnrecognized is %s, ", args.ignoreUnrecognized ? "JNI_TRUE" : "JNI_FALSE"); printf("nOptions is %ld\n", (long)args.nOptions); for (i = 0; i < numOptions; i++) printf(" option[%2d] = '%s'\n", i, args.options[i].optionString); } r = ifn->CreateJavaVM(pvm, (void **)penv, &args); JLI_MemFree(options); return r == JNI_OK;}

          略去核心加載jvm的實(shí)現(xiàn),要加載main類,還是比較簡單的。主要就是通過前面查找出的main_class, 然后通過在jvm的實(shí)例中獲取到的各方法的函數(shù)指針,然后按照字節(jié)碼的規(guī)范,調(diào)用 MainClass.main(String[]) 方法。當(dāng)然了,為了兼容其他非main的場景,它還有很多附加處理邏輯。

          另外,如何翻譯java代碼,并執(zhí)行,這是jvm的重中之重。即編譯原理的拿手好戲,也是我們普通程序員越不過的坎!但至少,我們來到了門檻前面!

          2. jvm啟動框架

          前面講了加載main方法的過程,大致理解了c如何啟動調(diào)用java的main的。那么,這又是如何調(diào)用準(zhǔn)備的呢,在這之前都需要做哪些準(zhǔn)備呢?

          實(shí)際上,這是平臺相關(guān)的實(shí)現(xiàn)。

          // share/bin/java.c/* * Entry point. */intJLI_Launch(int argc, char ** argv,              /* main argc, argc */        int jargc, const char** jargv,          /* java args */        int appclassc, const char** appclassv,  /* app classpath */        const char* fullversion,                /* full version defined */        const char* dotversion,                 /* dot version defined */        const char* pname,                      /* program name */        const char* lname,                      /* launcher name */        jboolean javaargs,                      /* JAVA_ARGS */        jboolean cpwildcard,                    /* classpath wildcard*/        jboolean javaw,                         /* windows-only javaw */        jint ergo                               /* ergonomics class policy */){    int mode = LM_UNKNOWN;    char *what = NULL;    char *cpath = 0;    char *main_class = NULL;    int ret;    InvocationFunctions ifn;    jlong start, end;    char jvmpath[MAXPATHLEN];    char jrepath[MAXPATHLEN];    char jvmcfg[MAXPATHLEN];    _fVersion = fullversion;    _dVersion = dotversion;    _launcher_name = lname;    _program_name = pname;    _is_java_args = javaargs;    _wc_enabled = cpwildcard;    _ergo_policy = ergo;    // 初始化啟動器    InitLauncher(javaw);    // 打印狀態(tài)    DumpState();    // 跟蹤調(diào)用啟動    if (JLI_IsTraceLauncher()) {        int i;        printf("Command line args:\n");        for (i = 0; i < argc ; i++) {            printf("argv[%d] = %s\n", i, argv[i]);        }        AddOption("-Dsun.java.launcher.diag=true", NULL);    }    /*     * Make sure the specified version of the JRE is running.     *     * There are three things to note about the SelectVersion() routine:     *  1) If the version running isn't correct, this routine doesn't     *     return (either the correct version has been exec'd or an error     *     was issued).     *  2) Argc and Argv in this scope are *not* altered by this routine.     *     It is the responsibility of subsequent code to ignore the     *     arguments handled by this routine.     *  3) As a side-effect, the variable "main_class" is guaranteed to     *     be set (if it should ever be set).  This isn't exactly the     *     poster child for structured programming, but it is a small     *     price to pay for not processing a jar file operand twice.     *     (Note: This side effect has been disabled.  See comment on     *     bugid 5030265 below.)     */    // 解析命令行參數(shù),選擇一jre版本    SelectVersion(argc, argv, &main_class);    CreateExecutionEnvironment(&argc, &argv,                               jrepath, sizeof(jrepath),                               jvmpath, sizeof(jvmpath),                               jvmcfg,  sizeof(jvmcfg));    if (!IsJavaArgs()) {        // 設(shè)置一些特殊的環(huán)境變量        SetJvmEnvironment(argc,argv);    }    ifn.CreateJavaVM = 0;    ifn.GetDefaultJavaVMInitArgs = 0;    if (JLI_IsTraceLauncher()) {        start = CounterGet();    }    // 加載VM, 重中之重    if (!LoadJavaVM(jvmpath, &ifn)) {        return(6);    }    if (JLI_IsTraceLauncher()) {        end   = CounterGet();    }    JLI_TraceLauncher("%ld micro seconds to LoadJavaVM\n",             (long)(jint)Counter2Micros(end-start));    ++argv;    --argc;    // 解析更多參數(shù)信息    if (IsJavaArgs()) {        /* Preprocess wrapper arguments */        TranslateApplicationArgs(jargc, jargv, &argc, &argv);        if (!AddApplicationOptions(appclassc, appclassv)) {            return(1);        }    } else {        /* Set default CLASSPATH */        cpath = getenv("CLASSPATH");        if (cpath == NULL) {            cpath = ".";        }        SetClassPath(cpath);    }    /* Parse command line options; if the return value of     * ParseArguments is false, the program should exit.     */    // 解析參數(shù)    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))    {        return(ret);    }    /* Override class path if -jar flag was specified */    if (mode == LM_JAR) {        SetClassPath(what);     /* Override class path */    }    /* set the -Dsun.java.command pseudo property */    SetJavaCommandLineProp(what, argc, argv);    /* Set the -Dsun.java.launcher pseudo property */    SetJavaLauncherProp();    /* set the -Dsun.java.launcher.* platform properties */    SetJavaLauncherPlatformProps();    return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);}
          // macos/bin/java_md_macos.c// MacOSX we may continue in the same threadintJVMInit(InvocationFunctions* ifn, jlong threadStackSize, int argc, char **argv, int mode, char *what, int ret) { if (sameThread) { JLI_TraceLauncher("In same thread\n"); // need to block this thread against the main thread // so signals get caught correctly __block int rslt = 0; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; { NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{ JavaMainArgs args; args.argc = argc; args.argv = argv; args.mode = mode; args.what = what; args.ifn = *ifn; // 調(diào)用 JavaMain() rslt = JavaMain(&args); }]; /* * We cannot use dispatch_sync here, because it blocks the main dispatch queue. * Using the main NSRunLoop allows the dispatch queue to run properly once * SWT (or whatever toolkit this is needed for) kicks off it's own NSRunLoop * and starts running. */ [op performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:YES]; } [pool drain]; return rslt; } else { return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret); }}

          以上是mac調(diào)用 javaMain的方式。在windows, 以及l(fā)inux上則稍有不同。

          // windows/bin/java_md.cintJVMInit(InvocationFunctions* ifn, jlong threadStackSize,        int argc, char **argv,        int mode, char *what, int ret){    ShowSplashScreen();    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);}// java.cintContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,                    int argc, char **argv,                    int mode, char *what, int ret){
          /* * If user doesn't specify stack size, check if VM has a preference. * Note that HotSpot no longer supports JNI_VERSION_1_1 but it will * return its default stack size through the init args structure. */ if (threadStackSize == 0) { struct JDK1_1InitArgs args1_1; memset((void*)&args1_1, 0, sizeof(args1_1)); args1_1.version = JNI_VERSION_1_1; ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */ if (args1_1.javaStackSize > 0) { threadStackSize = args1_1.javaStackSize; } }
          { /* Create a new thread to create JVM and invoke main method */ JavaMainArgs args; int rslt;
          args.argc = argc; args.argv = argv; args.mode = mode; args.what = what; args.ifn = *ifn; // 傳入 JavaMain() 函數(shù)參數(shù),在新線程中運(yùn)行 JavaMain rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args); /* If the caller has deemed there is an error we * simply return that, otherwise we return the value of * the callee */ return (ret != 0) ? ret : rslt; }}/* * Block current thread and continue execution in a new thread */intContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) { int rslt = 0; unsigned thread_id;#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION#define STACK_SIZE_PARAM_IS_A_RESERVATION (0x10000)#endif /* * STACK_SIZE_PARAM_IS_A_RESERVATION is what we want, but it's not * supported on older version of Windows. Try first with the flag; and * if that fails try again without the flag. See MSDN document or HotSpot * source (os_win32.cpp) for details. * 調(diào)用底層內(nèi)核方法,創(chuàng)建新線程,將JavaMain(args)作為函數(shù)調(diào)用處理。創(chuàng)建后立即執(zhí)行 * 如果第一次帶參創(chuàng)建失敗,則棄參重試一次 */ HANDLE thread_handle = (HANDLE)_beginthreadex(NULL, (unsigned)stack_size, continuation, args, STACK_SIZE_PARAM_IS_A_RESERVATION, &thread_id); if (thread_handle == NULL) { thread_handle = (HANDLE)_beginthreadex(NULL, (unsigned)stack_size, continuation, args, 0, &thread_id); } /* AWT preloading (AFTER main thread start) */#ifdef ENABLE_AWT_PRELOAD /* D3D preloading */ if (awtPreloadD3D != 0) { char *envValue; /* D3D routines checks env.var J2D_D3D if no appropriate * command line params was specified */ envValue = getenv("J2D_D3D"); if (envValue != NULL && JLI_StrCaseCmp(envValue, "false") == 0) { awtPreloadD3D = 0; } /* Test that AWT preloading isn't disabled by J2D_D3D_PRELOAD env.var */ envValue = getenv("J2D_D3D_PRELOAD"); if (envValue != NULL && JLI_StrCaseCmp(envValue, "false") == 0) { awtPreloadD3D = 0; } if (awtPreloadD3D < 0) { /* If awtPreloadD3D is still undefined (-1), test * if it is turned on by J2D_D3D_PRELOAD env.var. * By default it's turned OFF. */ awtPreloadD3D = 0; if (envValue != NULL && JLI_StrCaseCmp(envValue, "true") == 0) { awtPreloadD3D = 1; } } } if (awtPreloadD3D) { AWTPreload(D3D_PRELOAD_FUNC); }#endif /* ENABLE_AWT_PRELOAD */ if (thread_handle) { // 等待線程結(jié)束,并獲取返回碼 WaitForSingleObject(thread_handle, INFINITE); GetExitCodeThread(thread_handle, &rslt); CloseHandle(thread_handle); } else { // 如果實(shí)在是創(chuàng)建線程失敗,則自身直接執(zhí)行該方法即可 rslt = continuation(args); }#ifdef ENABLE_AWT_PRELOAD if (awtPreloaded) { AWTPreloadStop(); }#endif /* ENABLE_AWT_PRELOAD */ return rslt;}

          在linux中的創(chuàng)建線程如下:

          // solaris/bin/java_md_solinux.c/* * Block current thread and continue execution in a new thread */intContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {    int rslt;#ifdef __linux__    pthread_t tid;    pthread_attr_t attr;    pthread_attr_init(&attr);    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
          if (stack_size > 0) { pthread_attr_setstacksize(&attr, stack_size); } // 常見的 pthread_xx 方式 創(chuàng)建線程 if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) { void * tmp; pthread_join(tid, &tmp); rslt = (int)tmp; } else { /* * Continue execution in current thread if for some reason (e.g. out of * memory/LWP) a new thread can't be created. This will likely fail * later in continuation as JNI_CreateJavaVM needs to create quite a * few new threads, anyway, just give it a try.. */ rslt = continuation(args); }
          pthread_attr_destroy(&attr);#else /* ! __linux__ */ thread_t tid; long flags = 0; if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) { void * tmp; thr_join(tid, NULL, &tmp); rslt = (int)tmp; } else { /* See above. Continue in current thread if thr_create() failed */ rslt = continuation(args); }#endif /* __linux__ */ return rslt;}


          即相同的語義,不同平臺下的各自實(shí)現(xiàn)而已。

          通過C語言的函數(shù)傳遞,從而進(jìn)行調(diào)用。通過創(chuàng)建一個新線程,調(diào)用JavaMain(); 由javaMain處理所有java事務(wù)。而自身則只需等待JavaMain完成即可。這也符合面向過程編程思想。

          3. jvm初始化小結(jié)

          我們平時是通過 java xxx.xxx ?或者 java -jar xx.jar 啟動jvm, 通過這兩篇文章,我們也看清了其背后的原理。就是通過解析各種參數(shù),驗證各種參數(shù),驗證jre環(huán)境,然后驗證jre是否可用,最后將指定的mainClass加載出來。丟到一個新的線程中去執(zhí)行,將執(zhí)行權(quán)力轉(zhuǎn)發(fā)給java代碼,最后等待該線程完成。

          應(yīng)該說事個流程沒有難點(diǎn),或者說一切都很理所當(dāng)然。但其中的核心,都是通過加載JavaVM()去做的,也就是真正的jvm. 然后通過或者對應(yīng)的幾個接口方法地址,進(jìn)行調(diào)用,從而完成啟動任務(wù)。所以,java雖是一個啟動命令,但核心并不在這里。而是在 hotspot 或其他地方。jdk 畢竟只是一個工具箱而已。jre 才是關(guān)鍵。




          往期精彩推薦



          騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因為你沒認(rèn)真看完這篇文章


          END


          關(guān)注作者微信公眾號 —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識以及最新面試寶典


          你點(diǎn)的每個好看,我都認(rèn)真當(dāng)成了


          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動力


          作者:等你歸去來

          出處:https://www.cnblogs.com/yougewe/p/14400859.html

          瀏覽 104
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品自拍三级 | 欧美色哟哟哟 | 四虎福利 | 成人做爰黄 片视频动漫 | 天天夜夜肏|