這篇文章記錄一下gc和對虛擬機的理解,其它的相關jvm的內容就暫時先不涉及了,以後有機會再弄。
1.gc流程
在前臺的文章中,我記錄了一個模擬的gc流程,那個裏面由於對於根對象 具有很高的抽象程度,因此對java的gc想要去了解一下,這裏分塊看看gc的源碼:
// 0. create a TEMP new-oop-pool:
unordered_map<Oop *, Oop *> new_oop_map; // must be `map/set` instead of list, for getting rid of duplication!!!
首先,定義一個緩存容器。
Oop *new_oop; // global local variable
// 0.5. first migrate all of the basic type mirrors.
for (auto & iter : java_lang_class::get_single_basic_type_mirrors()) {
Oop *mirror = iter.second;
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(mirror, new_oop_map);
iter.second = (MirrorOop *)mirror;
}
// 0.7. I don't want to uninstall all StringTable...
unordered_set<Oop *, java_string_hash, java_string_equal_to> new_string_table;
for (auto & iter : java_lang_string::get_string_table()) {
new_oop = iter;
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(new_oop, new_oop_map);
new_string_table.insert(new_oop);
}
new_string_table.swap(java_lang_string::get_string_table());
然後收集全局變量。
// 1. for all GC-Roots [InstanceKlass]:
for (auto iter : system_classmap) {
klass_inner_oop_gc(iter.second, new_oop_map); // gc the klass
}
for (auto iter : AutomanClassLoader::get_loader().classmap) {
klass_inner_oop_gc(iter.second, new_oop_map); // gc the klass
}
for (auto iter : AutomanClassLoader::get_loader().anonymous_klassmap) {
klass_inner_oop_gc(iter, new_oop_map); // gc the klass
}
然會回收類實例(這個流程可能是 卸載,與加載類對應);
// 2. for all GC-Roots [vm_threads]:
for (auto & thread : automan_jvm::threads()) {
// std::wcout << "thread: " << thread.tid << ", has " << thread.vm_stack.size() << " frames... "<< std::endl; // delete
// 2.3. for thread.args
for (auto & iter : thread.arg) {
// std::wcout << "arg: " << iter << std::endl; // delete
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(iter, new_oop_map);
}
for (auto & frame : thread.vm_stack) {
// 2.5. for vm_stack::StackFrame::LocalVariableTable
for (auto & oop : frame.localVariableTable) {
// std::wcout << "localVariableTable: " << oop; // delete
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(oop, new_oop_map);
// std::wcout << " to " << oop; // delete
}
// 2.7. for vm_stack::StackFrame::op_stack
// stack can't use iter. so make it with another vector...
list<Oop *> temp;
while(!frame.op_stack.empty()) {
Oop *oop = frame.op_stack.top(); frame.op_stack.pop();
temp.push_front(oop);
}
for (auto & oop : temp) {
// std::wcout << "op_stack: " << oop; // delete
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(oop, new_oop_map);
// std::wcout << " to " << oop; // delete
}
for (auto & oop : temp) {
frame.op_stack.push(oop);
}
}
}
然會回收線程的資源,包括本地變量表,虛擬機棧,操作棧,等可回收資源。
// 2.5. for all GC-Roots: ThreadTable
for (auto & iter : ThreadTable::get_thread_table()) {
// std::wcout << "thread: from " << iter.second.second;
Oop *thread = std::get<1>(iter.second);
recursive_add_oop_and_its_inner_oops_and_modify_pointers_by_the_way(thread, new_oop_map);
std::get<1>(iter.second) = (InstanceOop *)thread;
// std::wcout << " to " << iter.second.second;
}
然後回收線程。
// 3. create a new oop table and exchange with the global Mempool
list<Oop *> new_oop_handler_pool;
for (auto & iter : new_oop_map) {
new_oop_handler_pool.push_back(iter.second);
}
// delete all:
for (auto iter : Mempool::oop_handler_pool()) {
delete iter;
}
// swap.
new_oop_handler_pool.swap(Mempool::oop_handler_pool());
// 4. final: must do this.
gc() = false; // no need to lock.
//todo: 這裏gc完成後,通知所有的線程(由 gc線程發出通知)
signal_all_thread();
std::wcout << "gc over!!" << std::endl; // delete
最後回收資源,並處罰線程通知。
從代碼看,貌似沒有用分代回收。 。 emmmm... ,但是至少知道所謂的根是怎麼來的了。
2.jvm虛擬機是什麼?
這個主要是我用於對這段時間調試的理解。
之前總是聽說,java是介於解釋型與編譯型語言之間,即中間字節碼的方式運行的,它較純編譯語言的速度有所下降。 從源碼來分析,它的速度到底有哪些影響呢?
實際上,這個jvm的主題部分就是一個 switch...case... 結構, 其中,ByteCodeEngine在目前功能不完善的情況下,這個控制結構的代碼長度爲 3000多行。 注意,這是一個方法哦! 比絕大多數類都要大了。
在看看 case中,用於判斷的是 十六進制的 操作數, 類似於 0x01,0x02... 0xff 這種,數字有個什麼好處?? 它是天然的索引!!! 簡言之,這個switch...case...結構是一顆天然的 B樹索引,任意一個操作的檢索時間爲 O(1)的時間複雜度。 從這個層面講,它與原生的程序幾乎沒有區別。
那爲什麼說它的速度有一定影響呢?我覺得主要從以下幾個方面考慮:
1.任意一個java程序,它要啓動前,必須啓動jvm,啓動jvm我們前面已經說了,它會經歷那樣那樣的步驟。 不算輕量級。
2.啓動一個java程序之後,它的內部會至少開闢三個線程,這就涉及到線程調度,而且gc線程調用較爲頻繁,這是比較耗時的。
3.雖然執行流程跟原生的並沒有多大區別,但是虛擬機棧,本地變量表,全局常量表 等等都是通過代碼維護,而純編譯語言這些東西是由操作系統維護的,它是機器碼級別。 所以這裏速度會有一些差別。
jvm的內容暫時就記到這裏了,代碼地址在: 這裏。