|  | /* | 
|  | * Copyright (C) 2008 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include "reference_table.h" | 
|  |  | 
|  | #include "base/mutex.h" | 
|  | #include "indirect_reference_table.h" | 
|  | #include "mirror/array.h" | 
|  | #include "mirror/array-inl.h" | 
|  | #include "mirror/class.h" | 
|  | #include "mirror/class-inl.h" | 
|  | #include "mirror/object-inl.h" | 
|  | #include "mirror/string-inl.h" | 
|  | #include "runtime-inl.h" | 
|  | #include "thread.h" | 
|  | #include "utils.h" | 
|  |  | 
|  | namespace art { | 
|  |  | 
|  | ReferenceTable::ReferenceTable(const char* name, size_t initial_size, size_t max_size) | 
|  | : name_(name), max_size_(max_size) { | 
|  | CHECK_LE(initial_size, max_size); | 
|  | entries_.reserve(initial_size); | 
|  | } | 
|  |  | 
|  | ReferenceTable::~ReferenceTable() { | 
|  | } | 
|  |  | 
|  | void ReferenceTable::Add(mirror::Object* obj) { | 
|  | DCHECK(obj != NULL); | 
|  | VerifyObject(obj); | 
|  | if (entries_.size() >= max_size_) { | 
|  | LOG(FATAL) << "ReferenceTable '" << name_ << "' " | 
|  | << "overflowed (" << max_size_ << " entries)"; | 
|  | } | 
|  | entries_.push_back(GcRoot<mirror::Object>(obj)); | 
|  | } | 
|  |  | 
|  | void ReferenceTable::Remove(mirror::Object* obj) { | 
|  | // We iterate backwards on the assumption that references are LIFO. | 
|  | for (int i = entries_.size() - 1; i >= 0; --i) { | 
|  | mirror::Object* entry = entries_[i].Read(); | 
|  | if (entry == obj) { | 
|  | entries_.erase(entries_.begin() + i); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // If "obj" is an array, return the number of elements in the array. | 
|  | // Otherwise, return zero. | 
|  | static size_t GetElementCount(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { | 
|  | // We assume the special cleared value isn't an array in the if statement below. | 
|  | DCHECK(!Runtime::Current()->GetClearedJniWeakGlobal()->IsArrayInstance()); | 
|  | if (obj == nullptr || !obj->IsArrayInstance()) { | 
|  | return 0; | 
|  | } | 
|  | return obj->AsArray()->GetLength(); | 
|  | } | 
|  |  | 
|  | struct ObjectComparator { | 
|  | bool operator()(GcRoot<mirror::Object> root1, GcRoot<mirror::Object> root2) const | 
|  | // TODO: enable analysis when analysis can work with the STL. | 
|  | NO_THREAD_SAFETY_ANALYSIS { | 
|  | Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); | 
|  | mirror::Object* obj1 = root1.Read<kWithoutReadBarrier>(); | 
|  | mirror::Object* obj2 = root2.Read<kWithoutReadBarrier>(); | 
|  | DCHECK(obj1 != nullptr); | 
|  | DCHECK(obj2 != nullptr); | 
|  | Runtime* runtime = Runtime::Current(); | 
|  | DCHECK(!runtime->IsClearedJniWeakGlobal(obj1)); | 
|  | DCHECK(!runtime->IsClearedJniWeakGlobal(obj2)); | 
|  | // Sort by class... | 
|  | if (obj1->GetClass() != obj2->GetClass()) { | 
|  | return obj1->GetClass()->IdentityHashCode() < obj2->GetClass()->IdentityHashCode(); | 
|  | } | 
|  | // ...then by size... | 
|  | const size_t size1 = obj1->SizeOf(); | 
|  | const size_t size2 = obj2->SizeOf(); | 
|  | if (size1 != size2) { | 
|  | return size1 < size2; | 
|  | } | 
|  | // ...and finally by identity hash code. | 
|  | return obj1->IdentityHashCode() < obj2->IdentityHashCode(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Log an object with some additional info. | 
|  | // | 
|  | // Pass in the number of elements in the array (or 0 if this is not an | 
|  | // array object), and the number of additional objects that are identical | 
|  | // or equivalent to the original. | 
|  | static void DumpSummaryLine(std::ostream& os, mirror::Object* obj, size_t element_count, | 
|  | int identical, int equiv) | 
|  | SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { | 
|  | if (obj == NULL) { | 
|  | os << "    NULL reference (count=" << equiv << ")\n"; | 
|  | return; | 
|  | } | 
|  | if (Runtime::Current()->IsClearedJniWeakGlobal(obj)) { | 
|  | os << "    cleared jweak (count=" << equiv << ")\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string className(PrettyTypeOf(obj)); | 
|  | if (obj->IsClass()) { | 
|  | // We're summarizing multiple instances, so using the exemplar | 
|  | // Class' type parameter here would be misleading. | 
|  | className = "java.lang.Class"; | 
|  | } | 
|  | if (element_count != 0) { | 
|  | StringAppendF(&className, " (%zd elements)", element_count); | 
|  | } | 
|  |  | 
|  | size_t total = identical + equiv + 1; | 
|  | std::string msg(StringPrintf("%5zd of %s", total, className.c_str())); | 
|  | if (identical + equiv != 0) { | 
|  | StringAppendF(&msg, " (%d unique instances)", equiv + 1); | 
|  | } | 
|  | os << "    " << msg << "\n"; | 
|  | } | 
|  |  | 
|  | size_t ReferenceTable::Size() const { | 
|  | return entries_.size(); | 
|  | } | 
|  |  | 
|  | void ReferenceTable::Dump(std::ostream& os) { | 
|  | os << name_ << " reference table dump:\n"; | 
|  | Dump(os, entries_); | 
|  | } | 
|  |  | 
|  | void ReferenceTable::Dump(std::ostream& os, Table& entries) { | 
|  | if (entries.empty()) { | 
|  | os << "  (empty)\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Dump the most recent N entries. | 
|  | const size_t kLast = 10; | 
|  | size_t count = entries.size(); | 
|  | int first = count - kLast; | 
|  | if (first < 0) { | 
|  | first = 0; | 
|  | } | 
|  | os << "  Last " << (count - first) << " entries (of " << count << "):\n"; | 
|  | Runtime* runtime = Runtime::Current(); | 
|  | for (int idx = count - 1; idx >= first; --idx) { | 
|  | mirror::Object* ref = entries[idx].Read(); | 
|  | if (ref == nullptr) { | 
|  | continue; | 
|  | } | 
|  | if (runtime->IsClearedJniWeakGlobal(ref)) { | 
|  | os << StringPrintf("    %5d: cleared jweak\n", idx); | 
|  | continue; | 
|  | } | 
|  | if (ref->GetClass() == nullptr) { | 
|  | // should only be possible right after a plain dvmMalloc(). | 
|  | size_t size = ref->SizeOf(); | 
|  | os << StringPrintf("    %5d: %p (raw) (%zd bytes)\n", idx, ref, size); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string className(PrettyTypeOf(ref)); | 
|  |  | 
|  | std::string extras; | 
|  | size_t element_count = GetElementCount(ref); | 
|  | if (element_count != 0) { | 
|  | StringAppendF(&extras, " (%zd elements)", element_count); | 
|  | } else if (ref->GetClass()->IsStringClass()) { | 
|  | mirror::String* s = ref->AsString(); | 
|  | std::string utf8(s->ToModifiedUtf8()); | 
|  | if (s->GetLength() <= 16) { | 
|  | StringAppendF(&extras, " \"%s\"", utf8.c_str()); | 
|  | } else { | 
|  | StringAppendF(&extras, " \"%.16s... (%d chars)", utf8.c_str(), s->GetLength()); | 
|  | } | 
|  | } | 
|  | os << StringPrintf("    %5d: ", idx) << ref << " " << className << extras << "\n"; | 
|  | } | 
|  |  | 
|  | // Make a copy of the table and sort it, only adding non null and not cleared elements. | 
|  | Table sorted_entries; | 
|  | for (GcRoot<mirror::Object>& root : entries) { | 
|  | if (!root.IsNull() && !runtime->IsClearedJniWeakGlobal(root.Read())) { | 
|  | sorted_entries.push_back(root); | 
|  | } | 
|  | } | 
|  | if (sorted_entries.empty()) { | 
|  | return; | 
|  | } | 
|  | std::sort(sorted_entries.begin(), sorted_entries.end(), ObjectComparator()); | 
|  |  | 
|  | // Dump a summary of the whole table. | 
|  | os << "  Summary:\n"; | 
|  | size_t equiv = 0; | 
|  | size_t identical = 0; | 
|  | mirror::Object* prev = nullptr; | 
|  | for (GcRoot<mirror::Object>& root : sorted_entries) { | 
|  | mirror::Object* current = root.Read<kWithoutReadBarrier>(); | 
|  | if (prev != nullptr) { | 
|  | const size_t element_count = GetElementCount(prev); | 
|  | if (current == prev) { | 
|  | // Same reference, added more than once. | 
|  | ++identical; | 
|  | } else if (current->GetClass() == prev->GetClass() && | 
|  | GetElementCount(current) == element_count) { | 
|  | // Same class / element count, different object. | 
|  | ++equiv; | 
|  | } else { | 
|  | // Different class. | 
|  | DumpSummaryLine(os, prev, element_count, identical, equiv); | 
|  | equiv = 0; | 
|  | identical = 0; | 
|  | } | 
|  | } | 
|  | prev = current; | 
|  | } | 
|  | // Handle the last entry. | 
|  | DumpSummaryLine(os, prev, GetElementCount(prev), identical, equiv); | 
|  | } | 
|  |  | 
|  | void ReferenceTable::VisitRoots(RootCallback* visitor, void* arg, uint32_t tid, | 
|  | RootType root_type) { | 
|  | for (GcRoot<mirror::Object>& root : entries_) { | 
|  | root.VisitRoot(visitor, arg, tid, root_type); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace art |