1.å®åjniå¼åï¼
2.java编译器软件有哪些(java编译器推荐)
3.怎样把JAVA源代码编译exe文件?
4.java是如何调用native方法?hotspot源码分析必会技能
5.记一次源码追踪分析,从Java到JNI,再到JVM的C++:fileChannel.map()为什么快;源码分析map方法,put方法
6.从HotSpot源码,深度解读 park 和 unpark
å®åjniå¼åï¼
å¦ä½ç¼åå®å软件
1ãè§ææ2é1å®åçæ¬ï¼åå¼å§ä¹ä¸ç¥ééé¢æäºä»ä¹ææ¯é¾åº¦ï¼ä½æ¯è¦åçç®æ å·²ç»æç¡®äºï¼èä¸ä¹æ²¡æç°æçï¼ç¢°å°é®é¢å°±æ¥èµæï¼æ ¢æ ¢å°è§£å³ï¼è¿æ ·æçæ¾ç¢ï¼å¦ä¹ çææé常好ã
2ãä¸é¢æå°±å¼å§ä»ç»æ们å¼åéè¦ç¨ç软件ï¼Xcode(软件ä¸è½½å°åï¼developer.apple/xcode/)ï¼IOS模æå¨ã
3ãéæ±åæãç¡®å®è¦å¼åæä¸æ¬¾è½¯ä»¶çåæ¶è¦å¯¹éæ±è¿è¡åæï¼å¼åçappæå®é å®ç¨æä¹æå¯ä»¥ãå¼åè éè¦å¯¹è½¯ä»¶ç³»ç»è¿è¡æ¦è¦è®¾è®¡ï¼å³ç³»ç»è®¾è®¡ã
4ãè°·ææ¨åºçAppInventorAndroidAppå¼åå·¥å ·å¯ä»¥è®©ä½ ä» éè¿ææå¼çç®åæä½å°±å¯ä»¥å建èªå·±çAndroidAppã对äºé£äºä¸ºäºç¹å®ç®çæ³è¦å¨æå°è¯å¼åä¸ä¸ªç®ååºç¨çç¨æ·ã
5ãEclipseADTEclipseADTæ¯Eclipseå¹³å°ä¸ç¨æ¥å¼åAndroidåºç¨ç¨åºçæ件TheSDKandAVDManagerè¯¥å·¥å ·å å«å¾å¤éè¦çåè½ï¼å æ¬ç®¡çä¸åçAndroidSDKçæ¬ï¼æ建ç®æ ï¼ï¼Androidççæ¬ä¼å¤ï¼APIä¸æäºå ¼å®¹æ§é®é¢ã
6ãä¸é¢ï¼ä¸ç§è±æ就为大家æ®åä¸ä¸å®å软件å¼åå ¥é¨ç¥è¯ãå级é¶æ®µè¦å¦ä¹ çå 容é ç½®ç¯å¢ï¼å ¨é¨éç¨åæ°çæ¬SDKãADTãECLIPSEãJDKãç¼åæè è¿è¡æå¦ç¤ºä¾è¿æ¶é´ä¸»è¦è¿è¡ç示ä¾å¦HELLOWORLDï¼è¿æSDK带çä¾åã
Androidä¸æä¹éè¿JNIç¼ç¨å»åéadbæ令æ§å¶ææºç¼åä¸ä¸ªCç¨åºï¼ä½¿ç¨system(âcmdline)ï¼ç´æ¥è°ç¨å½ä»¤è¡ç¨åºå³å¯ã
(constchar*)(*env)-GetStringUTFChars(envï¼inputStrï¼JNI_FALSE)ï¼LOGI(dufresne---%sï¼(constchar*)str)ï¼//éç¥èææºæ¬å°ä»£ç ä¸åéè¦éè¿str访é®Javaå符串ã
$cd~/project/Android/JNITest/bin$cd~/project/Android/JNITest/binæ们ç¨lså½ä»¤æ¥çï¼å¯ä»¥çå°binç®å½ä¸æ个classesç®å½ï¼å ¶ç®å½ç»æ为classes/org/tonny/jniï¼å³classesçåç®å½ç»ææ¯androidå·¥ç¨çå åorg.tonny.jniã
å¦ä½ç¨javaè¿è¡å®ååºç¨çå¼åç´æ¥ç¾åº¦æç´¢å®åå¼åæç¨ï¼èµæºé常å¤ãå å»æJavaåCå¦å¥½å说ã
æ¯çï¼Javaå¯ä»¥ç¨äºå¼åå®ååºç¨ãå®ååºç¨çå¼åå¯ä»¥ä½¿ç¨Javaç¼ç¨è¯è¨åAndroidSDKï¼è½¯ä»¶å¼åå·¥å ·å ï¼ãè³äºâå®åæ¯å¦ä¼ä¸è½½å± æ°âï¼æä¸å¤ªæç½æ¨çææã
第ä¸æ¥ï¼å®è£ jdkï¼é ç½®jdkç¯å¢ãç¾åº¦æjdké 置第äºæ¥ï¼å®è£ AndroidStudio+SDKãæè Eclipse+ADT+AndroidSDKã第ä¸æ¥ï¼éè¦Androidæºåæè å®è£ èææºãå¦æçé®ï¼è¯·æåºã
ä¸ï¼JavaSEç¼ç¨Javaæ¯ä¸ç§é¢å对象çå¼åè¯è¨ï¼Androidæä½ç³»ç»çåºç¨å±ä½¿ç¨Javaè¯è¨æ¥å¼åï¼æ以è¦æ³è¿è¡Androidå¼åå¿ é¡»æè¯å¥½çJavaåºç¡ã
Androidåºç¨ç¨åºå¼åæ¯ä»¥Javaè¯è¨ä¸ºåºç¡çï¼æ以éè¦ææå®çJavaåºç¡ç¥è¯ãé¦å çæjavaåºæ¬è¯æ³ï¼ç¶åçæ设计模å¼çã
ç¨jniåçå®ååºç¨è½è¢«åç¼è¯å
å°ç¨åºè½¬æ¢ææ¬å°ä»£ç ä¹æ¯ä¸ç§é²æ¢åç¼è¯çæææ¹æ³ãå 为æ¬å°ä»£ç å¾å¾é¾ä»¥è¢«åç¼è¯ãå¼å人åå¯ä»¥éæ©å°æ´ä¸ªåºç¨ç¨åºè½¬æ¢ææ¬å°ä»£ç ï¼ä¹å¯ä»¥éæ©å ³é®æ¨¡å转æ¢ã
æå ç§æ¹å¼æ¥æé«è¢«åç¼è¯å代ç çé¾åº¦ï¼1å ³é®ä»£ç 使ç¨jniè°ç¨æ¬å°ä»£ç ï¼ç¨cæè c++ç¼åï¼å æ¤ç¸å¯¹æ¯è¾é¾äºåç¼è¯2æ··æ·java代ç ãæ··æ·æ¯ä¸æ¹å代ç é»è¾çæ åµä¸ï¼å¢å æ ç¨ä»£ç ï¼æè éå½åï¼ä½¿åç¼è¯åçæºä»£ç é¾äºçæã
ç±äºapkæ¯Androidèææºå è½½çï¼å®æä¸å®çè§èï¼å å¯apkåDalvikæ æ³è¯å«apkäºãå®å ¨é¿å æ¯ä¸å¯è½çï¼æ»æ人è½å¤ç ´è§£åç代ç ãä½æ¯æå ç§æ¹å¼æ¥æé«è¢«åç¼è¯å代ç çé¾åº¦ã
å¦ä½JAVA代ç è°ç¨ï¼jniå§ï¼å®åçSOæ件æ¯linuxä¸çæ件ï¼ç¨cæè c++åçã
ä¸é¢è¯´äºï¼è¿ç§æ¹å¼å ¶å®å¹¶ä¸æ¯çæ£å å¯ä»£ç ï¼å ¶å®ä»£ç è¿æ¯è½å¤è¢«äººåç¼è¯(æ人å¯è½è¯´ï¼ä½¿ç¨proguardä¸çoptimizeé项ï¼å¯ä»¥ä»åèæµå±é¢æ´æ¹ä»£ç ï¼çè³å¯ä»¥è®©JDè¿äºåç¼è¯è½¯ä»¶å¯ä»¥æ æ³å¾å°å 容ã
Androidä¸JNIæ¯ç¼è¯soåºçæºä»£ç ï¼ç¼è¯æååä¼çæSOåºï¼androidä¸æç»æ¯ä½¿ç¨SOåºçã
å®åå¼åè°ç¨åºå±ç¡¬ä»¶æä½æ¹æ³å¦ä¸ï¼ç¼è¯åå®è£ 该çæçapkå³å¯ä½¿ç¨è¯¥ç¨åºè°ç¨åºå±ç¡¬ä»¶é©±å¨ã大æ¦æ´ä¸ªè¿ç¨å°±è¿æ ·ï¼ä¸å±appè°ç¨æ¡æ¶å±çjavaæ¥å£ï¼javaæ¥å£éè¿jniè°ç¨ç¡¬ä»¶æ½è±¡å±å³å¯ã
ï¼Androidåºç¨ç¨åºå±ï¼2ï¼åºç¨ç¨åºæ¡æ¶å±ï¼3ï¼ç³»ç»è¿è¡åºå±ï¼4ï¼Linuxæ ¸å¿å±ã
å¯ä»¥åèä¸ä¸/post//
问题/目的问题1Java中哪些API使用到了mmap问题2怎么知道该API使用到了mmap,如何追踪程序的游戏源码笔记系统调用目的1源码中分析验证,从Java到JNI,再到C++:fileChannel.map()使用的是系统调用mmap目的2源码验证分析:调用mmapedByteBuffer.put(Byte[])时JVM在搞些什么?mmap比普通的read/write快在哪?揭晓答案1mmap在Java NIO中的体现/使用看一个例子
// 1GBpublic static final int _GB = 1**;File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer mmapedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB);for (int i = 0; i < _GB; i++) { count++;mmapedByteBuffer.put((byte)0);}其中fileChannel.map()底层使用的就是系统调用mmap,函数签名为: public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException
答案2程序执行的系统调用追踪/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}把上面这段代码编译后把“.class”文件拉到linux执行,并用linux上的strace工具记录其系统调用日志,拿到日志文件我们可以在日志中看到以下信息(关于怎么拿到日志可以参照我的博文:无(代写)):
注:日志有多行,这里只选取我们关注的
// ...// 看到了我们打的开始标志openat(AT_FDCWD, "start1.txt", O_RDONLY) = -1 ENOENT (No such file or directory)// ... // 打开文件,文件描述符fd为6openat(AT_FDCWD, "filename", O_RDWR|O_CREAT, ) = 6// 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// ... // 判断文件状态fstat(6, { st_mode=S_IFREG|, st_size=, ...}) = 0// 进行内存映射mmap(NULL, , PROT_READ|PROT_WRITE, MAP_SHARED, 6, 0) = 0x7f2fd6cd// ...// 程序退出exit(0)// 看到了我们打的结束标志openat(AT_FDCWD, "end.txt", O_RDONLY) = -1 ENOENT (No such file or directory)在上面程序的系统调用日志中我们确实看到了我们打的开始标志,结束标志。在开始标志和结束标志之间我们看到了我们的文件"filename"确实被打开了,文件描述符fd = 6;在打开文件后紧接着又执行了系统调用mmap,这一点我们Java代码一致,这样,我们就验证了我们答案1中的程序源码使用教程结论,可以开始我们的下文了
源码追踪分析,从Java到JNI,再到JVM的C++目的1寻源之旅:fileChannel.map()我们知道我们执行Java代码fileChannel.map()确实会在底层调用系统调用,那怎么在源码中得到验证呢?怎么落脚于源码进行分析呢?下面开始我们的寻源之旅
FileChannelImpl.map() 注:由于代码较长,这里代码中略去了一些我们不关注的,比如异常捕获等
public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{ // ...try { // ...synchronized (positionLock) { // ...long mapPosition = position - pagePosition;mapSize = size + pagePosition;try { // !我们要找的语句就在这!addr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError x) { // 如果内存不足,先尝试进行GCSystem.gc();try { Thread.sleep();} catch (InterruptedException y) { Thread.currentThread().interrupt();}try { // 再次试着mmapaddr = map0(imode, mapPosition, mapSize);} catch (OutOfMemoryError y) { // After a second OOME, failthrow new IOException("Map failed", y);}}} // ...} finally { // ...}}上面函数源码中真正执行mmap的语句是在addr = map0(imode, mapPosition, mapSize),于是我们寻着这里继续追踪
FileChannelImpl.map0()
// Creates a new mappingprivate native long map0(int prot, long position, long length)throws IOException;可以看到,该方法是一个native方法,所以后面的源码我们需要到这个FileChannelImpl.class对应的fileChannelImpl.c中去看,所以我们需要去找到JDK的源码
在JDK源码中我们找到fileChannelImpl.c文件
fileChannelImpl.c 根据JNI的对应规则,我们找到该文件内对应的Java_sun_nio_ch_FileChannelImpl_map0方法,其源码如下:
JNIEXPORT jlong JNICALLJava_sun_nio_ch_FileChannelImpl_map0(JNIEnv *env, jobject this, jint prot, jlong off, jlong len){ void *mapAddress = 0;jobject fdo = (*env)->GetObjectField(env, this, chan_fd);jint fd = fdval(env, fdo);int protections = 0;int flags = 0;if (prot == sun_nio_ch_FileChannelImpl_MAP_RO) { protections = PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_RW) { protections = PROT_WRITE | PROT_READ;flags = MAP_SHARED;} else if (prot == sun_nio_ch_FileChannelImpl_MAP_PV) { protections =PROT_WRITE | PROT_READ;flags = MAP_PRIVATE;}// !我们要找的语句就在这里!mapAddress = mmap(0,/* Let OS decide location */len,/* Number of bytes to map */protections,/* File permissions */flags,/* Changes are shared */fd, /* File descriptor of mapped file */off); /* Offset into file */if (mapAddress == MAP_FAILED) { if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "Map failed");return IOS_THROWN;}return handle(env, -1, "Map failed");}return ((jlong) (unsigned long) mapAddress);}我们要找的语句就上面代码中的mapAddress = mmap(0,len,protections,flags,fd,off),至于为什么不是直接的mmap,而是mmap,是因为这里的mmap是一个宏,在文件上方有其定义,如下:
#define mmap mmap至此,cas 5.3 源码学习我们就在源码中得到验证了我们问题2中的结论:fileChannelImpl.map()底层使用的是mmap系统调用
目的2寻源之旅:mmapedByteBuffer.put(Byte[ ])接着我们来看看当我们调用mmapedByteBuffer.put(Byte[])JVM底层在搞些什么动作
MappedByteBuffer ?首先我们得知道,当我们执行MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB)时,实际返回的对象是DirectByteBuffer类的实例,因为MappedByteBuffer为抽象类,且只有DirectByteBuffer继承了它,看下面两图就明白了
DirectByteBuffer 于是我们找到DirectByteBuffer内的put(Byte[ ])方法
public ByteBuffer put(byte x) { unsafe.putByte(ix(nextPutIndex()), ((x)));return this;}可以看到该方法内实际是调用Unsafe类内的putByte方法来实现功能的,所以我们还得去看Unsafe类
Unsafe.class
public native voidputByte(long address, byte x);该方法在Unsafe内是一个native方法,所以所以我们还得去看unsafe.cpp文件内对应的实现
unsafe.cpp
在JDK源码中,我们找到unsafe.cpp
在这份源码内,没有使用JNI内普通加前缀的方法来形成对应关系
不过我们还是能顺着源码的蛛丝轨迹找到我们要找的方法
注意到源码中有这样的注册机制,所以我们可以知道我们要找的代码就是上图中标注的代码
顺藤摸瓜,我们就找到了该方法的定义
UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \UnsafeWrapper("Unsafe_SetNative"#Type); \JavaThread* t = JavaThread::current(); \t->set_doing_unsafe_access(true); \void* p = addr_from_java(addr); \*(volatile native_type*)p = x; \t->set_doing_unsafe_access(false); \UNSAFE_END \该方法内主要的逻辑语句就是以下两句:
/** * @author Tptogiar * @description * @date /5/ - : */public class TestMappedByteBuffer{ public static final int _4kb = 4*;public static final int _GB= 1**;public static void main(String[] args) throws IOException, InterruptedException { // 为了方便在日志中找到本段代码的开始位置和结束位置,这里利用文件io来打开始标记FileInputStream startInput = null;try { startInput = new FileInputStream("start1.txt");startInput.read();} catch (IOException e) { e.printStackTrace();}File file = new File("filename");FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, _GB); //我们想分析的语句问题2for (int i = 0; i < _GB; i++) { map.put((byte)0); // 下文中需要分析的语句目的2}// 打结束标记FileInputStream endInput = null;try { endInput = new FileInputStream("end.txt");endInput.read();} catch (IOException e) { e.printStackTrace();}}}0至此,我们就知道:其实我们调用mmapedByteBuffer.put(Byte[ ])时,JVM底层并不需要涉及到系统调用(这里也可以用strace工具追踪从而得到验证)。也就是说通过mmap映射的空间在内核空间和用户空间是共享的,我们在用户空间只需要像平时使用用户空间那样就行了————获取地址,设置值,而不涉及用户态,柳霭馨源码内核态的切换
总结fileChannelImpl.map()底层用调用系统函数mmap
fileChannelImpl.map()返回的其实不是MappedByteBuffer类对象,而是DirectByteBuffer类对象
在linux上可以通过strace来追踪系统调用
JNI中“.class”文件内方法与“.cpp”文件内函数的对应关系不止是前缀对应的方法,还可以是注册的方式,这一点的追寻代码的时候有很大帮助
directByteBuffer.put()方法底层并没有涉及系统调用,也就不需要涉及切态的性能开销(其底层知识执行获取地址,设置值的操作),所以mmap的性能就比普通读写read/write好
...
原文:/post/从HotSpot源码,深度解读 park 和 unpark
我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的小伙伴可以加入。自习室加入码:D5A7A
Java并发包下的类大多基于AQS(AbstractQueuedSynchronizer)框架实现,而AQS线程安全的实现依赖于两个关键类:Unsafe和LockSupport。
其中,Unsafe主要提供CAS操作(关于CAS,在文章《读懂AtomicInteger源码(多线程专题)》中讲解过),LockSupport主要提供park/unpark操作。实际上,park/unpark操作的最终调用还是基于Unsafe类,因此Unsafe类才是核心。
Unsafe类的页面源码直播源实现是由native关键字说明的,这意味着这个方法是原生函数,是用C/C++语言实现的,并被编译成了DLL,由Java去调用。
park函数的作用是将当前调用线程阻塞,而unpark函数则是唤醒指定线程。
park是等待一个许可,unpark是为某线程提供一个许可。如果线程A调用park,除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且,unpark可以先于park调用,但不管unpark先调用多少次,都只提供一个许可,不可叠加。只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
在Linux系统下,park和unpark是通过Posix线程库pthread中的mutex(互斥量)和condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。当_counter=0时线程阻塞,当_counter>0时直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类的部分源码如下:
由源码可知,Parker类继承于PlatformParker,实际上是用Posix的mutex和condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。源码如下:
(如果不会下载JVM源码可以后台回复“jdk”,获得下载压缩包)
Native 关键字详解
在JDK源码中的Object类中,我们发现了getClass()方法、hashCode()方法、clone()方法,它们的共同点是使用了native关键词进行修饰。这意味着这些方法的实现不是用Java语言编写的,而是用其他语言(如C或C++)实现的。
那么,为什么要使用native关键词?这样做有什么作用?答案在于JNI(Java Native Interface)。JNI允许Java代码和其他语言编写的代码进行交互,满足以下需求:当Java类库不支持所需平台功能、已用其他语言编写类库需要调用、某些方法使用性能敏感语言(如汇编)实现时。从Java 1.1开始,JNI就作为Java平台的一部分,为解决上述需求提供了支持。
通过JNI,Java程序可以调用操作系统的相关技术实现的库函数,实现与其他技术和系统的交互,或调用其他技术实现的系统功能。同时,其他技术和系统也可以通过JNI提供的原生接口调用Java应用系统内部实现的功能。
以Windows系统为例,大部分可执行应用基于native PE结构,而Java虚拟机也是基于native结构实现的。Java应用体系构建于JVM之上,但使用JNI会使得程序不再跨平台,需要在不同系统环境下重新编译本地语言部分。程序的安全性也会降低,不当使用本地代码可能导致整个程序崩溃。尽管存在这些缺点,JNI仍因其性能优势而被广泛使用。
接下来,我们以HelloWorld程序为例,演示如何使用Java代码调用本地C程序。首先编写带有native声明的Java类,并生成.java文件。然后使用javac命令编译生成.class文件,接着使用javah -jni命令生成.h头文件。接着用C/C++(或其他语言)实现本地方法,生成动态链接库。最后,通过Java程序加载动态库,并实现调用。
在调用本地C程序的过程中,我们需要确保操作环境配置正确。这包括编译环境(如gcc)的安装和配置,以及确保Java和动态库的路径正确。
通过以上步骤,我们完成了使用JNI调用本地C程序的过程。这不仅展示了native关键词的使用,还展示了JNI在跨语言调用中的应用。
综上所述,native关键词允许Java程序调用非Java实现的代码,通过JNI提供与本地语言代码的交互能力。这在满足性能需求、集成外部库或实现平台相关功能时至关重要。