java提供了从语言角度能够强制jvm进行垃圾回收,在我们的程序中可以通过调用System.gc去强制jvm进行垃圾回收,通过源码我们可以看到实际上是调用了Runtime去强制gc
- public static void gc() {
- Runtime.getRuntime().gc();
- }
Runtime的gc方法是native方法也就是Rumtime.c中的
- JNIEXPORT void JNICALL
- Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
- {
- JVM_GC();
- }
而JVM_GC方法是在jvm.cpp中实现
- JVM_ENTRY_NO_ENV(void, JVM_GC(void))
- JVMWrapper("JVM_GC");
- if (!DisableExplicitGC) {
- Universe::heap()->collect(GCCause::_java_lang_system_gc);
- }
- JVM_END
我们看到参数DisableExplicitGC,从代码中可以使用-XX:+DisableExplicitGC 可以关闭system,gc
对universe:heap() -> Universe::_collectedHeap 是在初始化的时候决定使用什么类型的heap,也决定了使用什么gc的策略,也就是说java heap的分配方式是在java启动的时候就决定的,无法中间更改,同样对应的gc策略也无法更改
- if (UseParallelGC) {
- #ifndef SERIALGC
- Universe::_collectedHeap = new ParallelScavengeHeap();
- #else // SERIALGC
- fatal("UseParallelGC not supported in java kernel vm.");
- #endif // SERIALGC
-
- } else if (UseG1GC) {
- #ifndef SERIALGC
- G1CollectorPolicy* g1p = new G1CollectorPolicy_BestRegionsFirst();
- G1CollectedHeap* g1h = new G1CollectedHeap(g1p);
- Universe::_collectedHeap = g1h;
- #else // SERIALGC
- fatal("UseG1GC not supported in java kernel vm.");
- #endif // SERIALGC
-
- } else {
- GenCollectorPolicy *gc_policy;
-
- if (UseSerialGC) {
- gc_policy = new MarkSweepPolicy();
- } else if (UseConcMarkSweepGC) {
- #ifndef SERIALGC
- if (UseAdaptiveSizePolicy) {
- gc_policy = new ASConcurrentMarkSweepPolicy();
- } else {
- gc_policy = new ConcurrentMarkSweepPolicy();
- }
- #else // SERIALGC
- fatal("UseConcMarkSweepGC not supported in java kernel vm.");
- #endif // SERIALGC
- } else { // default old generation
- gc_policy = new MarkSweepPolicy();
- }
-
- Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
- }
从源码中我们可以看到
Parallel GC 是用的是ParallelScavengeHeap,
CMS 使用的是 GenCollectdHeap
G1使用的是G1CollectedHeap
java的gc回收的类型主要有几种 UseSerialGC,UseConcMarkSweepGC,UseParNewGC,UseParallelGC,UseParallelOldGC,UseG1GC,而这几个参数是如何搭配的,实际上只要看下面的代码就非常清楚
- bool Arguments::check_gc_consistency() {
- bool status = true;
- // Ensure that the user has not selected conflicting sets
- // of collectors. [Note: this check is merely a user convenience;
- // collectors over-ride each other so that only a non-conflicting
- // set is selected; however what the user gets is not what they
- // may have expected from the combination they asked for. It's
- // better to reduce user confusion by not allowing them to
- // select conflicting combinations.
- uint i = 0;
- if (UseSerialGC) i++;
- if (UseConcMarkSweepGC || UseParNewGC) i++;
- if (UseParallelGC || UseParallelOldGC) i++;
- if (UseG1GC) i++;
- if (i > 1) {
- jio_fprintf(defaultStream::error_stream(),
- "Conflicting collector combinations in option list; "
- "please refer to the release notes for the combinations "
- "allowed\n");
- status = false;
- }
-
- return status;
- }
我们把GC分成4种类型
1. SerialGC
参数-XX:+UseSerialGC
就是Young区和old区都使用serial 垃圾回收算法,
2. ParallelGC
参数-XX:+UseParallelGC
Young区:使用Parallel scavenge 回收算法
Old 区:可以使用单线程的或者Parallel 垃圾回收算法,由 -XX:+UseParallelOldGC 来控制
3. CMS
参数-XX:+UseConcMarkSweepGC
Young区:可以使用普通的或者parallel 垃圾回收算法,由参数 -XX:+UseParNewGC来控制
Old 区:只能使用Concurrent Mark Sweep
4. G1
参数:-XX:+UseG1GC
没有young/old区
在第一篇中曾经提到过,System.gc 曾经调用代码
- Universe::heap()->collect(GCCause::_java_lang_system_gc);
而每个不同类型的gc,使用不同的heap策略,以parallelScavengeHeap 为例子
- void ParallelScavengeHeap::collect(GCCause::Cause cause) {
- assert(!Heap_lock->owned_by_self(),
- "this thread should not own the Heap_lock");
-
- unsigned int gc_count = 0;
- unsigned int full_gc_count = 0;
- {
- MutexLocker ml(Heap_lock);
- // This value is guarded by the Heap_lock
- gc_count = Universe::heap()->total_collections();
- full_gc_count = Universe::heap()->total_full_collections();
- }
-
- VM_ParallelGCSystemGC op(gc_count, full_gc_count, cause);
- VMThread::execute(&op);
- }
代码中显示当前线程提交了一个operation VM_ParallelGCSystemGC 到了VMThread 线程,关于VMThread线程请参考以前写的2篇文章
http://blog.csdn.net/raintungli/article/details/7045024 VMThread
http://blog.csdn.net/raintungli/article/details/6553337 VMThread 执行的operation
而对VM_ParallelGCSystemGC, 最后VMThread 调用了doit的方法
- void VM_ParallelGCSystemGC::doit() {
- JvmtiGCFullMarker jgcm;
- notify_gc_begin(true);
-
- ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();
- assert(heap->kind() == CollectedHeap::ParallelScavengeHeap,
- "must be a ParallelScavengeHeap");
-
- GCCauseSetter gccs(heap, _gc_cause);
- if (_gc_cause == GCCause::_gc_locker
- DEBUG_ONLY(|| _gc_cause == GCCause::_scavenge_alot)) {
- // If (and only if) the scavenge fails, this will invoke a full gc.
- heap->invoke_scavenge();
- } else {
- heap->invoke_full_gc(false);
- }
- notify_gc_end();
- }
也就是调用了parallelScavengeHeap 的invoke_full_gc方法。
当前线程提交了一个GC的operation 给了VMThread线程, 由VMThread线程来执行回收,因为VMThread线程是执行在Queue里的任务,也就是system.gc 未必会及时触发因为queue里可能还是有其他的任务在执行,同样VMThread在执行GC的时候,queue里的其他任务也会因此而无法执行比如thread.stop
为了提高垃圾回收的性能,java在parallel回收的时候可以设置同时并行处理的线程数也就是ParallelGCThreads,如果你没有设置该参数,该参数jvm会默认设置成online的cpu的核数但并不包括被shutdown的cpu的核数。
Linux 下获取online的cpu的核数
- int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN);
前面曾经提到过GC的主要过程是由VMThread线程执行,在执行parallel的时候,VMThread线程实际上是在等待线程执行结果的,这也就是为什么并没有设置ParallelGCThreads= online cpu core-1
Parallel gc 和CMS里的young区都可以设置ParallelGCThreads参数,下面主要讲的是Parallel GC
1. Parallel Threads 初始化
Parallel Threads本质是由gcTaskManager管理的gcTaskThread, 在parallelScavenge.cpp 中的initialize() 方法中通过GCTaskManager::create(ParallelGCThreads);初始化gcTaskThread的,也就是当jvm启动的时候thread 就已经创建并且运行。
参数:BindGCTaskThreadsToCPUs
jvm在linux中并没有对gcTaskThread线程绑定到固定的cpu上,但在solaris上却支持了,请参考函数os::distribute_processes 和 os::bind_to_processor
2. GCTaskQueue
这是一个存放的元素是GCTask的链表结构,指针_insert_end指向最后一个元素,指针_remove_end指向第一个元素
GCTask本身也是一个链表结构,指针_newer指向后面的元素,_older指向前面一个元素,可以参考下面的图
当增加一个GCTask C的时候,会从Queue中取出_insert_end的GCTask B,设置B _newer 为C, 设置C older_为B,设置_insert_end为C
当取一个GCTask的时候,从Queue取出_remove_end的GCTask B, 设置_remove_end为C,设置C的old为null, 设置 B的new为nul
参数 UseGCTaskAffinity
通过设置_affinity 属性用于表示该task的是由哪个线程执行,当GCTaskThread运行的时候会取出该线程的所对应的task
做法使用轮询从queue的第一个开始顺序查找,一直找到然后移除。
3. GCTaskThread 运行
GCTaskThread不停轮询GCTaskQueue,当Queue里面没有数据的时候,线程会wait等待,直到有数据插入notify线程,由于Queue的操作并不是线程安全的,需要在增加和删除的时候申请互斥锁MutexLockerEx ml(monitor(), Mutex::_no_safepoint_check_flag);
VMthread 线程在Parallel GC 时候将任务添加到queue中,然后等待结果。