2016-06-30 20 views
0

我讀了關於TheUnsafe的東西,但我很困惑的事實是,與C/C++不同,我們必須計算出東西的偏移量,還有32位VM與64位VM可能有也可能沒有不同的指針大小,具體取決於打開還是關閉特定的虛擬機設置(另外,我假設數據的所有偏移實際上都是基於指針算術,這會影響到它們)。瞭解如何與TheUnsafe memcpy

不幸的是,似乎所有關於如何使用Theensafe的東西都是源於一篇文章(碰巧是第一篇文章),而其他所有的文章都是從一定程度上覆制粘貼的。其中不多存在,有些不清楚,因爲作者顯然不會說英文。

我的問題是:

我怎樣才能找到偏移字段+指針實例擁有該領域(或字段的字段,或者字段,字段,字段的..使用TheUnsafe

。)

我如何使用它來執行的memcpy到另一個指針+偏移內存地址

考慮到數據可能有幾個GB的大小,並考慮堆提供了數據比對沒有直接控制和它可能肯定是分散的,因爲:

1)我不認爲沒有什麼能夠阻止虛擬機在offset + 10和field2的偏移量sizeof(field1)+ 32處分配field1,是嗎?

2)我還假設GC會移動大塊數據,導致1GB大小的字段有時會被分割。

因此,我所描述的memcpy操作甚至可能嗎?

如果數據由於GC而碎片化,當然堆有一個指向下一塊數據的指針,但是使用上述簡單的過程似乎沒有涵蓋這一點。

所以數據必須堆到這個(也許)工作?如果是這樣,如何使用TheUnsafe分配堆外數據,將這些數據作爲實例的字段工作,並且一旦完成後就釋放分配的內存?

我鼓勵任何不太明白問題的人要求提供他們需要了解的任何細節。

我也敦促人們不要回答,如果他們的整個想法是「把你需要複製的所有對象放在一個數組中,並使用System.arraycopy。我知道在這個美妙的論壇中這是常見的做法,而不是回答所問的問題,提供了一個完整的替代解決方案,原則上,除了完成相同的工作之外,與原始問題無關。

此致敬意。

+0

爲什麼你想這樣做而不是設計語言的設計方式? –

+0

主要是出於好奇(知道可以做什麼和不可以做什麼的限制),還因爲我碰巧正在編寫一款需要與大型聲音文件一起工作並且能夠同時實時處理圖像的軟件使用什麼可能是我的過於複雜的算法,現在,我的概念驗證運行速度低於60fps,我認爲這可能會提高性能。我沒有學術知識,所以我可以做的最好的改進算法是非常基礎的(避免太多的實例化,儘可能內聯的東西,如果可能的話使用位移而不是算術運算等) – FinnTheHuman

+0

你讀過它的javadocs還是看過使用它的代碼,例如在github上還是在jdk本身? – the8472

回答

1

首先一個大的警告:「不安全必須死」http://blog.takipi.com/still-unsafe-the-major-bug-in-java-6-that-turned-into-a-java-9-feature/

一些先決條件

static class DataHolder { 
    int i1; 
    int i2; 
    int i3; 
    DataHolder d1; 
    DataHolder d2; 
    public DataHolder(int i1, int i2, int i3, DataHolder dh) { 
     this.i1 = i1; 
     this.i2 = i2; 
     this.i3 = i3; 
     this.d1 = dh; 
     this.d2 = this; 
    } 
} 

Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); 
theUnsafe.setAccessible(true); 
Unsafe unsafe = (Unsafe) theUnsafe.get(null); 

DataHolder dh1 = new DataHolder(11, 13, 17, null); 
DataHolder dh2 = new DataHolder(23, 29, 31, dh1); 

的基礎

爲了得到一個場(I1)的偏移量,你可以使用以下代碼:

Field fi1 = DataHolder.class.getDeclaredField("i1"); 
long oi1 = unsafe.objectFieldOffset(fi1); 

和訪問實例字段的值DH1你可以寫

System.out.println(unsafe.getInt(dh1, oi1)); // will print 11 

可以使用類似的代碼來訪問一個對象引用(D1):

Field fd1 = DataHolder.class.getDeclaredField("d1"); 
long od1 = unsafe.objectFieldOffset(fd1); 

,你可以用它來獲取基準從DH2 DH1:

System.out.println(dh1 == unsafe.getObject(dh2, od1)); // will print true 

現場訂貨,並對準

要獲得附加賽所有已聲明對象的字段:

for (Field f: DataHolder.class.getDeclaredFields()) { 
    if (!Modifier.isStatic(f.getModifiers())) { 
     System.out.println(f.getName()+" "+unsafe.objectFieldOffset(f)); 
    } 
} 

在我的測試中,似乎JVM按照它認爲合適的順序重新排列字段(即,添加字段可以產生在一次運行完全不同的偏移量)

的本機內存中的對象地址

明白,下面的代碼是要崩潰的JVM遲早是很重要的,因爲垃圾收集器會隨機移動您的物體,而無需控制何時以及爲何發生。

此外,瞭解以下代碼依賴於JVM類型(32位與64位)以及JVM的一些啓動參數(即在64位JVM上使用壓縮的oops)很重要。

在32位虛擬機上,對象的引用與int具有相同的大小。那麼如果你撥打int addr = unsafe.getInt(dh2, od1));而不是unsafe.getObject(dh2, od1)),你會得到什麼?它可能是對象的本機地址嗎?

讓我們試試:

System.out.println(unsafe.getInt(null, unsafe.getInt(dh2, od1)+oi1)); 

會打印出11預期。

在64位虛擬機,而不壓縮糟糕​​(-XX:-UseCompressedOops),您將需要編寫

System.out.println(unsafe.getInt(null, unsafe.getLong(dh2, od1)+oi1)); 

在64位虛擬機與壓縮糟糕(-XX:+ UseCompressedOops),事情有點複雜。該變體具有通過將它們與8L相乘變成64個地址的32位對象的引用:

System.out.println(unsafe.getInt(null, 8L*(0xffffffffL&(dh2, od1)+oi1)); 

什麼是與這些訪問

的問題是垃圾收集器與該代碼一起的問題。垃圾收集器可以隨意移動物體。由於JVM知道它是對象引用(局部變量dh1和dh2,這些對象的字段d1和d2),因此它可以相應地調整這些引用,所以您的代碼將永遠不會注意到。

通過將對象引用提取到int/long變量中,可以將這些對象引用轉換爲碰巧與對象引用具有相同位模式的原始值,但垃圾收集器不知道這些是對象引用(它們可以也是由隨機生成器生成的),因此在移動物體時不會調整這些值。因此,一旦垃圾收集週期被觸發,您提取的地址就不再有效,並且嘗試訪問這些地址處的內存可能會立即導致JVM崩潰(正常情況),或者您可能會在不注意現場的情況下垃圾內存(壞的案件)。

+0

so如果所有這些大塊數據都使用不安全的方式分配到託管堆外部,然後刪除,那麼我不會遇到GC問題。但是,這將如何完成?非安全數據庫是否爲我剛剛分配的數據提供了一個指針?有沒有我可以使用的功能,類似於memcpy,考慮到我有那堆堆地址? – FinnTheHuman

+0

爲了實現你自己的內存管理,有一些函數'allocateMemory'(它返回分配的內存塊的地址),'copyMemory'用於複製數據,'freeMemory'用來釋放內存。參見例如http://www.docjar.com/docs/api/sun/misc/Unsafe.html @FinnTheHuman –