您好,登錄后才能下訂單哦!
并發作為 Java 中非常重要的一部分,其內部大量使用了 Unsafe 類,它為 java.util.concurrent 包中的類提供了底層支持。然而 Unsafe 并不是 JDK 的標準,它是 Sun?的內部實現,存在于 sun.misc 包中,在 Oracle 發行的 JDK 中并不包含其源代碼。
Unsafe 提供兩個功能:
繞過 JVM 直接修改內存(對象)
使用硬件 CPU 指令實現 CAS 原子操作
雖然我們在一般的并發編程中不會直接用到 Unsafe,但是很多 Java 基礎類庫與諸如?Netty、Cassandra 和?Kafka 等高性能庫都采用它,它在提升 Java 運行效率、增強 Java 語言底層操作能力方面起了很大作用。筆者覺得了解一個使用如此廣泛的庫還是很有必要的。本文將深入到 Unsafe 的源碼,分析一下它的邏輯。
本文使用 OpenJDK(jdk8-b120)中 Unsafe 的源碼,Unsafe 的實現是和虛擬機實現相關的,不同的虛擬機實現,它們的對象結構可能不一樣,這個 Unsafe 只能用于 Hotspot 虛擬機。
上源碼
Unsafe 為調用者提供執行非安全操作的能力,由于返回的 Unsafe 對象可以讀寫任意的內存地址數據,調用者應該小心謹慎的使用改對象,一定不用把它傳遞到非信任代碼。該類的大部分方法都是非常底層的操作,并牽涉到一小部分典型的機器都包含的硬件指令,編譯器可以對這些進行優化。
public?final?class?Unsafe?{????private?static?native?void?registerNatives();????static?{ ????????registerNatives(); ????????sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class,?"getUnsafe"); ????}????private?Unsafe()?{}????private?static?final?Unsafe?theUnsafe?=?new?Unsafe();????@CallerSensitive ????public?static?Unsafe?getUnsafe()?{ ????????Class<?>?caller?=?Reflection.getCallerClass();????????if?(!VM.isSystemDomainLoader(caller.getClassLoader()))????????????throw?new?SecurityException("Unsafe");????????return?theUnsafe; ????} ????...... }
上面的代碼包含如下功能:
本地靜態方法:registerNatives(),該方法會在靜態塊中執行
私有構造函數:該類實例是單例的,不能實例化,可以通過 getUnsafe() 方法獲取實例
靜態單例方法:getUnsafe(),獲取實例
靜態塊:包含初始化的注冊功能
要使用此類必須獲得其實例,獲得實例的方法是 getUnsafe(),那就先看看這個方法。
getUnsafe()?方法包含一個注釋 @CallerSensitive,說明該方法不是誰都可以調用的。如果調用者不是由系統類加載器(bootstrap classloader)加載,則將拋出 SecurityException,所以默認情況下,應用代碼調用此方法將拋出異常。我們的代碼要想通過 getUnsafe() 獲取實例是不可能的了,不過可通過反射獲取 Unsafe 實例:
Field?f=?Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); U=?(Unsafe)?f.get(null);
此處通過反射獲取類的靜態字段,這樣就繞過了 getUnsafe() 的安全限制。
也可以通過反射獲取構造方法再實例化,但這樣違法了該類單例的原則,并且在使用上可能會有其它問題,所以不建議這樣做。
?
再來看看如何獲取指定變量的值:
public?native?int?getInt(Object?o,?long?offset);
獲取指定對象中指定偏移量的字段或數組元素的值,參數 o 是變量關聯的 Java 堆對象,如果指定的對象為 null,則獲取內存中該地址的值(即 offset 為內存絕對地址)。
如果不符合以下任意條件,則該方法返回的值是不確定的:
offset 通過 objectFieldOffset 方法獲取的類的某一字段的偏移量,并且關聯的對象 o 是該類的兼容對象(對象 o 所在的類必須是該類或該類的子類)
offset 通過 staticFieldOffset 方法獲取的類的某一字段的偏移量,o 是通過 staticFieldBase 方法獲取的對象
如果 o 引用的是數組,則 offset 的值為 B+N*S,其中 N 是數組的合法下標,B 是通過 arrayBaseOffset 方法從該數組類獲取的,S 是通過 arrayIndexScale 方法從該數組類獲取的
如果以上任意條件符合,則調用者能獲取 Java 字段的引用,但是如果該字段的類型和該方法返回的類型不一致,則結果是不一定的,比如該字段是 short,但調用了 getInt 方法。
該方法通過兩個參數引用一個變量,它為 Java 變量提供 double-register 地址模型。如果引用的對象為 null,則該方法將 offset 當作內存絕對地址,就像 getInt(long)一樣,它為非 Java 變量提供 single-register 地址模型,然而 Java 變量的內存布局可能和非 Java 變量的內存布局不同,不應該假設這兩種地址模型是相等的。同時,應該記住 double-register 地址模型的偏移量不應該和 single-register 地址模型中的地址(long 參數)混淆。
?
再看條件中提到的幾個相關方法:
public?native?long?objectFieldOffset(Field?f);public?native?Object?staticFieldBase(Field?f);public?native?long?staticFieldOffset(Field?f);public?native?int?arrayBaseOffset(Class?arrayClass);public?native?int?arrayIndexScale(Class?arrayClass);
這幾個方法分別是獲取靜態字段、非靜態字段與數組字段的一些信息。
objectFieldOffset
很難想象 JVM 需要使用這么多比特位來編碼非數組對象的偏移量,為了和該類的其它方法保持一致,所以該方法也返回 long 類型。
staticFieldBase
獲取指定靜態字段的位置,和 staticFieldOffset 一起使用。獲取該靜態字段所在的“對象”可通過類似 getInt(Object,long)的方法訪問。
staticFieldOffset
返回給定的字段在該類的偏移地址。對于任何給定的字段,該方法總是返回相同的值,同一個類的不同字段總是返回不同的值。從 1.4.1 開始,字段的偏移以 long 表示,雖然 Sun 的 JVM 只使用了 32 位,但是那些將靜態字段存儲到絕對地址的 JVM 實現需要使用 long 類型的偏移量,通過 getXX(null,long) 獲取字段值,為了保持代碼遷移到 64 位平臺上 JVM 的優良性,必須保持靜態字段偏移量的所有比。
arrayBaseOffset
返回給定數組類第一個元素在內存中的偏移量。如果 arrayIndexScale 方法返回非 0 值,要獲得訪問數組元素的新的偏移量,則需要使用 s。
arrayIndexScale
返回給定數組類的每個元素在內存中的 scale(所占用的字節)。然而對于“narrow”類型的數組,類似 getByte(Object, int)的訪問方法一般不會獲得正確的結果,所以這些類返回的 scale 會是 0。
下邊用代碼解釋:
public?class?MyObj?{????int?objField=10;????static?int?clsField=10;????int[]?array={10,20,30,40,50};????static?Unsafe?U;????static?{????????try?{ ????????????init(); ????????}?catch?(NoSuchFieldException?e)?{ ????????????e.printStackTrace(); ????????}?catch?(IllegalAccessException?e)?{ ????????????e.printStackTrace(); ????????} ????}????public?static?void?init()?throws?NoSuchFieldException,?IllegalAccessException?{ ????????Field?f=?Unsafe.class.getDeclaredField("theUnsafe"); ????????f.setAccessible(true); ????????U=?(Unsafe)?f.get(null); ????} ????...... }
定義一個類包含成員變量 objField、類變量 clsField、成員數組 array 用于實驗。要獲取正確的結果,必須滿足注釋里的三個條件之一:
1、offset 通過 objectFieldOffset 方法獲取的類的某一字段的偏移量,并且關聯的對象 o 是該類的兼容對象(對象 o 所在的類必須是該類或該類的子類)
public?class?MyObjChild?extends?MyObj?{????int?anthor; }
????static?void?getObjFieldVal()?throws?NoSuchFieldException?{ ????????Field?field=MyObj.class.getDeclaredField("objField");????????long?offset=?U.objectFieldOffset(field); ????????MyObj?obj=new?MyObj();????????int?val=?U.getInt(obj,offset); ????????System.out.println("1.\t"+(val==10)); ????????MyObjChild?child=new?MyObjChild();????????int?corVal1=?U.getInt(child,offset); ????????System.out.println("2.\t"+(corVal1==10)); ????????Field?fieldChild=MyObj.class.getDeclaredField("objField");????????long?offsetChild=?U.objectFieldOffset(fieldChild); ????????System.out.println("3.\t"+(offset==offsetChild));????????int?corVal2=?U.getInt(obj,offsetChild); ????????System.out.println("4.\t"+(corVal2==10));????????short?errVal1=U.getShort(obj,offset); ????????System.out.println("5.\t"+(errVal1==10));????????int?errVal2=U.getInt("abcd",offset); ????????System.out.println("6.\t"+errVal2); ????}
輸出結果為:
truetruetruetruetrue-223271518
第一個參數 o 和 offset 都是從 MyObj 獲取的,所以返回 true。
第二個參數 o 是 MyObjChild 的實例,MyObjChild 是 MyObj 的子類,對象 o 是 MyObj 的兼容實例,所以返回 true。這從側面說明在虛擬機中子類的實例的內存結構繼承了父類的實例的內存結構。
第三個比較子類和父類中獲取的字段偏移量是否相同,返回 true 說明是一樣的,既然是一樣的,第四個自然就返回 true。
這里重點說一下第五個,objField 是一個 int 類型,占四個字節,其值為 10,二進制為 00000000 00000000 00000000 00001010。Intel 處理器讀取內存使用的是小端(Little-Endian)模式,在使用 Intel 處理器的機器的內存中多字節類型是按小端存儲的,即低位在內存的低字節存儲,高位在內存的高字節存儲,所以 int 10 在內存中是(offset 0-3)?00001010 00000000 00000000 00000000。使用 getShort 會讀取兩個字節,即 00001010 00000000,獲取的值仍為 10。
但是某些處理器是使用大端(Big-Endian),如 ARM 支持小端和大端,使用此處理器的機器的內存就會按大端存儲多字節類型,與小端相反,此模式下低位在內存的高字節存儲,高位在內存的低字節存儲,所以 int 10 在內存中是(offset 0-3)00000000 00000000 00000000 00001010。在這種情況下,getShort 獲取的值將會是 0。
不同的機器可能產生不一樣的結果,基于此情況,如果字段是 int 類型,但需要一個 short 類型,也不應該調用 getShort,而應該調用 getInt,然后強制轉換成 short。此外,如果調用 getLong,該方法返回的值一定不是 10。就像方法注釋所說,調用該類型方法時,要保證方法的返回值和字段的值是同一種類型。
第五個測試獲取非 MyObj 實例的偏移位置的值,這種情況下代碼本身并不會報錯,但獲取到的值并非該字段的值(未定義的值)
2、offset 通過 staticFieldOffset 方法獲取的類的某一字段的偏移量,o 是通過 staticFieldBase 方法獲取的對象
????static?void?getClsFieldVal()?throws?NoSuchFieldException?{ ????????Field?field=MyObj.class.getDeclaredField("clsField");????????long?offset=?U.staticFieldOffset(field); ????????Object?obj=U.staticFieldBase(field);????????int?val1=U.getInt(MyObj.class,offset); ????????System.out.println("1.\t"+(val1==10));????????int?val2=U.getInt(obj,offset); ????????System.out.println("2.\t"+(val2==10)); ????}
輸出結果:
truetrue
獲取靜態字段的值,有兩個方法:staticFieldBase 獲取字段所在的對象,靜態字段附著于 Class 本身(java.lang.Class 的實例),該方法返回的其實就是該類本身,本例中是 MyObj.class。
3、如果 o 引用的是數組,則 offset 的值為 B+N*S,其中 N 是數組的合法的下標,B 是通過 arrayBaseOffset 方法從該數組類獲取的,S 是通過 arrayIndexScale 方法從該數組類獲取的
????static?void?getArrayVal(int?index,int?expectedVal)?throws?NoSuchFieldException?{????????int?base=U.arrayBaseOffset(int[].class);????????int?scale=U.arrayIndexScale(int[].class); ????????MyObj?obj=new?MyObj(); ????????Field?field=MyObj.class.getDeclaredField("array");????????long?offset=?U.objectFieldOffset(field);????????int[]?array=?(int[])?U.getObject(obj,offset);????????int?val1=U.getInt(array,(long)base+index*scale); ????????System.out.println("1.\t"+(val1==expectedVal));????????int?val2=U.getInt(obj.array,(long)base+index*scale); ????????System.out.println("2.\t"+(val2==expectedVal)); ????}
getArrayVal(2,30);
輸出結果:
truetrue
獲取數組的值以及獲取數組中某下標的值。獲取數組某一下標的偏移量有一個計算公式 B+N*S,B 是數組元素在數組中的基準偏移量,S 是每個元素占用的字節數,N 是數組元素的下標。
有個要注意的地方,上面例子中方法內的數組的 offset 和 base 是兩個完全不同的偏移量,offset 是數組 array 在對象 obj 中的偏移量,base 是數組元素在數組中的基準偏移量,這兩個值沒有任何聯系,不能通過 offset 推導出 base。
getInt 的參數 o 可以是 null,在這種情況下,其和方法 getInt(long) 就是一樣的了,offset 就不是表示相對的偏移地址了,而是表示內存中的絕對地址。操作系統中,一個進程是不能訪問其他進程的內存的,所以傳入 getInt 中的絕對地址必須是當前 JVM 管理的內存地址,否則進程會退出。
?
下一個方法,將值存儲到 Java 變量中:
????public?native?void?putInt(Object?o,?long?offset,?int?x);
前兩個參數會被解釋成 Java 變量(字段或數組)的引用,
參數給定的值會被存儲到該變量,變量的類型必須和方法參數的類型一致
參數 o 是變量關聯的 Java 堆對象,可以為 null
參數 offset 代表該變量在該對象的位置,如果 o 是 null 則是內存的絕對地址
修改指定位置的內存,測試代碼:
????static?void?setObjFieldVal(int?val)?throws?NoSuchFieldException?{ ????????Field?field=MyObj.class.getDeclaredField("objField");????????long?offset=?U.objectFieldOffset(field); ????????MyObj?obj=new?MyObj(); ????????U.putInt(obj,offset,val);????????int?getVal=?U.getInt(obj,offset); ????????System.out.println(val==getVal); ????????U.putLong(obj,offset,val); ????????Field?fieldArray=MyObj.class.getDeclaredField("array");????????long?offsetArray=?U.objectFieldOffset(fieldArray);//????????int[]?array=?(int[])?U.getObject(obj,offsetArray);//????????for(int?i=0;i<array.length;i++){//????????????System.out.println(array[i]);//????????} ????}
objField 是 int 類型,通過 putInt 修改 objField 的值可以正常修改,結果打印 true。然后使用 putLong 修改 objField 的值,這個修改操作本身也不會報錯,但是 objField 并不是 long 類型,這樣修改會導致其它程序錯誤,它不僅修改了 objField 的內存值,還修改了 objField 之后四個字節的內存值。
在這個例子中,objField 后面八個字節存儲的是 array 字段所表示的數組對象的偏移位置,但是被修改了,如果后面的代碼嘗試訪問 array 字段就會出錯。修改其它字段(array、clsField)也是一樣的,只要按之前的方法獲取字段的偏移位置,使用與字段類型一致的 put 方法就可以。
下面的方法都是差不多的,只是針對不同的數據類型或者兼容 1.4 的字節碼:
????public?native?void?putObject(Object?o,?long?offset,?Object?x);????public?native?boolean?getBoolean(Object?o,?long?offset);????public?native?void????putBoolean(Object?o,?long?offset,?boolean?x);????public?native?byte????getByte(Object?o,?long?offset);????public?native?void????putByte(Object?o,?long?offset,?byte?x);????public?native?short???getShort(Object?o,?long?offset);? ????public?native?void????putShort(Object?o,?long?offset,?short?x);????public?native?char????getChar(Object?o,?long?offset);????public?native?void????putChar(Object?o,?long?offset,?char?x);????public?native?long????getLong(Object?o,?long?offset);????public?native?void????putLong(Object?o,?long?offset,?long?x);????public?native?float???getFloat(Object?o,?long?offset);????public?native?void????putFloat(Object?o,?long?offset,?float?x);????public?native?double??getDouble(Object?o,?long?offset);????public?native?void????putDouble(Object?o,?long?offset,?double?x);????@Deprecated ????public?int?getInt(Object?o,?int?offset)?{????????return?getInt(o,?(long)offset); ????}??? ????@Deprecated ????public?void?putInt(Object?o,?int?offset,?int?x)?{ ????????putInt(o,?(long)offset,?x); ????}??? ????@Deprecated ????public?Object?getObject(Object?o,?int?offset)?{????????return?getObject(o,?(long)offset); ????}??? ????@Deprecated ????public?void?putObject(Object?o,?int?offset,?Object?x)?{ ????????putObject(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?boolean?getBoolean(Object?o,?int?offset)?{????????return?getBoolean(o,?(long)offset); ????}??? ????@Deprecated ????public?void?putBoolean(Object?o,?int?offset,?boolean?x)?{ ????????putBoolean(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?byte?getByte(Object?o,?int?offset)?{????????return?getByte(o,?(long)offset); ????}???? ????@Deprecated ????public?void?putByte(Object?o,?int?offset,?byte?x)?{ ????????putByte(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?short?getShort(Object?o,?int?offset)?{????????return?getShort(o,?(long)offset); ????}??? ????@Deprecated ????public?void?putShort(Object?o,?int?offset,?short?x)?{ ????????putShort(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?char?getChar(Object?o,?int?offset)?{????????return?getChar(o,?(long)offset); ????}???? ????@Deprecated ????public?void?putChar(Object?o,?int?offset,?char?x)?{ ????????putChar(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?long?getLong(Object?o,?int?offset)?{????????return?getLong(o,?(long)offset); ????}???? ????@Deprecated ????public?void?putLong(Object?o,?int?offset,?long?x)?{ ????????putLong(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?float?getFloat(Object?o,?int?offset)?{????????return?getFloat(o,?(long)offset); ????}???? ????@Deprecated ????public?void?putFloat(Object?o,?int?offset,?float?x)?{ ????????putFloat(o,?(long)offset,?x); ????}???? ????@Deprecated ????public?double?getDouble(Object?o,?int?offset)?{????????return?getDouble(o,?(long)offset); ????}???? ????@Deprecated ????public?void?putDouble(Object?o,?int?offset,?double?x)?{ ????????putDouble(o,?(long)offset,?x); ????}
下面的方法和上面的也類似,只是這些方法只有一個參數,即內存絕對地址,這些方法不需要 Java 對象地址作為基準地址,所以它們可以作用于本地方法區:
????//獲取內存地址的值,如果地址是?0?或者不是指向通過?allocateMemory?方法獲取的內存塊,則結果是未知的 ???? ????public?native?byte????getByte(long?address);????//將一個值寫入內存,如果地址是?0?或者不是指向通過?allocateMemory?方法獲取的內存塊,則結果是未知的 ????public?native?void????putByte(long?address,?byte?x);????public?native?short???getShort(long?address);????public?native?void????putShort(long?address,?short?x);????public?native?char????getChar(long?address);????public?native?void????putChar(long?address,?char?x);????public?native?int?????getInt(long?address);????public?native?void????putInt(long?address,?int?x);????public?native?long????getLong(long?address);? ????public?native?void????putLong(long?address,?long?x);????public?native?float???getFloat(long?address);? ????public?native?void????putFloat(long?address,?float?x);?? ????public?native?double??getDouble(long?address);?? ????public?native?void????putDouble(long?address,?double?x);
這里提到一個方法 allocateMemory,它是用于分配本地內存的。看看和本地內存有關的幾個方法:
????///包裝malloc,realloc,free ????/** ?????*?分配指定大小的一塊本地內存。分配的這塊內存不會初始化,它們的內容通常是沒用的數據 ?????*?返回的本地指針不會是?0,并且該內存塊是連續的。調用?freeMemory?方法可以釋放此內存,調用 ?????*?reallocateMemory?方法可以重新分配 ?????*/ ????public?native?long?allocateMemory(long?bytes);????/** ?????*?重新分配一塊指定大小的本地內存,超出老內存塊的字節不會被初始化,它們的內容通常是沒用的數據 ?????*?當且僅當請求的大小為?0?時,該方法返回的本地指針會是?0。 ?????*?該內存塊是連續的。調用?freeMemory?方法可以釋放此內存,調用?reallocateMemory?方法可以重新分配 ?????*?參數?address?可以是?null,這種情況下會分配新內存(和?allocateMemory?一樣) ?????*/ ????public?native?long?reallocateMemory(long?address,?long?bytes);????/**? ?????*?將給定的內存塊的所有字節設置成固定的值(通常是?0) ?????*?該方法通過兩個參數確定內存塊的基準地址,就像在?getInt(Object,long)?中討論的,它提供了?double-register?地址模型 ?????*?如果引用的對象是?null,?則?offset?會被當成絕對基準地址 ?????*?該寫入操作是按單元寫入的,單元的字節大小由地址和長度參數決定,每個單元的寫入是原子性的。如果地址和長度都是?8?的倍數,則一個單元為?long ?????*?型(一個單元?8?個字節);如果地址和長度都是?4?的倍數,則一個單元為?int?型(一個單元?4?個字節); ?????*?如果地址和長度都是?2?的倍數,則一個單元為?short?型(一個單元?2?個字節); ?????*/ ????public?native?void?setMemory(Object?o,?long?offset,?long?bytes,?byte?value);????//將給定的內存塊的所有字節設置成固定的值(通常是?0) ????//就像在?getInt(Object,long)?中討論的,該方法提供?single-register?地址模型 ????public?void?setMemory(long?address,?long?bytes,?byte?value)?{ ????????setMemory(null,?address,?bytes,?value); ????}????//復制指定內存塊的字節到另一內存塊 ????//該方法的兩個基準地址分別由兩個參數決定 ????public?native?void?copyMemory(Object?srcBase,?long?srcOffset, ??????????????????????????????????Object?destBase,?long?destOffset,??????????????????????????????????long?bytes);????//復制指定內存塊的字節到另一內存塊,但使用?single-register?地址模型 ????? ????public?void?copyMemory(long?srcAddress,?long?destAddress,?long?bytes)?{ ????????copyMemory(null,?srcAddress,?null,?destAddress,?bytes); ????}????//釋放通過?allocateMemory?或者?reallocateMemory?獲取的內存,如果參數?address?是?null,則不做任何處理 ????public?native?void?freeMemory(long?address);
allocateMemory、reallocateMemory、freeMemory 與 setMemory 分別是對 C 函數 malloc、realloc、free 和 memset 的封裝,這樣該類就提供了動態獲取/釋放本地方法區內存的功能。
malloc 用于分配一個全新的未使用的連續內存,但該內存不會初始化,即不會被清零;
realloc 用于內存的縮容或擴容,有兩個參數,從 malloc 返回的地址和要調整的大小,該函數和 malloc 一樣,不會初始化,它能保留之前放到內存里的值,很適合用于擴容;
free 用于釋放內存,該方法只有一個地址參數,那它如何知道要釋放多少個字節呢?其實在 malloc 分配內存的時候會多分配 4 個字節用于存放該塊的長度,比如 malloc(10) 其實會花費 14 個字節。理論上講能分配的最大內存是 4G(2^32-1)。在 hotspot 虛擬機的設計中,數組對象也有 4 個字節用于存放數組長度,那么在 hotspot 中,數組的最大長度就是 2^32-1,這樣 free 函數只要讀取前 4 個字節就知道要釋放多少內存了(10+4);
memset? 一般用于初始化內存,可以設置初始化內存的值,一般初始值會設置成 0,即清零操作。
來一個簡單的例子,申請內存-寫入內存-讀取內存-釋放內存:
long?address=U.allocateMemory(10); U.setMemory(address,10,(byte)1);/** ?*?1的二進制碼為00000001,int為四個字節,U.getInt將讀取四個字節, ?*?讀取的字節為00000001?00000001?00000001?00000001 ?*/int?i=0b00000001000000010000000100000001; System.out.println(i==U.getInt(address)); U.freeMemory(address);
?
接下來看看獲取類變量相關信息的幾個方法:
????///?random?queries ????///?隨機搜索,對象是放在一塊連續的內存空間中,所以是支持隨機搜索的 ????/** ?????*?staticFieldOffset,objectFieldOffset,arrayBaseOffset?方法的返回值不會是該常量(-1) ?????*/ ????public?static?final?int?INVALID_FIELD_OFFSET???=?-1;????/** ?????*?返回字段的偏移量,32?字節 ?????*?從?1.4.1?開始,對于靜態字段,請使用?staticFieldOffset?方法,非靜態字段使用?objectFieldOffset?方法獲取 ?????*/ ????@Deprecated ????public?int?fieldOffset(Field?f)?{????????if?(Modifier.isStatic(f.getModifiers()))????????????return?(int)?staticFieldOffset(f);????????else ????????????return?(int)?objectFieldOffset(f); ????}????/** ?????*?返回用于訪問靜態字段的基準地址 ?????*?從?1.4.1?開始,要獲取訪問指定字段的基準地址,請使用?staticFieldBase(Field) ?????*?該方法僅能作用于把所有靜態字段放在一起的?JVM?實現 ?????*/ ????@Deprecated ????public?Object?staticFieldBase(Class<?>?c)?{ ????????Field[]?fields?=?c.getDeclaredFields();????????for?(int?i?=?0;?i?<?fields.length;?i++)?{????????????if?(Modifier.isStatic(fields[i].getModifiers()))?{????????????????return?staticFieldBase(fields[i]); ????????????} ????????}????????return?null; ????}????/** ?????*?返回給定的字段在該類的偏移地址 ?????* ?????*?對于任何給定的字段,該方法總是返回相同的值;同一個類的不同字段總是返回不同的值 ?????* ?????*?從?1.4.1?開始,字段的偏移以?long?表示,雖然?Sun?的?JVM?只使用了?32?位,但是那些將靜態字段存儲到絕對地址的?JVM?實現 ?????*?需要使用?long?類型的偏移量,通過?getXX(null,long)?獲取字段值,為了保持代碼遷移到?64?位平臺上?JVM?的優良性, ?????*?必須保持靜態字段偏移量的所有比特位 ?????*/ ????public?native?long?staticFieldOffset(Field?f);????/** ?????*?很難想象?JVM?需要使用這么多比特位來編碼非數組對象的偏移量,它們只需要很少的比特位就可以了(有誰看過有100個成員變量的類么? ?????*?一個字節能表示?256?個成員變量), ?????*?為了和該類的其他方法保持一致,所以該方法也返回?long?類型 ?????* ?????*/ ????public?native?long?objectFieldOffset(Field?f);????/** ?????*?獲取指定靜態字段的位置,和?staticFieldOffset?一起使用 ?????*?獲取該靜態字段所在的"對象",這個"對象"可通過類似?getInt(Object,long)?的方法訪問 ?????*?該"對象"可能是?null,并且引用的可能是對象的"cookie"(此處cookie具體含義未知,沒有找到相關資料),不保證是真正的對象,該"對象"只能當作此類中?put?和?get?方法的參數, ?????*?其他情況下不應該使用它 ?????*/ ????public?native?Object?staticFieldBase(Field?f);????/** ?????*?檢查給定的類是否需要初始化,它通常和?staticFieldBase?方法一起使用 ?????*?只有當?ensureClassInitialized?方法不產生任何影響時才會返回?false ?????*/ ????public?native?boolean?shouldBeInitialized(Class<?>?c);????/** ?????*?確保給定的類已被初始化,它通常和?staticFieldBase?方法一起使用 ?????*/ ????public?native?void?ensureClassInitialized(Class<?>?c);????/** ?????*?返回給定數組類第一個元素在內存中的偏移量,如果?arrayIndexScale?方法返回非0值,要獲得訪問數組元素的新的偏移量, ?????*?需要使用?scale ?????*/ ????public?native?int?arrayBaseOffset(Class<?>?arrayClass);????/**?The?value?of?{@code?arrayBaseOffset(boolean[].class)}?*/ ????public?static?final?int?ARRAY_BOOLEAN_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(boolean[].class);????/**?The?value?of?{@code?arrayBaseOffset(byte[].class)}?*/ ????public?static?final?int?ARRAY_BYTE_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(byte[].class);????/**?The?value?of?{@code?arrayBaseOffset(short[].class)}?*/ ????public?static?final?int?ARRAY_SHORT_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(short[].class);????/**?The?value?of?{@code?arrayBaseOffset(char[].class)}?*/ ????public?static?final?int?ARRAY_CHAR_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(char[].class);????/**?The?value?of?{@code?arrayBaseOffset(int[].class)}?*/ ????public?static?final?int?ARRAY_INT_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(int[].class);????/**?The?value?of?{@code?arrayBaseOffset(long[].class)}?*/ ????public?static?final?int?ARRAY_LONG_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(long[].class);????/**?The?value?of?{@code?arrayBaseOffset(float[].class)}?*/ ????public?static?final?int?ARRAY_FLOAT_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(float[].class);????/**?The?value?of?{@code?arrayBaseOffset(double[].class)}?*/ ????public?static?final?int?ARRAY_DOUBLE_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(double[].class);????/**?The?value?of?{@code?arrayBaseOffset(Object[].class)}?*/ ????public?static?final?int?ARRAY_OBJECT_BASE_OFFSET ????????????=?theUnsafe.arrayBaseOffset(Object[].class);????/** ?????*?返回給定數組類的每個元素在內存中的?scale(所占用的字節)。然而對于"narrow"類型的數組,類似?getByte(Object,?int)?的訪問方法 ?????*?一般不會獲得正確的結果,所以這些類返回的?scale?會是?0 ?????*?(本人水平有限,此處narrow類型不知道具體含義,不了解什么時候此方法會返回0) ?????*/ ????public?native?int?arrayIndexScale(Class<?>?arrayClass);????/**?The?value?of?{@code?arrayIndexScale(boolean[].class)}?*/ ????public?static?final?int?ARRAY_BOOLEAN_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(boolean[].class);????/**?The?value?of?{@code?arrayIndexScale(byte[].class)}?*/ ????public?static?final?int?ARRAY_BYTE_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(byte[].class);????/**?The?value?of?{@code?arrayIndexScale(short[].class)}?*/ ????public?static?final?int?ARRAY_SHORT_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(short[].class);????/**?The?value?of?{@code?arrayIndexScale(char[].class)}?*/ ????public?static?final?int?ARRAY_CHAR_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(char[].class);????/**?The?value?of?{@code?arrayIndexScale(int[].class)}?*/ ????public?static?final?int?ARRAY_INT_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(int[].class);????/**?The?value?of?{@code?arrayIndexScale(long[].class)}?*/ ????public?static?final?int?ARRAY_LONG_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(long[].class);????/**?The?value?of?{@code?arrayIndexScale(float[].class)}?*/ ????public?static?final?int?ARRAY_FLOAT_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(float[].class);????/**?The?value?of?{@code?arrayIndexScale(double[].class)}?*/ ????public?static?final?int?ARRAY_DOUBLE_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(double[].class);????/**?The?value?of?{@code?arrayIndexScale(Object[].class)}?*/ ????public?static?final?int?ARRAY_OBJECT_INDEX_SCALE ????????????=?theUnsafe.arrayIndexScale(Object[].class);
上面的一些方法之前已經提到過,注釋也說的比較明白, 說一下 shouldBeInitialized 和 ensureClassInitialized,shouldBeInitialized 判斷類是否已初始化,ensureClassInitialized 執行初始化。有個概念需要了解,虛擬機加載類包括加載和鏈接階段,加載階段只是把類加載進內存,鏈接階段會驗證加載的代碼的合法性,并初始化靜態字段和靜態塊;shouldBeInitialized 就是檢查鏈接階段有沒有執行。
????static?void?clsInitialized()?throws?NoSuchFieldException?{ ????????System.out.println(U.shouldBeInitialized(MyObj.class)); ????????System.out.println(U.shouldBeInitialized(MyObjChild.class)); ????????U.ensureClassInitialized(MyObjChild.class); ????????System.out.println(U.shouldBeInitialized(MyObjChild.class)); ????}
public?class?MyObjChild?extends?MyObj?{????static?int?f1=1;????final?static?int?f2=1;????static?{ ????????f1=2; ????????System.out.println("MyObjChild?init"); ????} }
輸出:
false?true?MyObjChild?init? false
第一行輸出 false 是因為我這個代碼(包括 main 方法)是在 MyObj 類里寫的,執行 main 的時候,MyObj 已經加載并初始化了。調用 U.shouldBeInitialized(MyObjChild.class) 只會加載 MyObjChild.class,但不會初始化,執行 ensureClassInitialized 才會初始化。
????static?void?clsInitialized2()?throws?NoSuchFieldException?{ ????????Field?f1=MyObjChild.class.getDeclaredField("f1"); ????????Field?f2=MyObjChild.class.getDeclaredField("f2");????????long?f1Offset=?U.staticFieldOffset(f1);????????long?f2Offset=?U.staticFieldOffset(f2);????????int?f1Val=U.getInt(MyObjChild.class,f1Offset);????????int?f2Val=U.getInt(MyObjChild.class,f2Offset); ????????System.out.println("1.\t"+(f1Val==0)); ????????System.out.println("2.\t"+(f2Val==1)); ????????U.ensureClassInitialized(MyObjChild.class); ????????f1Val=U.getInt(MyObjChild.class,f1Offset); ????????System.out.println("3.\t"+(f1Val==2)); ????}
輸出:
1.true2.true?MyObjChild?init3.true
f1 是 static int,f2 是 final static int,因為 f2 是 final,它的值在編譯期就決定了,存放在類的常量表里,所以即使還沒有初始化它的值就是 1。
?
????/** ?????*?獲取本地指針所占用的字節大小,值為?4?或者?8。其他基本類型的大小由其內容決定 ?????*/ ????public?native?int?addressSize();????/**?The?value?of?{@code?addressSize()}?*/ ????public?static?final?int?ADDRESS_SIZE?=?theUnsafe.addressSize();????/** ?????*?本地內存頁大小,值為?2?的?N?次方 ?????*/ ????public?native?int?pageSize();
addressSize 返回指針的大小,32 位虛擬機返回 4,64 位虛擬機默認返回 8,開啟指針壓縮功能(-XX:-UseCompressedOops)則返回 4。基本類型不是用指針表示的,它是直接存儲的值。一般情況下,我們會說在 Java 中,基本類型是值傳遞,對象是引用傳遞。Java 官方的表述是在任何情況下 Java 都是值傳遞。基本類型是傳遞值本身,對象類型是傳遞指針的值。
?
????///?random?trusted?operations?from?JNI: ????///?JNI信任的操作 ????/** ?????*?告訴虛擬機定義一個類,加載類不做安全檢查,默認情況下,參數類加載器(ClassLoader)和保護域(ProtectionDomain)來自調用者類 ?????*/ ????public?native?Class<?>?defineClass(String?name,?byte[]?b,?int?off,?int?len, ???????????????????????????????????????ClassLoader?loader, ???????????????????????????????????????ProtectionDomain?protectionDomain);????/** ?????*?定義一個匿名類,這里說的和我們代碼里寫的匿名內部類不是一個東西。 ?????*?(可以參考知乎上的一個問答?https://www.zhihu.com/question/51132462) ?????*/ ????public?native?Class<?>?defineAnonymousClass(Class<?>?hostClass,?byte[]?data,?Object[]?cpPatches);????/**? ?????*?分配實例的內存空間,但不會執行構造函數。如果沒有執行初始化,則會執行初始化 ?????*/ ????public?native?Object?allocateInstance(Class<?>?cls) ????????????throws?InstantiationException;????/**?Lock?the?object.??It?must?get?unlocked?via?{@link?#monitorExit}. ?????* ?????*?獲取對象內置鎖(即?synchronized?關鍵字獲取的鎖),必須通過?monitorExit?方法釋放鎖 ?????*?(synchronized?代碼塊在編譯后會產生兩個指令:monitorenter,monitorexit) ?????*/ ????public?native?void?monitorEnter(Object?o);????/** ?????*?Unlock?the?object.??It?must?have?been?locked?via?{@link ?????*?#monitorEnter}. ?????*?釋放鎖 ?????*/ ????public?native?void?monitorExit(Object?o);????/** ?????*?嘗試獲取對象內置鎖,通過返回?true?和?false?表示是否成功獲取鎖 ?????*/ ????public?native?boolean?tryMonitorEnter(Object?o);????/**?Throw?the?exception?without?telling?the?verifier. ?????*?不通知驗證器(verifier)直接拋出異常(此處?verifier?具體含義未知,沒有找到相關資料) ?????*/ ????public?native?void?throwException(Throwable?ee);
allocateInstance 方法的測試
public?class?MyObjChild?extends?MyObj?{????static?int?f1=1;????int?f2=1;????static?{ ????????f1=2; ????????System.out.println("MyObjChild?init"); ????}????public?MyObjChild(){ ????????f2=2; ????????System.out.println("run?construct"); ????} }
???static?void?clsInitialized3()?throws?InstantiationException?{ ????????MyObjChild?myObj=?(MyObjChild)?U.allocateInstance(MyObjChild.class); ????????System.out.println("1.\t"+(MyObjChild.f1==2)); ????????System.out.println("1.\t"+(myObj.f2==0)); ????}
輸出:
MyObjChild?init1.true2.true
可以看到分配對象的時候只執行了類的初始化代碼,沒有執行構造函數。
?
來看看最重要的 CAS 方法
????/** ?????*?Atomically?update?Java?variable?to?<tt>x</tt>?if?it?is?currently ?????*?holding?<tt>expected</tt>. ?????*?@return?<tt>true</tt>?if?successful ?????* ?????*?如果變量的值為預期值,則更新變量的值,該操作為原子操作 ?????*?如果修改成功則返回true ?????*/ ????public?final?native?boolean?compareAndSwapObject(Object?o,?long?offset, ?????????????????????????????????????????????????????Object?expected, ?????????????????????????????????????????????????????Object?x);????/** ?????*?Atomically?update?Java?variable?to?<tt>x</tt>?if?it?is?currently ?????*?holding?<tt>expected</tt>. ?????*?@return?<tt>true</tt>?if?successful ?????*/ ????public?final?native?boolean?compareAndSwapInt(Object?o,?long?offset,??????????????????????????????????????????????????int?expected,??????????????????????????????????????????????????int?x);????/** ?????*?Atomically?update?Java?variable?to?<tt>x</tt>?if?it?is?currently ?????*?holding?<tt>expected</tt>. ?????*?@return?<tt>true</tt>?if?successful ?????*/ ????public?final?native?boolean?compareAndSwapLong(Object?o,?long?offset,???????????????????????????????????????????????????long?expected,???????????????????????????????????????????????????long?x);
這幾個方法應該是最常用的方法了,用于實現原子性的 CAS 操作,這些操作可以避免加鎖,一般情況下,性能會更好, java.util.concurrent 包下很多類就是用的這些 CAS 操作而沒有用鎖。
????static?void?cas()?throws?NoSuchFieldException?{ ????????Field?field=MyObj.class.getDeclaredField("objField");????????long?offset=?U.objectFieldOffset(field); ????????MyObj?myObj=new?MyObj(); ????????myObj.objField=1; ????????U.compareAndSwapInt(myObj,offset,0,2); ????????System.out.println("1.\t"+(myObj.objField==2)); ????????U.compareAndSwapInt(myObj,offset,1,2); ????????System.out.println("2.\t"+(myObj.objField==2)); ????}
輸出:
1.false2.true
?
????/** ?????*?獲取給定變量的引用值,該操作有?volatile?加載語意,其他方面和?getObject(Object,?long)?一樣 ?????*/ ????public?native?Object?getObjectVolatile(Object?o,?long?offset);????/** ?????*?將引用值寫入給定的變量,該操作有?volatile?加載語意,其他方面和?putObject(Object,?long,?Object)?一樣 ?????*/ ????public?native?void????putObjectVolatile(Object?o,?long?offset,?Object?x);????/**?Volatile?version?of?{@link?#getInt(Object,?long)}??*/ ????public?native?int?????getIntVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putInt(Object,?long,?int)}??*/ ????public?native?void????putIntVolatile(Object?o,?long?offset,?int?x);????/**?Volatile?version?of?{@link?#getBoolean(Object,?long)}??*/ ????public?native?boolean?getBooleanVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putBoolean(Object,?long,?boolean)}??*/ ????public?native?void????putBooleanVolatile(Object?o,?long?offset,?boolean?x);????/**?Volatile?version?of?{@link?#getByte(Object,?long)}??*/ ????public?native?byte????getByteVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putByte(Object,?long,?byte)}??*/ ????public?native?void????putByteVolatile(Object?o,?long?offset,?byte?x);????/**?Volatile?version?of?{@link?#getShort(Object,?long)}??*/ ????public?native?short???getShortVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putShort(Object,?long,?short)}??*/ ????public?native?void????putShortVolatile(Object?o,?long?offset,?short?x);????/**?Volatile?version?of?{@link?#getChar(Object,?long)}??*/ ????public?native?char????getCharVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putChar(Object,?long,?char)}??*/ ????public?native?void????putCharVolatile(Object?o,?long?offset,?char?x);????/**?Volatile?version?of?{@link?#getLong(Object,?long)}??*/ ????public?native?long????getLongVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putLong(Object,?long,?long)}??*/ ????public?native?void????putLongVolatile(Object?o,?long?offset,?long?x);????/**?Volatile?version?of?{@link?#getFloat(Object,?long)}??*/ ????public?native?float???getFloatVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putFloat(Object,?long,?float)}??*/ ????public?native?void????putFloatVolatile(Object?o,?long?offset,?float?x);????/**?Volatile?version?of?{@link?#getDouble(Object,?long)}??*/ ????public?native?double??getDoubleVolatile(Object?o,?long?offset);????/**?Volatile?version?of?{@link?#putDouble(Object,?long,?double)}??*/ ????public?native?void????putDoubleVolatile(Object?o,?long?offset,?double?x);
這是具有 volatile 語意的 get 和 put方法。volatile 語意為保證不同線程之間的可見行,即一個線程修改一個變量之后,保證另一線程能觀測到此修改。這些方法可以使非 volatile 變量具有 volatile 語意。
?
????/** ?????*?putObjectVolatile(Object,?long,?Object)的另一個版本(有序的/延遲的),它不保證其他線程能立即看到修改, ?????*?該方法通常只對底層為?volatile?的變量(或者?volatile?類型的數組元素)有幫助 ?????*/ ????public?native?void????putOrderedObject(Object?o,?long?offset,?Object?x);????/**?Ordered/Lazy?version?of?{@link?#putIntVolatile(Object,?long,?int)}??*/ ????public?native?void????putOrderedInt(Object?o,?long?offset,?int?x);????/**?Ordered/Lazy?version?of?{@link?#putLongVolatile(Object,?long,?long)}?*/ ????public?native?void????putOrderedLong(Object?o,?long?offset,?long?x);
有三類很相近的方法:putXx、putXxVolatile 與 putOrderedXx:
putXx 只是寫本線程緩存,不會將其它線程緩存置為失效,所以不能保證其它線程一定看到此次修改;
putXxVolatile 相反,它可以保證其它線程一定看到此次修改;
putOrderedXx 也不保證其它線程一定看到此次修改,但和 putXx 又有區別,它的注釋上有兩個關鍵字:順序性(Ordered)和延遲性(lazy),順序性是指不會發生重排序,延遲性是指其它線程不會立即看到此次修改,只有當調用 putXxVolatile 使才能看到。
?
????/** ?????*?釋放當前阻塞的線程。如果當前線程沒有阻塞,則下一次調用?park?不會阻塞。這個操作是"非安全"的 ?????*?是因為調用者必須通過某種方式保證該線程沒有被銷毀 ?????* ?????*/ ????public?native?void?unpark(Object?thread);????/** ?????*?阻塞當前線程,當發生如下情況時返回: ?????*?1、調用?unpark?方法 ?????*?2、線程被中斷 ?????*?3、時間過期 ?????*?4、spuriously ?????*?該操作放在?Unsafe?類里沒有其它意義,它可以放在其它的任何地方 ?????*/ ????public?native?void?park(boolean?isAbsolute,?long?time);
阻塞和釋放當前線程,java.util.concurrent 中的鎖就是通過這兩個方法實現線程阻塞和釋放的。
?
????/** ?????*獲取一段時間內,運行的任務隊列分配到可用處理器的平均數(平常說的?CPU?使用率) ?????* ?????*/ ????public?native?int?getLoadAverage(double[]?loadavg,?int?nelems);
統計 CPU 負載。
?
????//?The?following?contain?CAS-based?Java?implementations?used?on ????//?platforms?not?supporting?native?instructions ????//下面的方法包含基于?CAS?的?Java?實現,用于不支持本地指令的平臺 ????/** ?????*?在給定的字段或數組元素的當前值原子性的增加給定的值 ?????*?@param?o?字段/元素所在的對象/數組 ?????*?@param?offset?字段/元素的偏移 ?????*?@param?delta?需要增加的值 ?????*?@return?原值 ?????*?@since?1.8 ?????*/ ????public?final?int?getAndAddInt(Object?o,?long?offset,?int?delta)?{????????int?v;????????do?{ ????????????v?=?getIntVolatile(o,?offset); ????????}?while?(!compareAndSwapInt(o,?offset,?v,?v?+?delta));????????return?v; ????}????public?final?long?getAndAddLong(Object?o,?long?offset,?long?delta)?{????????long?v;????????do?{ ????????????v?=?getLongVolatile(o,?offset); ????????}?while?(!compareAndSwapLong(o,?offset,?v,?v?+?delta));????????return?v; ????}????/** ?????*?將給定的字段或數組元素的當前值原子性的替換給定的值 ?????*?@param?o?字段/元素所在的對象/數組 ?????*?@param?offset?field/element?offset ?????*?@param?newValue?新值 ?????*?@return?原值 ?????*?@since?1.8 ?????*/ ????public?final?int?getAndSetInt(Object?o,?long?offset,?int?newValue)?{????????int?v;????????do?{ ????????????v?=?getIntVolatile(o,?offset); ????????}?while?(!compareAndSwapInt(o,?offset,?v,?newValue));????????return?v; ????}????public?final?long?getAndSetLong(Object?o,?long?offset,?long?newValue)?{????????long?v;????????do?{ ????????????v?=?getLongVolatile(o,?offset); ????????}?while?(!compareAndSwapLong(o,?offset,?v,?newValue));????????return?v; ????}????public?final?Object?getAndSetObject(Object?o,?long?offset,?Object?newValue)?{ ????????Object?v;????????do?{ ????????????v?=?getObjectVolatile(o,?offset); ????????}?while?(!compareAndSwapObject(o,?offset,?v,?newValue));????????return?v; ????}
基于 CAS 的一些原子操作實現,也是比較常用的方法。
?
????//確保該欄桿前的讀操作不會和欄桿后的讀寫操作發生重排序 ????public?native?void?loadFence();????//確保該欄桿前的寫操作不會和欄桿后的讀寫操作發生重排序 ????public?native?void?storeFence();????//確保該欄桿前的讀寫操作不會和欄桿后的讀寫操作發生重排序 ????public?native?void?fullFence();????//拋出非法訪問錯誤,僅用于VM內部 ????private?static?void?throwIllegalAccessError()?{????????throw?new?IllegalAccessError(); ????}
這是實現內存屏障的幾個方法,類似于 volatile 的語意,保證內存可見性和禁止重排序。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。