历数Java虚拟机垃圾回收GC收集器的缺点剖析

(env->GetPrimitiveArrayCritical(mat2, &isCopyB));

mult_SSE(A, B);
env->ReleasePrimitiveArrayCritical(mat1, A, 0);
env->ReleasePrimitiveArrayCritical(mat2, B, JNI_ABORT);

}

将GetFloatArrayElements和ReleaseFloatArrayElements换成GetPrimitiveArrayCritical和ReleasePrimitiveArrayCritical就行了。CriticalArray则是为了解决数组副本问题,它是通过在GetPrimitiveArrayCritical和ReleasePrimitiveArrayCritical中创建一个阻止GC的临界区,得以将数组的真实数据直接暴露给用户。
```csharp
JNIEXPORT void JNICALL JavaCritical_cn_hotspotvm_TestArray_mul( 
 jint length1, jfloat* mat1,
 jint length2, jfloat* mat2)
{
   mult_SSE(mat1, mat2);
}

CriticalNative是一种特殊的JNI函数,整个函数都是一个临界区(当然,也包括跳过一些非关键的安全检查),能够以牺牲JVM整体稳定性获取最大的性能。 由于最初是被设计为JRE的加密模块使用,考虑到现在的加密算法大多以块为单位,换句话说大多数情况下需要在JNI中频繁传递小规模的数组,CriticalNative被专门设计对数组的传递进行优化。 JavaCritical函数相比较之前的版本,能更进一步减少JNI调用开销,这是由于它可以跳过一些“多余”的检查,并进入一个禁止JVM进行垃圾回收的临界区,以此来获得性能上的提升。

4.2、堆外内存

许多的通信框架都会开辟一块堆外内存来提高效率,如netty等。实际上,在网络和磁盘IO过程中,如果数据是在Heap里的,最终也还是会拷贝一份到堆外,然后再进行发送。原因在于,操作系统把内存中的数据写入磁盘或网络时,要求数据所在的内存区域不能变动,但是GC会对内存进行整理,导致数据内存地址发生变化,所以只能先拷贝到堆外内存(不受GC影响),然后把这个地址发给操作系统。

源代码位置:openjdk/jdk/src/share/classes/sun/nio/ch/IOUtil.java
static int read(FileDescriptor fd, 
    ByteBuffer dst, long position,
    NativeDispatcher nd)
        throws IOException
{
        if (dst.isReadOnly())
            throw new IllegalArgumentException("Read-only buffer");
        // 如果是在堆外内存DirectBuffer时,直接读取内容并返回就可以
        if (dst instanceof DirectBuffer)
            return readIntoNativeBuffer(fd, dst, position, nd);
// 申请一个临时的DirectBuffer
        ByteBuffer bb = Util.getTemporaryDirectBuffer(dst.remaining());
        try {
            // 将堆中的内容拷贝到DirectBuffer
            int n = readIntoNativeBuffer(fd, bb, position, nd);
            bb.flip();
            if (n > 0)
                dst.put(bb);
            return n;
        } finally {
            Util.offerFirstTemporaryDirectBuffer(bb);
        }
}

在Java中有个DirectByteBuffer,DirectByteBuffer在创建的时候会通过Unsafe的native方法直接在Java堆外通过malloc分配一块内存,然后通过Unsafe的native方法来操作这块内存。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。 举个例子如下:

try (FileChannel channel = FileChannel.open(Paths.get("/tmp/data.txt"), StandardOpenOption.READ)) {
     // 直接缓冲区
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    while (channel.read(buffer) > 0) {
        buffer.flip();
        // 处理数据...
        buffer.clear();
    }
} catch (IOException e) {
    e.printStackTrace();
}

调用FileChannel的open()方法会返回一个FileChannelmpl实例,这个实例的read()方法会调用IOUtil.read()方法,这个方法这是我们上面介绍的方法。