現代 JVM 的垃圾回收裡面還有「引用計數」嗎?
如果有引用計數,那它和其他垃圾回收演算法之間如何協同的?java中大部分垃圾是被引用=0回收掉的呢?還是各種標記掃描出來的?還是標記掃描只主要是解決循環引用,其他的都是引用歸零回收了?如果沒有引用計數,那麼 jni.h裡面:
如果返回 true,那著兩行可能是內存拷貝和釋放實現的。而如果返回 isCopy = false,同時用完還需要 ReleaseStringChars,這樣做是為了防止外部 C模塊在訪問 jvm內部對象的時候對象被回收?還是其他?它內部是引用來實現的么?如果 JVM完全不用引用計數了,那什麼時候 isCopy會等於 false呢?如果用了引用計數,那為啥還要 Copy出來降低性能呢?這個 isCopy參數留在這裡,有什麼考慮呢?
const jchar *(JNICALL *GetStringChars) (JNIEnv *env, jstring str, jboolean *isCopy);
void (JNICALL *ReleaseStringChars) (JNIEnv *env, jstring str, const jchar *chars);
所有主流 JVM實現都是統一的么?如果java架構比較大型項目,一個對象不再需要使用的時候,人為手工的接除改對象對外部的各種引用是否對垃圾回收有所幫助?還是由於沒有引用計數了 java層也用不著去刻意解除引用,後期的標記掃描會自動找出來?
首先,最初設計JVM的時候就從來沒有假設過要用引用計數來實現JVM管理Java對象的的GC。JNI的設計自然也不會假設引用計數。Sun -&> Oracle所實現的JVM沒有一個是用引用計數來實現JVM的GC的。其它能稱得上「主流」的JVM也都無一使用過引用計數來實現GC。請看傳送門:目前主流的 Java 虛擬機有哪些? - RednaxelaFX 的回答 其中只有研究專用的Jikes RVM在某些研究項目里有過引用計數的實現。其次,JNI里的GetStringChars與ReleaseStringChars是幹嘛的。
GetStringChars
const jchar * GetStringChars(JNIEnv *env, jstring string,
jboolean *isCopy);
Returns a pointer to the array of Unicode characters of the string. This pointer is valid until ReleaseStringchars() is called.
If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made; or it is set to JNI_FALSE if no copy is made.
LINKAGE:Index 165 in the JNIEnv interface function table.PARAMETERS:env: the JNI interface pointer.
string: a Java string object.
isCopy: a pointer to a boolean.
RETURNS:Returns a pointer to a Unicode string, or NULL if the operation fails.
ReleaseStringChars
void ReleaseStringChars(JNIEnv *env, jstring string,
const jchar *chars);
Informs the VM that the native code no longer needs access to chars. The chars argument is a pointer obtained from string using GetStringChars().
LINKAGE: Index 166 in the JNIEnv interface function table.PARAMETERS:env: the JNI interface pointer.
string: a Java string object.
chars: a pointer to a Unicode string.
GetStringChars()參數里的isCopy是一個「傳出參數」(out parameter),用於告知調用者JVM在執行這個函數時是否有對字元串內容進行拷貝。
然後看看Oracle JDK8u的HotSpot VM里GetStringChars()的實現:jdk8u/jdk8u/hotspot: 3fa5c654c143 src/share/vm/prims/jni.cpp
JNI_QUICK_ENTRY(const jchar*, jni_GetStringChars(
JNIEnv *env, jstring string, jboolean *isCopy))
JNIWrapper("GetStringChars");
jchar* buf = NULL;
oop s = JNIHandles::resolve_non_null(string);
typeArrayOop s_value = java_lang_String::value(s);
if (s_value != NULL) {
int s_len = java_lang_String::length(s);
int s_offset = java_lang_String::offset(s);
buf = NEW_C_HEAP_ARRAY_RETURN_NULL(jchar, s_len + 1, mtInternal); // add one for zero termination
/* JNI Specification states return NULL on OOM */
if (buf != NULL) {
if (s_len &> 0) {
memcpy(buf, s_value-&>char_at_addr(s_offset), sizeof(jchar)*s_len);
}
buf[s_len] = 0;
//%note jni_5
if (isCopy != NULL) {
*isCopy = JNI_TRUE;
}
}
}
return buf;
JNI_END
也就是說,HotSpot VM在這個函數里,只要看到非null的字元串都會做拷貝,所以正常情況下isCopy傳出的結果總是JNI_TRUE。只有兩種情況它會是JNI_FALSE:
- 傳入的string為NULL,或
- 無法申請到足夠內存空間來做拷貝。此時主返回值會是NULL。
JNI_QUICK_ENTRY(void, jni_ReleaseStringChars(JNIEnv *env, jstring str, const jchar *chars))
JNIWrapper("ReleaseStringChars");
//%note jni_6
if (chars != NULL) {
// Since String objects are supposed to be immutable, don"t copy any
// new data back. A bad user will have to go after the char array.
FreeHeap((void*) chars);
}
JNI_END
GetStringCritical
ReleaseStringCritical
const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);
The semantics of these two functions are similar to the existing Get/ReleaseStringChars functions. If possible, the VM returns a pointer to string elements; otherwise, a copy is made. However, there are significant restrictions on how these functions can be used. In a code segment enclosed by Get/ReleaseStringCritical calls, the native code must not issue arbitrary JNI calls, or cause the current thread to block.
The restrictions on Get/ReleaseStringCritical are similar to those on Get/ReleasePrimitiveArrayCritical.
LINKAGE (GetStringCritical):Index 224 in the JNIEnv interface function table.LINKAGE (ReleaseStingCritical):Index 225 in the JNIEnv interface function table.SINCE:JDK/JRE 1.2
GetStringCritical()的實現方式在各JVM里也不一樣。HotSpot VM的話會通過阻止GC發生來保證對象不發生移動;JRockit VM的話,默認是可以對對象單獨做pinning,即便執行GC也可以保持特定對象不被移動。
=================================================================題主要是對某些其它JVM實現在這部分的細節感興趣的話,回頭可以補充點信息到這個回答里。這個isCopy參數自然有它的黑歷史…只是跟題主所想像的不一樣。所有主流 JVM實現都是統一的么?
嗯那當然不是咯。每個JVM實現都有各自不同的取捨,就算同樣的演算法在具體實現中都會有不少差異。
如果Java架構比較大型項目,一個對象不再需要使用的時候,人為手工的解除該對象對外部的各種引用是否對垃圾回收有所幫助?
跟大不大型沒關係。只跟具體用法有關係。
通常,人工解除Java對象之間的引用關係是沒有必要的。GC的工作就是把沒有活引用的對象都收集掉。但有些時候出問題是應該要解除引用但卻沒有解除的。典型情況是在復用線程池裡的線程的情況下忘記清理ThreadLocal的內容——線程池裡的線程可能被複用於多種任務,而每個任務各自使用各自的ThreadLocal變數。殊不知ThreadLocal的生命期不是跟任務綁定,而是跟線程綁定的,如果這種使用場景里在任務結束時不清理自己的ThreadLocal,就可能導致意外的內存泄漏。請忘了引用計數吧!請忘了引用計數吧!請忘了引用計數吧!重要事情說三遍判斷alive與否就是樹遍歷,不要問我是深度優先,還是廣度優先!!!都有,具體得查查code。
現在Java GC基於代實現,不用計數了。一般不用手工置null,除非在同一個scope內需要GC ,比如一個方法內分多次new 了好幾個大數組,在前面的數組用完後置null有助於GC 了解這個數組已經沒有引用可以回收了。當然實際應用中這種情況很少,一般等方法結束後引用自動消失比較自然
當前Java GC沒有使用引用計數的方法,並且以前的java gc仍然不使用,標記階段都用的是可達性分析方法。在jvm 中,引用計數法是一種很簡單的標記方法,也是一種不適用的標記方法(因為不能解決循環引用的問題),之所以有的書上提出引用計數法,目的在於讓我們對jvm 的堆內存分配又一個清晰的認識!不要搞混!
引用計數演算法無法實現垃圾判斷,一個對象被引用次數為0,這肯定是垃圾,但是引用次數不為0時,不一定不是垃圾。比如n個互相引用的死對象(孤島)。
所以引用技術演算法不可能用於jvm垃圾回收機制。真正的演算法是可達性分析。關於可達性分析演算法可以谷歌一下。
推薦閱讀:
※如何評價 2015 年 CCPC ?
※編程越來越平民化傻瓜化,這對於IT行業是否有影響?
※知乎上那些自學計算機的人們,你們都有什麼學習計劃和未來規劃?
※為什麼processing坐標系的原點在左上角?
TAG:編程 | Java | Java虛擬機JVM | JavaNativeInterface | GC垃圾回收計算機科學 |