前言 无论是Android开发者还是Java工程师应该都有使用过JNI开发,但对于JVM如何加载so、Android系统如何加载so,可能鲜有时间了解。 本文通过代码、流程解释,带大家快速了解其加载原理,扫清困惑。 Systemload()loadLibrary() load() System提供的load()用于指定so的完整的路径名且带文件后缀并加载,等同于调用Runtime类提供的load()。 Ifthefilenameargument,whenstrippedofanyplatformspecificlibraryprefix,path,andfileextension,indicatesalibrarywhosenameis,forexample,L,andanativelibrarycalledLisstaticallylinkedwiththeVM,thentheJNIOnLoadLfunctionexportedbythelibraryisinvokedratherthanattemptingtoloadadynamiclibrary。 Eg。 System。load(sdcardpathlibA。so) 步骤简述: 通过Reflection获取调用来源的Class实例接着调用Runtime的load0()实现 load0()首先获取系统的SecurityManager。当SecurityManager存在的话检查目标so文件的访问权限,权限不足的话打印拒绝信息、抛出SecurityException,如果name参数为空,抛出NullPointerException。如果so文件名非绝对路径的话,并不支持,并抛出UnsatisfiedLinkError,message为: Expectinganabsolutepathofthelibrary:xxx 针对so文件的权限检查和名称检查均通过的话,继续调用ClassLoader的loadLibrary()实现,需要留意的是绝对路径参数为true。 javalangSystem。javapublicstaticvoidload(Stringfilename){Runtime。getRuntime()。load0(Reflection。getCallerClass(),filename);}javalangRuntime。javasynchronizedvoidload0(Classlt;?fromClass,Stringfilename){SecurityManagersecuritySystem。getSecurityManager();if(security!null){security。checkLink(filename);}if(!(newFile(filename)。isAbsolute())){thrownewUnsatisfiedLinkError(Expectinganabsolutepathofthelibrary:filename);}ClassLoader。loadLibrary(fromClass,filename,true);} loadLibrary() System类提供的loadLibrary()用于指定so的名称并加载,等同于调用Runtime类提供的loadLibrary()。在Android平台系统会自动去系统目录(systemlib64)、应用lib目录(dataappxxxlib64)下去找libname参数拼接了lib前缀的库文件。 Thelibnameargumentmustnotcontainanyplatformspecificprefix,fileextensionorpath。 IfanativelibrarycalledlibnameisstaticallylinkedwiththeVM,thentheJNIOnLoadlibnamefunctionexportedbythelibraryisinvoked。 Eg。 System。loadLibrary(A) 步骤简述: 同样通过Reflection获取调用来源的Class实例接着调用Runtime的loadLibrary0()实现 loadLibrary0()首先获取系统的SecurityManager,并检查目标so文件的访问权限。权限不足或文件名为空的话和上面一样抛出Exception。确保so名称不包含,反之抛出UnsatisfiedLinkError,message为: Directoryseparatorshouldnotappearinlibraryname:xxx 检查通过后,同样调用ClassLoader的loadLibrary()实现继续下一步,只不过绝对路径参数为false。 javalangSystem。javapublicstaticvoidloadLibrary(Stringlibname){Runtime。getRuntime()。loadLibrary0(Reflection。getCallerClass(),libname);}javalangRuntime。javasynchronizedvoidloadLibrary0(Classlt;?fromClass,Stringlibname){SecurityManagersecuritySystem。getSecurityManager();if(security!null){security。checkLink(libname);}if(libname。indexOf((int)File。separatorChar)!1){thrownewUnsatisfiedLinkError(Directoryseparatorshouldnotappearinlibraryname:libname);}ClassLoader。loadLibrary(fromClass,libname,false);} ClassLoaderloadLibrary() 上面的调用栈可以看到无论是load()还是loadLibrary()最终都是调用ClassLoader的loadLibrary(),主要区别在于name参数是lib完整路径、还是lib名称,以及是否是绝对路径参数。 1。首先通过getClassLoader()获得加载源所属的ClassLoader实例 2。确保存放libraries路径的字符串数组syspaths不为空。尚且为空的话,调用initializePath(java。library。path)先初始化usr路径字符串数组,再调用initializePath(sun。boot。library。path)初始化system路径字符串数组。initializePath()具体见下章节。 3。依据是否isAbsolute决定是否直接加载library name是绝对路径的话,直接创建File实例,调用loadLibrary0(),继续加载该文件。具体见下章节。检查loadLibrary0的结果,true即表示加载成功,结束;false即表示加载失败,抛出UnsatisfiedLinkError Cantloadxxx name非绝对路径并且获取的ClassLoader存在的话,通过findLibrary(),根据so名称获得lib绝对路径,并创建指向该路径的File实例libfile。并确保该文件的路径是绝对路径。反之,抛出UnsatisfiedLinkError。 ClassLoader。findLibraryfailedtoreturnanabsolutepath:xxx 此后也是调用loadLibrary0()继续加载该文件,并检查loadLibrary0的结果,处理同上。 4。假使ClassLoader不存在,遍历system路径字符串数组的元素。 通过mapLibraryName()分别将libname映射到平台关联的lib完整名称并返回,具体见下章节。创建当前遍历的path下libfile实例。调用loadLibrary0()继续加载该文件,并检查结果: true则直接结束false的话,通过mapAlternativeName()获取该lib可能存在的替代文件名,比如将后缀替换为jnilib如果再度map后的libfile不为空,调用loadLibrary0()再度加载该文件并检查结果,true则直接结束;反之,进入下一次循环 5。至此,如果仍未成功找到library文件,则在ClassLoader存在的情况下,到usr路径字符串数组中查找。遍历usr路径字符串数组的元素后续逻辑和上述一致,只是map时候的前缀不同,是usrpaths的元素 6。最终进行默认处理,即抛出UnsatisfiedLinkError,提示在java。library。pathpropery代表的路径下也未找到so文件。 noxxinjava。library。path javalangClassLoader。javastaticvoidloadLibrary(Classlt;?fromClass,Stringname,booleanisAbsolute){ClassLoaderloader(fromClassnull)?null:fromClass。getClassLoader();if(syspathsnull){usrpathsinitializePath(java。library。path);syspathsinitializePath(sun。boot。library。path);}if(isAbsolute){if(loadLibrary0(fromClass,newFile(name))){return;}thrownewUnsatisfiedLinkError(Cantloadlibrary:name);}if(loader!null){Stringlibfilenameloader。findLibrary(name);if(libfilename!null){FilelibfilenewFile(libfilename);if(!libfile。isAbsolute()){thrownewUnsatisfiedLinkError(。。。);}if(loadLibrary0(fromClass,libfile)){return;}thrownewUnsatisfiedLinkError(Cantloadlibfilename);}}for(inti0;isyspaths。length;i){FilelibfilenewFile(syspaths〔i〕,System。mapLibraryName(name));if(loadLibrary0(fromClass,libfile)){return;}libfileClassLoaderHelper。mapAlternativeName(libfile);if(libfile!nullloadLibrary0(fromClass,libfile)){return;}}if(loader!null){for(inti0;iusrpaths。length;i){FilelibfilenewFile(usrpaths〔i〕,System。mapLibraryName(name));if(loadLibrary0(fromClass,libfile)){return;}libfileClassLoaderHelper。mapAlternativeName(libfile);if(libfile!nullloadLibrary0(fromClass,libfile)){return;}}}Oops,itfailedthrownewUnsatisfiedLinkError(nonameinjava。library。path);} ClassLoaderinitializePath() 从System中获取对应property代表的path到数组中。 1。先调用getProperty()从JVM中取出配置的路径,默认的是。 其中的checkKey()将检查key名称是否合法,null的话抛出NullPointerException: keycantbenull 如果为,抛出IllegalArgumentException: keycantbeempty 后面通过getSecurityManager()获取SecurityManager实例,检查是否存在该property的访问权限。 2。如果允许引用路径元素并且存在的话,将路径字符串的char取出进行拼接、计算得到路径字符串数组。 3。反之通过indexOf()统计出现的次数,并创建一个次数1的数组。 4。遍历该路径字符串,通过substring()将各的中间path内容提取到上述数组中。 5。最后返回得到的path数组。 javalangClassLoader。javaprivatestaticString〔〕initializePath(Stringpropname){StringldpathSystem。getProperty(propname,);StringpsFile。pathSeparator;。。。ildpath。indexOf(ps);n0;while(i0){n;ildpath。indexOf(ps,i1);}String〔〕pathsnewString〔n1〕;ni0;jldpath。indexOf(ps);while(j0){if(ji0){paths〔n〕ldpath。substring(i,j);}elseif(ji0){paths〔n〕。;}ij1;jldpath。indexOf(ps,i);}paths〔n〕ldpath。substring(i,ldlen);returnpaths;} ClassLoaderfindLibrary() findLibrary()将到ClassLoader中查找lib,取决于各JVM的具体实现。比如可以看看Android上的实现。 到DexPathList的具体实现中调用首先通过System类的mapLibraryName()中获得mapping后的lib全名,细节见下章节遍历存放nativelib路径元素数组nativeLibraryPathElements逐个调用各元素的findNativeLibrary()实现去寻找一经找到立即返回,遍历结束仍未发现的话返回null androidlibcoredalviksrcmainjavadalviksystemBaseDexClassLoader。javapublicStringfindLibrary(Stringname){returnpathList。findLibrary(name);}androidlibcoredalviksrcmainjavadalviksystemDexPathList。javapublicStringfindLibrary(StringlibraryName){到System中获得mapping后的lib全名StringfileNameSystem。mapLibraryName(libraryName);到存放nativelib路径数组中遍历for(NativeLibraryElementelement:nativeLibraryPathElements){Stringpathelement。findNativeLibrary(fileName);一旦找到立即返回并结束,反之进入下一次循环if(path!null){returnpath;}}路径中全找遍了,仍未找到则返回nullreturnnull;} SystemmapLibraryName() mapLibraryName()的作用很简单,即将lib名称mapping到完整格式的名称,比如输入opencv得到的是libopencv。so。如果遇到名称为空或者长度超上限240的话,将抛出相应Exception。 javalangSystem。javapublicstaticnativeStringmapLibraryName(Stringlibname); 其是native方法,具体实现位于JDKNativeSourceCode中,可在如下网站中看到,网站地址如下: http:hg。openjdk。java。netjdk8jdk8jdkfile687fd7c7986dsrcsharenative nativejavalangSystem。cdefineJNILIBPREFIXlibdefineJNILIBSUFFIX。soJavajavalangSystemmapLibraryName(JNIEnvenv,jclassign,jstringlibname){定义最后名称的Sring长度变量intlen;并获取lib前缀、后缀的字符串常量的长度intprefixlen(int)strlen(JNILIBPREFIX);intsuffixlen(int)strlen(JNILIBSUFFIX);定义临时的存放最后名称的char数组jcharchars〔256〕;如果libname参数为空,抛出NPEif(libnameNULL){JNUThrowNullPointerException(env,0);returnNULL;}获取libname长度len(env)GetStringLength(env,libname);如果大于240的话抛出IllegalArgumentExceptionif(len240){JNUThrowIllegalArgumentException(env,nametoolong);returnNULL;}将前缀lib的字符拷贝到临时的char数组头部cpchars(chars,JNILIBPREFIX,prefixlen);将lib名称从字符串里拷贝到char数组的lib后面(env)GetStringRegion(env,libname,0,len,charsprefixlen);更新名称长度为:前缀lib名称lenprefixlen;将后缀。so的字符拷贝到临时的char数组里的lib名称后cpchars(charslen,JNILIBSUFFIX,suffixlen);再次更新名称长度为:前缀lib名称后缀lensuffixlen;从char数组里提取当前长度的复数char成员到新创建的String对象中返回return(env)NewString(env,chars,len);}staticvoidcpchars(jchardst,charsrc,intn){inti;for(i0;in;i){dst〔i〕src〔i〕;}} 逻辑很清晰,检查lib名称参数是否合法,之后便是将名称分别加上前后缀到临时字符数组中,最后转为字符串返回。 nativeLibraryPathElements() nativeLibraryPathElements数组来源于获取到的所有nativeLibrary目录后转换而来。 androidlibcoredalviksrcmainjavadalviksystemDexPathList。javapublicDexPathList(ClassLoaderdefiningContext,StringlibrarySearchPath){。。。this。nativeLibraryPathElementsmakePathElements(getAllNativeLibraryDirectories());} 所有nativeLibrary目录除了包含应用自身的library目录列表以外,还包括了系统的列表部分。 androidlibcoredalviksrcmainjavadalviksystemDexPathList。javaprivateListFilegetAllNativeLibraryDirectories(){ListFileallNativeLibraryDirectoriesnewArrayList(nativeLibraryDirectories);allNativeLibraryDirectories。addAll(systemNativeLibraryDirectories);returnallNativeLibraryDirectories;}Listofapplicationnativelibrarydirectories。privatefinalListFilenativeLibraryDirectories;Listofsystemnativelibrarydirectories。privatefinalListFilesystemNativeLibraryDirectories; 应用自身的library目录列表来自于DexPathList初始化时传入的librarySearchPath参数,splitPaths()负责去该path下遍历各级目录得到对应数组。 androidlibcoredalviksrcmainjavadalviksystemDexPathList。javapublicDexPathList(ClassLoaderdefiningContext,StringlibrarySearchPath){。。。this。nativeLibraryDirectoriessplitPaths(librarySearchPath,false);}privatestaticListFilesplitPaths(StringsearchPath,booleandirectoriesOnly){ListFileresultnewArrayList();if(searchPath!null){for(Stringpath:searchPath。split(File。pathSeparator)){if(directoriesOnly){try{StructStatsbLibcore。os。stat(path);if(!SISDIR(sb。stmode)){continue;}}catch(ErrnoExceptionignored){continue;}}result。add(newFile(path));}}returnresult;} 系统列表则来自于系统的path路径,调用splitPaths()的第二个参数不同,促使其在分割的时候只处理目录类型的部分,纯文件的话跳过。 androidlibcoredalviksrcmainjavadalviksystemDexPathList。javapublicDexPathList(ClassLoaderdefiningContext,StringlibrarySearchPath){。。。this。systemNativeLibraryDirectoriessplitPaths(System。getProperty(java。library。path),true);。。。} 拿到path文件列表之后就是调用makePathElements转成对应元素数组。 按照列表长度创建等长的Element数组遍历path列表如果path包含!的话,将其拆分为path和zipDir两部分,并创建NativeLibraryElement实例反之如果是目录的话,直接用path创建NativeLibraryElement实例,zipDir参数则为空 androidlibcoredalviksrcmainjavadalviksystemDexPathList。javaprivatestaticNativeLibraryElement〔〕makePathElements(ListFilefiles){NativeLibraryElement〔〕elementsnewNativeLibraryElement〔files。size()〕;intelementsPos0;for(Filefile:files){Stringpathfile。getPath();if(path。contains(zipSeparator)){Stringsplit〔〕path。split(zipSeparator,2);FilezipnewFile(split〔0〕);Stringdirsplit〔1〕;elements〔elementsPos〕newNativeLibraryElement(zip,dir);}elseif(file。isDirectory()){Wesupportdirectoriesforlookingupnativelibraries。elements〔elementsPos〕newNativeLibraryElement(file);}}if(elementsPos!elements。length){elementsArrays。copyOf(elements,elementsPos);}returnelements;} findNativeLibrary() findNativeLibrary()将先确保当zip目录存在的情况下内部处理zip的ClassPathURLStreamHandler实例执行了创建。 如果zip目录不存在(一般情况下都是不存在的)直接判断该路径下lib文件是否可读,YES则返回pathname、反之返回nullzip目录存在并且ClassPathURLStreamHandler实例也创建完毕的话,检查该name的zip文件的存在。并在YES的情况下,在path和name之间跟上zip目录并返回,即:path!zipDirname DexPathList。javaandroid。。。libcoredalviksrcmainjavadalviksystemDexPathList。javaprivatestaticfinalStringzipSeparator!;staticclassNativeLibraryElement{publicStringfindNativeLibrary(Stringname){确保element初始化完成maybeInit();if(zipDirnull){如果zip目录为空,则直接创建该path下该文件的File实例可读的话则返回StringentryPathnewFile(path,name)。getPath();if(IoUtils。canOpenReadOnly(entryPath)){returnentryPath;}}elseif(urlHandler!null){zip目录并且urlHandler都存在创建该zip目录下lib文件的完整名称StringentryNamezipDirname;如果该名称的压缩包是否存在的话if(urlHandler。isEntryStored(entryName)){返回:路径zip目录lib名称的结果出去returnpath。getPath()zipSeparatorentryName;}}returnnull;}主要是确保在zipDir不为空的情况下内部处理zip的urlHandler实例已经创建完毕publicsynchronizedvoidmaybeInit(){。。。}} ClassLoaderloadLibrary0() 1。调用静态内部类NativeLibrary的native方法findBuiltinLib()检查是否是内置的动态链接库,细节见如下章节。如果不是内置的library,通过AccessController检查该library文件是否存在。 不存在则加载失败并结束存在则到本ClassLoader已加载library的nativeLibrariesVector或系统class的已加载libraryVectorsystemNativeLibraries中查找是否加载过已加载过则结束反之继续加载的任务 2。到所有ClassLoader已加载过的libraryVectorloadedLibraryNames里再次检查是否加载过,如果不存在的话,抛出UnsatisfiedLinkError: NativeLibraryxxxalreadyloadedinanotherclassloader 3。到正在加载卸载library的nativeLibraryContextStack中检查是否已经处理中了 存在并且ClassLoader来源匹配,则结束加载存在但ClassLoader来源不同,则抛出UnsatisfiedLinkError:NativeLibraryxxxisbeingloadedinanotherclassloader反之,继续加载的任务 4。依据ClassLoader、library名称、是否内置等信息,创建NativeLibrary实例并入nativeLibraryContext栈。 5。此后,交由NativeLibraryload,细节亦见如下章节,并在load后出栈。 6。最后根据load的结果决定是否将加载记录到对应的Vector当中。 javalangClassLoader。javaprivatestaticbooleanloadLibrary0(Classlt;?fromClass,finalFilefile){获取是否是内置动态链接库StringnameNativeLibrary。findBuiltinLib(file。getName());booleanisBuiltin(name!null);if(!isBuiltin){不是内置的话,检查文件是否存在booleanexistsAccessController。doPrivileged(newPrivilegedActionObject(){publicObjectrun(){returnfile。exists()?Boolean。TRUE:null;}})!null;if(!exists){returnfalse;}try{namefile。getCanonicalPath();}catch(IOExceptione){returnfalse;}}ClassLoaderloader(fromClassnull)?null:fromClass。getClassLoader();VectorNativeLibrarylibsloader!null?loader。nativeLibraries:systemNativeLibraries;synchronized(libs){intsizelibs。size();检查是否已经加载过for(inti0;isize;i){NativeLibraryliblibs。elementAt(i);if(name。equals(lib。name)){returntrue;}}synchronized(loadedLibraryNames){再次检查所有library加载历史中是否存在if(loadedLibraryNames。contains(name)){thrownewUnsatisfiedLinkError(。。。);}intnnativeLibraryContext。size();检查是否已经在加载中了for(inti0;in;i){NativeLibrarylibnativeLibraryContext。elementAt(i);if(name。equals(lib。name)){if(loaderlib。fromClass。getClassLoader()){returntrue;}else{thrownewUnsatisfiedLinkError(。。。);}}}创建NativeLibrary实例继续加载NativeLibrarylibnewNativeLibrary(fromClass,name,isBuiltin);并在加载前后压栈和出栈nativeLibraryContext。push(lib);try{lib。load(name,isBuiltin);}finally{nativeLibraryContext。pop();}加载成功的将该library名称缓存到vector中if(lib。loaded){loadedLibraryNames。addElement(name);libs。addElement(lib);returntrue;}returnfalse;}}} findBuiltinLib() 首先一如既往地先检查libraryname是否为空,为空则抛出ErrorNULLfilenamefornativelibrary将string类型的名称转为char指针,失败的话抛出OutOfMemoryError检查名称长度是否短于最起码的lib。so几位,失败的话返回NULL结束创建library名称指针libName并分配内存从char指针提取libxxx。so中xxx。so部分到libName中将libName中。so的。位置替换成调用findJniFunction()依据handle指针,library名称检查该library的JNIOnLoad()是否存在存在则释放libName内存并返回该函数地址反之,释放内存并返回NULL结束 nativejavalangClassLoader。cJavajavalangClassLoader00024NativeLibraryfindBuiltinLib(JNIEnvenv,jclasscls,jstringname){constcharcname;charlibName;。。。检查名称是否为空if(nameNULL){JNUThrowInternalError(env,NULLfilenamefornativelibrary);returnNULL;}procHandlegetProcessHandle();cnameJNUGetStringPlatformChars(env,name,0);检查char名称指针是否为空if(cnameNULL){JNUThrowOutOfMemoryError(env,NULL);returnNULL;}检查名称长度lenstrlen(cname);if(len(prefixLensuffixLen)){JNUReleaseStringPlatformChars(env,name,cname);returnNULL;}提取library名称(取出前后缀)libNamemalloc(len1);1fornullifprefixsuffix0if(libNameNULL){JNUReleaseStringPlatformChars(env,name,cname);JNUThrowOutOfMemoryError(env,NULL);returnNULL;}if(lenprefixLen){strcpy(libName,cnameprefixLen);}JNUReleaseStringPlatformChars(env,name,cname);libName〔strlen(libName)suffixLen〕;检查JNIOnLoad()释放存在retfindJniFunction(env,procHandle,libName,JNITRUE);if(ret!NULL){libJNUNewStringPlatform(env,libName);free(libName);returnlib;}free(libName);returnNULL;} findJniFunction() findJniFunction()用于到library指针、已加载卸载的JNI数组中查找该library名称所对应的JNIONLOAD、JNIONUNLOAD的函数地址。 nativejavalangClassLoader。cstaticvoidfindJniFunction(JNIEnvenv,voidhandle,constcharcname,jbooleanisLoad){constcharonLoadSymbols〔〕JNIONLOADSYMBOLS;constcharonUnloadSymbols〔〕JNIONUNLOADSYMBOLS;voidentryNameNULL;。。。如果是加载,则到JNIONLOADSYMBOLS中获取函数数组和长度if(isLoad){symsonLoadSymbols;symsLensizeof(onLoadSymbols)sizeof(char);}else{反之,则到JNIONUNLOADSYMBOLS中获取卸载函数数组和长度symsonUnloadSymbols;symsLensizeof(onUnloadSymbols)sizeof(char);}遍历该数组,调用JVMFindLibraryEntry()逐个查找JNIOn(Un)Loadlibnamefunction是否存在for(i0;isymsLen;i){cnamesymif((len(cname!NULL?strlen(cname):0)strlen(syms〔i〕)2)FILENAMEMAX){gotodone;}jniFunctionNamemalloc(len);if(jniFunctionNameNULL){JNUThrowOutOfMemoryError(env,NULL);gotodone;}buildJniFunctionName(syms〔i〕,cname,jniFunctionName);entryNameJVMFindLibraryEntry(handle,jniFunctionName);free(jniFunctionName);if(entryName){break;}}done:如果没有找到,默认返回NULLreturnentryName;} JVMFindLibraryEntry() JVMFindLibraryEntry()调用的是平台相关的dlllookup(),依据library指针和function名称。 vmprimsjvm。cppJVMLEAF(void,JVMFindLibraryEntry(voidhandle,constcharname))JVMWrapper2(JVMFindLibraryEntry(s),name);returnos::dlllookup(handle,name);JVMEND NativeLibraryload() NativeLibrary是定义在ClassLoader内的静态内部类,其代表着已加载library的实例,包含了该library的指针、所需的JNI版本、加载的Class来源、名称、是否是内置library、是否加载过重要信息。以及核心的加载load、卸载unloadnative实现。 javalangClassLoader。javastaticclassNativeLibrary{longhandle;privateintjniVersion;privatefinalClasslt;?fromClass;Stringname;booleanisBuiltin;booleanloaded;nativevoidload(Stringname,booleanisBuiltin);nativevoidunload(Stringname,booleanisBuiltin);staticnativeStringfindBuiltinLib(Stringname);。。。} 本章节我们着重看下load()的关键实现: 1。首先调用initIDs()初始化ID等基本数据 如果ClassLoaderNativeLibrary内部类、handle等属性有一不存在的话,返回FALSE并结束加载通过检查的话初始化procHandle指针 2。其次通过JNUGetStringPlatformChars()将String类型的library名称转为char类型,如果名称为空的话结束加载 3。如果不是内置的so,需要调用JVMLoadLibrary()加载得到指针(见下章节),反之沿用上述的procHandle指针即可 4。如果so指针存在的话,通过findJniFunction()和指针参数获取JNIOnLoad()的地址。如果JNIOnLoad()获取成功,则调用它并得到该so要求的jniVersion。反之设置为默认值0x00010001,即JNIVERSION11,1。1。接着调用JVMIsSupportedJNIVersion()检查JVM是否支持该版本,调用的是Threads的issupportedjniversionincluding11()。如果不支持或者是内置so同时版本低于1。8,抛出UnsatisfiedLinkError: unsupportedJNIversionxxxrequiredbyyyy 反之表示加载成功。 5。反之,抛出异常ExceptionOccurred。 nativejavalangClassLoader。cJavajavalangClassLoader00024NativeLibraryload(JNIEnvenv,jobjectthis,jstringname,jbooleanisBuiltin){constcharcname;。。。voidhandle;if(!initIDs(env))return;cnameJNUGetStringPlatformChars(env,name,0);if(cname0)return;handleisBuiltin?procHandle:JVMLoadLibrary(cname);if(handle){JNIOnLoadtJNIOnLoad;JNIOnLoad(JNIOnLoadt)findJniFunction(env,handle,isBuiltin?cname:NULL,JNITRUE);if(JNIOnLoad){。。。jniVersion(JNIOnLoad)(jvm,NULL);}else{jniVersion0x00010001;}。。。if(!JVMIsSupportedJNIVersion(jniVersion)(isBuiltinjniVersionJNIVERSION18)){charmsg〔256〕;jiosnprintf(msg,sizeof(msg),unsupportedJNIversion0x08Xrequiredbys,jniVersion,cname);JNUThrowByName(env,javalangUnsatisfiedLinkError,msg);if(!isBuiltin){JVMUnloadLibrary(handle);}gotodone;}(env)SetIntField(env,this,jniVersionID,jniVersion);}else{cause(env)ExceptionOccurred(env);if(cause){(env)ExceptionClear(env);(env)SetLongField(env,this,handleID,(jlong)0);(env)Throw(env,cause);}gotodone;}(env)SetLongField(env,this,handleID,ptrtojlong(handle));(env)SetBooleanField(env,this,loadedID,JNITRUE);done:JNUReleaseStringPlatformChars(env,name,cname);}staticjbooleaninitIDs(JNIEnvenv){if(handleID0){jclassthis(env)FindClass(env,javalangClassLoaderNativeLibrary);if(this0)returnJNIFALSE;handleID(env)GetFieldID(env,this,handle,J);if(handleID0)returnJNIFALSE;。。。procHandlegetProcessHandle();}returnJNITRUE;} JVMLoadLibrary() JVMLoadLibrary()是JVM这层加载library的最后一个实现,具体步骤如下: 定义1024长度的char数组和接收加载结果的指针调用dllload()加载library,其细节见下章节加载失败的话,打印library名称和错误message同时抛出UnsatisfiedLinkError反之将加载结果返回 vmprimsjvm。cppJVMENTRYNOENV(void,JVMLoadLibrary(constcharname))JVMWrapper2(JVMLoadLibrary(s),name);charebuf〔1024〕;voidloadresult;{ThreadToNativeFromVMttnfvm(thread);loadresultos::dllload(name,ebuf,sizeofebuf);}if(loadresultNULL){charmsg〔1024〕;jiosnprintf(msg,sizeofmsg,s:s,name,ebuf);HandlehexceptionExceptions::newexception(。。。);THROWHANDLE0(hexception);}returnloadresult;JVMEND dllload() dllload()的实现跟平台相关,比如bsd平台就是调用标准库的dlopen(),而其最终的结果来自于dodlopen(),其将通过findlibrary()得到soinfo实例,内部将执行tohandle()得到library的指针。 bioniclibdllibdl。cppvoiddlopen(constcharfilename,intflag){constvoidcalleraddrbuiltinreturnaddress(0);returnloaderdlopen(filename,flag,calleraddr);}voidloaderdlopen(constcharfilename,intflags,constvoidcalleraddr){returndlopenext(filename,flags,nullptr,calleraddr);}staticvoiddlopenext(。。。){ScopedPthreadMutexLockerlocker(gdlmutex);glinkerlogger。ResetState();voidresultdodlopen(filename,flags,extinfo,calleraddr);if(resultnullptr){bionicformatdlerror(dlopenfailed,linkergeterrorbuffer());returnnullptr;}returnresult;}voiddodlopen(。。。){。。。if(si!nullptr){voidhandlesitohandle();sicallconstructors();failureguard。Disable();returnhandle;}returnnullptr;} JNIOnLoad() JNIOnLoad()定义在jni。h中,当library被JVM加载时会回调,该方法内一般会通过registerNatives()注册native方法并返回该library所需的JNI版本。该头文件还定义了其他函数和常量,比如JNI1。1等数值。 jni。h。。。structJNIEnv{。。。jintRegisterNatives(jclassclazz,constJNINativeMethodmethods,jintnMethods){returnfunctionsRegisterNatives(this,clazz,methods,nMethods);}jintUnregisterNatives(jclassclazz){returnfunctionsUnregisterNatives(this,clazz);}}。。。Definedbynativelibraries。JNIEXPORTjintJNICALLJNIOnLoad(JavaVMvm,voidreserved);defineJNIVERSION110x00010001。。。 结语 总体流程可以归纳如下: System类提供的load()加载so的完整的路径名且带文件后缀,等同于直接调用Runtime类提供的load();loadLibrary()用于加载指定so的名称,等同于调用Runtime类提供的loadLibrary()。两者都将通过SecurityManager检查so的访问权限以及名称是否合法之后调用ClassLoader类的loadLibrary()实现,区别在于前者指定的是否是绝对路径的isAbsolute参数是否为trueClassLoader首先需要通过System提供的getProperty()获取JVM配置的存放usr、systemlibrary路径字符串数组如果libraryname非绝对路径,需要先调用findLibrary()获取该name对应的完整so文件,之后再调用loadLibrary0()继续当ClassLoader不存在,分别到system、usr字符串数组中查找该so是否存在loadLibrary0()将调用native方法findBuiltinLib()检查是否是内置的动态链接库,并到加载过vector、加载中context中查找是否已经加载过、加载中通过检查的话调用NativeLibrary静态内部类继续,事实上是调用ClassLoader。c的load()其将调用jvm。cpp的JVMLoadLibrary()进行so的加载获得指针根据OS的实现,dllload()通过dlopen()执行so的打开和地址返回最后通过findJniFunction()获取JNIOnLoad()地址进行native方法的注册和所需JNI版本的收集。 原文链接:https:mp。weixin。qq。comsHVQvjDhhUuCrkBuOP8PJZw