这篇文章记录一下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的内容暂时就记到这里了,代码地址在: 这里。