8236847: CDS archive with 4K alignment unusable on machines with 64k pages
Reviewed-by: iklam, stuefe, erikj, ihse
This commit is contained in:
parent
273f8bdf5f
commit
3820ab9e82
@ -248,6 +248,7 @@ JDKOPT_ENABLE_DISABLE_GENERATE_CLASSLIST
|
|||||||
JDKOPT_EXCLUDE_TRANSLATIONS
|
JDKOPT_EXCLUDE_TRANSLATIONS
|
||||||
JDKOPT_ENABLE_DISABLE_MANPAGES
|
JDKOPT_ENABLE_DISABLE_MANPAGES
|
||||||
JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE
|
JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE
|
||||||
|
JDKOPT_ENABLE_DISABLE_COMPATIBLE_CDS_ALIGNMENT
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
|
# Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
#
|
#
|
||||||
# This code is free software; you can redistribute it and/or modify it
|
# This code is free software; you can redistribute it and/or modify it
|
||||||
@ -585,6 +585,30 @@ AC_DEFUN([JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE],
|
|||||||
AC_SUBST(BUILD_CDS_ARCHIVE)
|
AC_SUBST(BUILD_CDS_ARCHIVE)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Enable the alternative CDS core region alignment
|
||||||
|
#
|
||||||
|
AC_DEFUN([JDKOPT_ENABLE_DISABLE_COMPATIBLE_CDS_ALIGNMENT],
|
||||||
|
[
|
||||||
|
UTIL_ARG_ENABLE(NAME: compatible-cds-alignment, DEFAULT: false,
|
||||||
|
RESULT: ENABLE_COMPATIBLE_CDS_ALIGNMENT,
|
||||||
|
DESC: [enable use alternative compatible cds core region alignment],
|
||||||
|
DEFAULT_DESC: [disabled],
|
||||||
|
CHECKING_MSG: [if compatible cds region alignment enabled],
|
||||||
|
CHECK_AVAILABLE: [
|
||||||
|
AC_MSG_CHECKING([if CDS archive is available])
|
||||||
|
if test "x$BUILD_CDS_ARCHIVE" = "xfalse"; then
|
||||||
|
AVAILABLE=false
|
||||||
|
AC_MSG_RESULT([no (CDS is disabled)])
|
||||||
|
else
|
||||||
|
AVAILABLE=true
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
fi
|
||||||
|
])
|
||||||
|
AC_SUBST(ENABLE_COMPATIBLE_CDS_ALIGNMENT)
|
||||||
|
])
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Disallow any output from containing absolute paths from the build system.
|
# Disallow any output from containing absolute paths from the build system.
|
||||||
|
@ -349,6 +349,8 @@ BUILD_MANPAGES := @BUILD_MANPAGES@
|
|||||||
|
|
||||||
BUILD_CDS_ARCHIVE := @BUILD_CDS_ARCHIVE@
|
BUILD_CDS_ARCHIVE := @BUILD_CDS_ARCHIVE@
|
||||||
|
|
||||||
|
ENABLE_COMPATIBLE_CDS_ALIGNMENT := @ENABLE_COMPATIBLE_CDS_ALIGNMENT@
|
||||||
|
|
||||||
ALLOW_ABSOLUTE_PATHS_IN_OUTPUT := @ALLOW_ABSOLUTE_PATHS_IN_OUTPUT@
|
ALLOW_ABSOLUTE_PATHS_IN_OUTPUT := @ALLOW_ABSOLUTE_PATHS_IN_OUTPUT@
|
||||||
|
|
||||||
# The boot jdk to use. This is overridden in bootcycle-spec.gmk. Make sure to keep
|
# The boot jdk to use. This is overridden in bootcycle-spec.gmk. Make sure to keep
|
||||||
|
@ -441,6 +441,7 @@ var getJibProfilesProfiles = function (input, common, data) {
|
|||||||
dependencies: ["devkit", "gtest", "pandoc"],
|
dependencies: ["devkit", "gtest", "pandoc"],
|
||||||
configure_args: concat(common.configure_args_64bit, "--with-zlib=system",
|
configure_args: concat(common.configure_args_64bit, "--with-zlib=system",
|
||||||
"--with-macosx-version-max=10.12.00",
|
"--with-macosx-version-max=10.12.00",
|
||||||
|
"--enable-compatible-cds-alignment",
|
||||||
// Use system SetFile instead of the one in the devkit as the
|
// Use system SetFile instead of the one in the devkit as the
|
||||||
// devkit one may not work on Catalina.
|
// devkit one may not work on Catalina.
|
||||||
"SETFILE=/usr/bin/SetFile"),
|
"SETFILE=/usr/bin/SetFile"),
|
||||||
@ -477,6 +478,7 @@ var getJibProfilesProfiles = function (input, common, data) {
|
|||||||
dependencies: ["devkit", "gtest", "build_devkit", "pandoc"],
|
dependencies: ["devkit", "gtest", "build_devkit", "pandoc"],
|
||||||
configure_args: [
|
configure_args: [
|
||||||
"--openjdk-target=aarch64-linux-gnu",
|
"--openjdk-target=aarch64-linux-gnu",
|
||||||
|
"--enable-compatible-cds-alignment",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
|
# Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
#
|
#
|
||||||
# This code is free software; you can redistribute it and/or modify it
|
# This code is free software; you can redistribute it and/or modify it
|
||||||
@ -99,3 +99,7 @@ endif
|
|||||||
ifneq ($(HOTSPOT_OVERRIDE_LIBPATH), )
|
ifneq ($(HOTSPOT_OVERRIDE_LIBPATH), )
|
||||||
JVM_CFLAGS += -DOVERRIDE_LIBPATH='"$(HOTSPOT_OVERRIDE_LIBPATH)"'
|
JVM_CFLAGS += -DOVERRIDE_LIBPATH='"$(HOTSPOT_OVERRIDE_LIBPATH)"'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ENABLE_COMPATIBLE_CDS_ALIGNMENT), true)
|
||||||
|
JVM_CFLAGS += -DCOMPATIBLE_CDS_ALIGNMENT
|
||||||
|
endif
|
||||||
|
@ -25,6 +25,12 @@
|
|||||||
#ifndef OS_CPU_BSD_X86_OS_BSD_X86_HPP
|
#ifndef OS_CPU_BSD_X86_OS_BSD_X86_HPP
|
||||||
#define OS_CPU_BSD_X86_OS_BSD_X86_HPP
|
#define OS_CPU_BSD_X86_OS_BSD_X86_HPP
|
||||||
|
|
||||||
|
// Core region alignment is 16K to be able to run binaries built on MacOS x64
|
||||||
|
// on MacOS aarch64.
|
||||||
|
#if defined(__APPLE__) && defined(COMPATIBLE_CDS_ALIGNMENT)
|
||||||
|
#define CDS_CORE_REGION_ALIGNMENT (16*K)
|
||||||
|
#endif
|
||||||
|
|
||||||
static void setup_fpu();
|
static void setup_fpu();
|
||||||
static bool supports_sse();
|
static bool supports_sse();
|
||||||
static juint cpu_microcode_revision();
|
static juint cpu_microcode_revision();
|
||||||
|
@ -26,6 +26,10 @@
|
|||||||
#ifndef OS_CPU_LINUX_AARCH64_OS_LINUX_AARCH64_HPP
|
#ifndef OS_CPU_LINUX_AARCH64_OS_LINUX_AARCH64_HPP
|
||||||
#define OS_CPU_LINUX_AARCH64_OS_LINUX_AARCH64_HPP
|
#define OS_CPU_LINUX_AARCH64_OS_LINUX_AARCH64_HPP
|
||||||
|
|
||||||
|
#if defined(COMPATIBLE_CDS_ALIGNMENT)
|
||||||
|
#define CDS_CORE_REGION_ALIGNMENT (64*K)
|
||||||
|
#endif
|
||||||
|
|
||||||
static void setup_fpu();
|
static void setup_fpu();
|
||||||
|
|
||||||
static bool is_allocatable(size_t bytes);
|
static bool is_allocatable(size_t bytes);
|
||||||
|
@ -327,19 +327,19 @@ size_t ArchiveBuilder::estimate_archive_size() {
|
|||||||
total += _estimated_hashtable_bytes;
|
total += _estimated_hashtable_bytes;
|
||||||
|
|
||||||
// allow fragmentation at the end of each dump region
|
// allow fragmentation at the end of each dump region
|
||||||
total += _total_dump_regions * reserve_alignment();
|
total += _total_dump_regions * MetaspaceShared::core_region_alignment();
|
||||||
|
|
||||||
log_info(cds)("_estimated_hashtable_bytes = " SIZE_FORMAT " + " SIZE_FORMAT " = " SIZE_FORMAT,
|
log_info(cds)("_estimated_hashtable_bytes = " SIZE_FORMAT " + " SIZE_FORMAT " = " SIZE_FORMAT,
|
||||||
symbol_table_est, dictionary_est, _estimated_hashtable_bytes);
|
symbol_table_est, dictionary_est, _estimated_hashtable_bytes);
|
||||||
log_info(cds)("_estimated_metaspaceobj_bytes = " SIZE_FORMAT, _estimated_metaspaceobj_bytes);
|
log_info(cds)("_estimated_metaspaceobj_bytes = " SIZE_FORMAT, _estimated_metaspaceobj_bytes);
|
||||||
log_info(cds)("total estimate bytes = " SIZE_FORMAT, total);
|
log_info(cds)("total estimate bytes = " SIZE_FORMAT, total);
|
||||||
|
|
||||||
return align_up(total, reserve_alignment());
|
return align_up(total, MetaspaceShared::core_region_alignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
address ArchiveBuilder::reserve_buffer() {
|
address ArchiveBuilder::reserve_buffer() {
|
||||||
size_t buffer_size = estimate_archive_size();
|
size_t buffer_size = estimate_archive_size();
|
||||||
ReservedSpace rs(buffer_size);
|
ReservedSpace rs(buffer_size, MetaspaceShared::core_region_alignment(), false);
|
||||||
if (!rs.is_reserved()) {
|
if (!rs.is_reserved()) {
|
||||||
log_error(cds)("Failed to reserve " SIZE_FORMAT " bytes of output buffer.", buffer_size);
|
log_error(cds)("Failed to reserve " SIZE_FORMAT " bytes of output buffer.", buffer_size);
|
||||||
vm_direct_exit(0);
|
vm_direct_exit(0);
|
||||||
@ -377,7 +377,7 @@ address ArchiveBuilder::reserve_buffer() {
|
|||||||
|
|
||||||
// At run time, we will mmap the dynamic archive at my_archive_requested_bottom
|
// At run time, we will mmap the dynamic archive at my_archive_requested_bottom
|
||||||
_requested_static_archive_top = _requested_static_archive_bottom + static_archive_size;
|
_requested_static_archive_top = _requested_static_archive_bottom + static_archive_size;
|
||||||
my_archive_requested_bottom = align_up(_requested_static_archive_top, MetaspaceShared::reserved_space_alignment());
|
my_archive_requested_bottom = align_up(_requested_static_archive_top, MetaspaceShared::core_region_alignment());
|
||||||
|
|
||||||
_requested_dynamic_archive_bottom = my_archive_requested_bottom;
|
_requested_dynamic_archive_bottom = my_archive_requested_bottom;
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,8 @@ private:
|
|||||||
static ArchiveBuilder* _current;
|
static ArchiveBuilder* _current;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Use this when you allocate space with MetaspaceShare::read_only_space_alloc()
|
// Use this when you allocate space outside of ArchiveBuilder::dump_{rw,ro}_region.
|
||||||
// outside of ArchiveBuilder::dump_{rw,ro}_region. These are usually for misc tables
|
// These are usually for misc tables that are allocated in the RO space.
|
||||||
// that are allocated in the RO space.
|
|
||||||
class OtherROAllocMark {
|
class OtherROAllocMark {
|
||||||
char* _oldtop;
|
char* _oldtop;
|
||||||
public:
|
public:
|
||||||
@ -265,10 +264,6 @@ protected:
|
|||||||
|
|
||||||
size_t estimate_archive_size();
|
size_t estimate_archive_size();
|
||||||
|
|
||||||
static size_t reserve_alignment() {
|
|
||||||
return os::vm_allocation_granularity();
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_dump_space(DumpRegion* next);
|
void start_dump_space(DumpRegion* next);
|
||||||
void verify_estimate_size(size_t estimate, const char* which);
|
void verify_estimate_size(size_t estimate, const char* which);
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ void DumpRegion::init(ReservedSpace* rs, VirtualSpace* vs) {
|
|||||||
|
|
||||||
void DumpRegion::pack(DumpRegion* next) {
|
void DumpRegion::pack(DumpRegion* next) {
|
||||||
assert(!is_packed(), "sanity");
|
assert(!is_packed(), "sanity");
|
||||||
_end = (char*)align_up(_top, MetaspaceShared::reserved_space_alignment());
|
_end = (char*)align_up(_top, MetaspaceShared::core_region_alignment());
|
||||||
_is_packed = true;
|
_is_packed = true;
|
||||||
if (next != NULL) {
|
if (next != NULL) {
|
||||||
next->_rs = _rs;
|
next->_rs = _rs;
|
||||||
|
@ -47,12 +47,6 @@
|
|||||||
|
|
||||||
|
|
||||||
class DynamicArchiveBuilder : public ArchiveBuilder {
|
class DynamicArchiveBuilder : public ArchiveBuilder {
|
||||||
public:
|
|
||||||
|
|
||||||
static size_t reserve_alignment() {
|
|
||||||
return os::vm_allocation_granularity();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void mark_pointer(address* ptr_loc) {
|
void mark_pointer(address* ptr_loc) {
|
||||||
ArchivePtrMarker::mark_pointer(ptr_loc);
|
ArchivePtrMarker::mark_pointer(ptr_loc);
|
||||||
@ -179,7 +173,7 @@ void DynamicArchiveBuilder::init_header() {
|
|||||||
for (int i = 0; i < MetaspaceShared::n_regions; i++) {
|
for (int i = 0; i < MetaspaceShared::n_regions; i++) {
|
||||||
_header->set_base_region_crc(i, base_info->space_crc(i));
|
_header->set_base_region_crc(i, base_info->space_crc(i));
|
||||||
}
|
}
|
||||||
_header->populate(base_info, os::vm_allocation_granularity());
|
_header->populate(base_info, base_info->core_region_alignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DynamicArchiveBuilder::release_header() {
|
void DynamicArchiveBuilder::release_header() {
|
||||||
|
@ -198,18 +198,18 @@ FileMapInfo::~FileMapInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileMapInfo::populate_header(size_t alignment) {
|
void FileMapInfo::populate_header(size_t core_region_alignment) {
|
||||||
header()->populate(this, alignment);
|
header()->populate(this, core_region_alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileMapHeader::populate(FileMapInfo* mapinfo, size_t alignment) {
|
void FileMapHeader::populate(FileMapInfo* mapinfo, size_t core_region_alignment) {
|
||||||
if (DynamicDumpSharedSpaces) {
|
if (DynamicDumpSharedSpaces) {
|
||||||
_magic = CDS_DYNAMIC_ARCHIVE_MAGIC;
|
_magic = CDS_DYNAMIC_ARCHIVE_MAGIC;
|
||||||
} else {
|
} else {
|
||||||
_magic = CDS_ARCHIVE_MAGIC;
|
_magic = CDS_ARCHIVE_MAGIC;
|
||||||
}
|
}
|
||||||
_version = CURRENT_CDS_ARCHIVE_VERSION;
|
_version = CURRENT_CDS_ARCHIVE_VERSION;
|
||||||
_alignment = alignment;
|
_core_region_alignment = core_region_alignment;
|
||||||
_obj_alignment = ObjectAlignmentInBytes;
|
_obj_alignment = ObjectAlignmentInBytes;
|
||||||
_compact_strings = CompactStrings;
|
_compact_strings = CompactStrings;
|
||||||
if (HeapShared::is_heap_object_archiving_allowed()) {
|
if (HeapShared::is_heap_object_archiving_allowed()) {
|
||||||
@ -267,7 +267,7 @@ void FileMapHeader::print(outputStream* st) {
|
|||||||
st->print_cr("============ end regions ======== ");
|
st->print_cr("============ end regions ======== ");
|
||||||
|
|
||||||
st->print_cr("- header_size: " SIZE_FORMAT, _header_size);
|
st->print_cr("- header_size: " SIZE_FORMAT, _header_size);
|
||||||
st->print_cr("- alignment: " SIZE_FORMAT, _alignment);
|
st->print_cr("- core_region_alignment: " SIZE_FORMAT, _core_region_alignment);
|
||||||
st->print_cr("- obj_alignment: %d", _obj_alignment);
|
st->print_cr("- obj_alignment: %d", _obj_alignment);
|
||||||
st->print_cr("- narrow_oop_base: " INTPTR_FORMAT, p2i(_narrow_oop_base));
|
st->print_cr("- narrow_oop_base: " INTPTR_FORMAT, p2i(_narrow_oop_base));
|
||||||
st->print_cr("- narrow_oop_base: " INTPTR_FORMAT, p2i(_narrow_oop_base));
|
st->print_cr("- narrow_oop_base: " INTPTR_FORMAT, p2i(_narrow_oop_base));
|
||||||
@ -1225,7 +1225,7 @@ void FileMapInfo::open_for_write(const char* path) {
|
|||||||
header_bytes += strlen(Arguments::GetSharedArchivePath()) + 1;
|
header_bytes += strlen(Arguments::GetSharedArchivePath()) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
header_bytes = align_up(header_bytes, os::vm_allocation_granularity());
|
header_bytes = align_up(header_bytes, MetaspaceShared::core_region_alignment());
|
||||||
_file_offset = header_bytes;
|
_file_offset = header_bytes;
|
||||||
seek_to_position(_file_offset);
|
seek_to_position(_file_offset);
|
||||||
}
|
}
|
||||||
@ -1251,7 +1251,7 @@ void FileMapInfo::write_header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t FileMapRegion::used_aligned() const {
|
size_t FileMapRegion::used_aligned() const {
|
||||||
return align_up(used(), os::vm_allocation_granularity());
|
return align_up(used(), MetaspaceShared::core_region_alignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileMapRegion::init(int region_index, size_t mapping_offset, size_t size, bool read_only,
|
void FileMapRegion::init(int region_index, size_t mapping_offset, size_t size, bool read_only,
|
||||||
@ -1456,7 +1456,7 @@ void FileMapInfo::write_bytes(const void* buffer, size_t nbytes) {
|
|||||||
|
|
||||||
bool FileMapInfo::is_file_position_aligned() const {
|
bool FileMapInfo::is_file_position_aligned() const {
|
||||||
return _file_offset == align_up(_file_offset,
|
return _file_offset == align_up(_file_offset,
|
||||||
os::vm_allocation_granularity());
|
MetaspaceShared::core_region_alignment());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Align file position to an allocation unit boundary.
|
// Align file position to an allocation unit boundary.
|
||||||
@ -1464,7 +1464,7 @@ bool FileMapInfo::is_file_position_aligned() const {
|
|||||||
void FileMapInfo::align_file_position() {
|
void FileMapInfo::align_file_position() {
|
||||||
assert(_file_open, "must be");
|
assert(_file_open, "must be");
|
||||||
size_t new_file_offset = align_up(_file_offset,
|
size_t new_file_offset = align_up(_file_offset,
|
||||||
os::vm_allocation_granularity());
|
MetaspaceShared::core_region_alignment());
|
||||||
if (new_file_offset != _file_offset) {
|
if (new_file_offset != _file_offset) {
|
||||||
_file_offset = new_file_offset;
|
_file_offset = new_file_offset;
|
||||||
// Seek one byte back from the target and write a byte to insure
|
// Seek one byte back from the target and write a byte to insure
|
||||||
@ -1507,8 +1507,7 @@ bool FileMapInfo::remap_shared_readonly_as_readwrite() {
|
|||||||
// the space is already readwrite so we are done
|
// the space is already readwrite so we are done
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
size_t used = si->used();
|
size_t size = si->used_aligned();
|
||||||
size_t size = align_up(used, os::vm_allocation_granularity());
|
|
||||||
if (!open_for_read()) {
|
if (!open_for_read()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2088,8 +2087,7 @@ void FileMapInfo::unmap_region(int i) {
|
|||||||
assert(!HeapShared::is_heap_region(i), "sanity");
|
assert(!HeapShared::is_heap_region(i), "sanity");
|
||||||
FileMapRegion* si = space_at(i);
|
FileMapRegion* si = space_at(i);
|
||||||
char* mapped_base = si->mapped_base();
|
char* mapped_base = si->mapped_base();
|
||||||
size_t used = si->used();
|
size_t size = si->used_aligned();
|
||||||
size_t size = align_up(used, os::vm_allocation_granularity());
|
|
||||||
|
|
||||||
if (mapped_base != NULL) {
|
if (mapped_base != NULL) {
|
||||||
if (size > 0 && si->mapped_from_file()) {
|
if (size > 0 && si->mapped_from_file()) {
|
||||||
|
@ -154,7 +154,7 @@ public:
|
|||||||
size_t mapping_offset() const { return _mapping_offset; }
|
size_t mapping_offset() const { return _mapping_offset; }
|
||||||
size_t mapping_end_offset() const { return _mapping_offset + used_aligned(); }
|
size_t mapping_end_offset() const { return _mapping_offset + used_aligned(); }
|
||||||
size_t used() const { return _used; }
|
size_t used() const { return _used; }
|
||||||
size_t used_aligned() const; // aligned up to os::vm_allocation_granularity()
|
size_t used_aligned() const; // aligned up to MetaspaceShared::core_region_alignment()
|
||||||
char* mapped_base() const { assert_is_not_heap_region(); return _mapped_base; }
|
char* mapped_base() const { assert_is_not_heap_region(); return _mapped_base; }
|
||||||
char* mapped_end() const { return mapped_base() + used_aligned(); }
|
char* mapped_end() const { return mapped_base() + used_aligned(); }
|
||||||
bool read_only() const { return _read_only != 0; }
|
bool read_only() const { return _read_only != 0; }
|
||||||
@ -187,7 +187,7 @@ class FileMapHeader: private CDSFileMapHeaderBase {
|
|||||||
// The following fields record the states of the VM during dump time.
|
// The following fields record the states of the VM during dump time.
|
||||||
// They are compared with the runtime states to see if the archive
|
// They are compared with the runtime states to see if the archive
|
||||||
// can be used.
|
// can be used.
|
||||||
size_t _alignment; // how shared archive should be aligned
|
size_t _core_region_alignment; // how shared archive should be aligned
|
||||||
int _obj_alignment; // value of ObjectAlignmentInBytes
|
int _obj_alignment; // value of ObjectAlignmentInBytes
|
||||||
address _narrow_oop_base; // compressed oop encoding base
|
address _narrow_oop_base; // compressed oop encoding base
|
||||||
int _narrow_oop_shift; // compressed oop encoding shift
|
int _narrow_oop_shift; // compressed oop encoding shift
|
||||||
@ -251,7 +251,7 @@ public:
|
|||||||
// Accessors -- fields declared in FileMapHeader
|
// Accessors -- fields declared in FileMapHeader
|
||||||
|
|
||||||
size_t header_size() const { return _header_size; }
|
size_t header_size() const { return _header_size; }
|
||||||
size_t alignment() const { return _alignment; }
|
size_t core_region_alignment() const { return _core_region_alignment; }
|
||||||
int obj_alignment() const { return _obj_alignment; }
|
int obj_alignment() const { return _obj_alignment; }
|
||||||
address narrow_oop_base() const { return _narrow_oop_base; }
|
address narrow_oop_base() const { return _narrow_oop_base; }
|
||||||
int narrow_oop_shift() const { return _narrow_oop_shift; }
|
int narrow_oop_shift() const { return _narrow_oop_shift; }
|
||||||
@ -312,7 +312,7 @@ public:
|
|||||||
return FileMapRegion::cast(&_space[i]);
|
return FileMapRegion::cast(&_space[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void populate(FileMapInfo* info, size_t alignment);
|
void populate(FileMapInfo* info, size_t core_region_alignment);
|
||||||
|
|
||||||
static bool is_valid_region(int region) {
|
static bool is_valid_region(int region) {
|
||||||
return (0 <= region && region < NUM_CDS_REGIONS);
|
return (0 <= region && region < NUM_CDS_REGIONS);
|
||||||
@ -378,17 +378,17 @@ public:
|
|||||||
int compute_header_crc() const { return header()->compute_crc(); }
|
int compute_header_crc() const { return header()->compute_crc(); }
|
||||||
void set_header_crc(int crc) { header()->set_crc(crc); }
|
void set_header_crc(int crc) { header()->set_crc(crc); }
|
||||||
int space_crc(int i) const { return space_at(i)->crc(); }
|
int space_crc(int i) const { return space_at(i)->crc(); }
|
||||||
void populate_header(size_t alignment);
|
void populate_header(size_t core_region_alignment);
|
||||||
bool validate_header();
|
bool validate_header();
|
||||||
void invalidate();
|
void invalidate();
|
||||||
int crc() const { return header()->crc(); }
|
int crc() const { return header()->crc(); }
|
||||||
int version() const { return header()->version(); }
|
int version() const { return header()->version(); }
|
||||||
size_t alignment() const { return header()->alignment(); }
|
|
||||||
address narrow_oop_base() const { return header()->narrow_oop_base(); }
|
address narrow_oop_base() const { return header()->narrow_oop_base(); }
|
||||||
int narrow_oop_shift() const { return header()->narrow_oop_shift(); }
|
int narrow_oop_shift() const { return header()->narrow_oop_shift(); }
|
||||||
uintx max_heap_size() const { return header()->max_heap_size(); }
|
uintx max_heap_size() const { return header()->max_heap_size(); }
|
||||||
address narrow_klass_base() const { return header()->narrow_klass_base(); }
|
address narrow_klass_base() const { return header()->narrow_klass_base(); }
|
||||||
int narrow_klass_shift() const { return header()->narrow_klass_shift(); }
|
int narrow_klass_shift() const { return header()->narrow_klass_shift(); }
|
||||||
|
size_t core_region_alignment() const { return header()->core_region_alignment(); }
|
||||||
|
|
||||||
CompressedOops::Mode narrow_oop_mode() const { return header()->narrow_oop_mode(); }
|
CompressedOops::Mode narrow_oop_mode() const { return header()->narrow_oop_mode(); }
|
||||||
jshort app_module_paths_start_index() const { return header()->app_module_paths_start_index(); }
|
jshort app_module_paths_start_index() const { return header()->app_module_paths_start_index(); }
|
||||||
|
@ -120,7 +120,21 @@ char* MetaspaceShared::symbol_space_alloc(size_t num_bytes) {
|
|||||||
return _symbol_region.allocate(num_bytes);
|
return _symbol_region.allocate(num_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MetaspaceShared::reserved_space_alignment() { return os::vm_allocation_granularity(); }
|
// os::vm_allocation_granularity() is usually 4K for most OSes. However, on Linux/aarch64,
|
||||||
|
// it can be either 4K or 64K and on Macosx-arm it is 16K. To generate archives that are
|
||||||
|
// compatible for both settings, an alternative cds core region alignment can be enabled
|
||||||
|
// at building time:
|
||||||
|
// --enable-compactible-cds-alignment
|
||||||
|
// Upon successful configuration, the compactible alignment then can be defined as in:
|
||||||
|
// os_linux_aarch64.hpp
|
||||||
|
// which is the highest page size configured on the platform.
|
||||||
|
size_t MetaspaceShared::core_region_alignment() {
|
||||||
|
#if defined(CDS_CORE_REGION_ALIGNMENT)
|
||||||
|
return CDS_CORE_REGION_ALIGNMENT;
|
||||||
|
#else
|
||||||
|
return (size_t)os::vm_allocation_granularity();
|
||||||
|
#endif // CDS_CORE_REGION_ALIGNMENT
|
||||||
|
}
|
||||||
|
|
||||||
static bool shared_base_valid(char* shared_base) {
|
static bool shared_base_valid(char* shared_base) {
|
||||||
#ifdef _LP64
|
#ifdef _LP64
|
||||||
@ -133,7 +147,7 @@ static bool shared_base_valid(char* shared_base) {
|
|||||||
static bool shared_base_too_high(char* specified_base, char* aligned_base, size_t cds_max) {
|
static bool shared_base_too_high(char* specified_base, char* aligned_base, size_t cds_max) {
|
||||||
if (specified_base != NULL && aligned_base < specified_base) {
|
if (specified_base != NULL && aligned_base < specified_base) {
|
||||||
// SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so
|
// SharedBaseAddress is very high (e.g., 0xffffffffffffff00) so
|
||||||
// align_up(SharedBaseAddress, MetaspaceShared::reserved_space_alignment()) has wrapped around.
|
// align_up(SharedBaseAddress, MetaspaceShared::core_region_alignment()) has wrapped around.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (max_uintx - uintx(aligned_base) < uintx(cds_max)) {
|
if (max_uintx - uintx(aligned_base) < uintx(cds_max)) {
|
||||||
@ -146,7 +160,7 @@ static bool shared_base_too_high(char* specified_base, char* aligned_base, size_
|
|||||||
|
|
||||||
static char* compute_shared_base(size_t cds_max) {
|
static char* compute_shared_base(size_t cds_max) {
|
||||||
char* specified_base = (char*)SharedBaseAddress;
|
char* specified_base = (char*)SharedBaseAddress;
|
||||||
char* aligned_base = align_up(specified_base, MetaspaceShared::reserved_space_alignment());
|
char* aligned_base = align_up(specified_base, MetaspaceShared::core_region_alignment());
|
||||||
|
|
||||||
const char* err = NULL;
|
const char* err = NULL;
|
||||||
if (shared_base_too_high(specified_base, aligned_base, cds_max)) {
|
if (shared_base_too_high(specified_base, aligned_base, cds_max)) {
|
||||||
@ -162,7 +176,7 @@ static char* compute_shared_base(size_t cds_max) {
|
|||||||
p2i((void*)Arguments::default_SharedBaseAddress()));
|
p2i((void*)Arguments::default_SharedBaseAddress()));
|
||||||
|
|
||||||
specified_base = (char*)Arguments::default_SharedBaseAddress();
|
specified_base = (char*)Arguments::default_SharedBaseAddress();
|
||||||
aligned_base = align_up(specified_base, MetaspaceShared::reserved_space_alignment());
|
aligned_base = align_up(specified_base, MetaspaceShared::core_region_alignment());
|
||||||
|
|
||||||
// Make sure the default value of SharedBaseAddress specified in globals.hpp is sane.
|
// Make sure the default value of SharedBaseAddress specified in globals.hpp is sane.
|
||||||
assert(!shared_base_too_high(specified_base, aligned_base, cds_max), "Sanity");
|
assert(!shared_base_too_high(specified_base, aligned_base, cds_max), "Sanity");
|
||||||
@ -172,11 +186,11 @@ static char* compute_shared_base(size_t cds_max) {
|
|||||||
|
|
||||||
void MetaspaceShared::initialize_for_static_dump() {
|
void MetaspaceShared::initialize_for_static_dump() {
|
||||||
assert(DumpSharedSpaces, "should be called for dump time only");
|
assert(DumpSharedSpaces, "should be called for dump time only");
|
||||||
|
log_info(cds)("Core region alignment: " SIZE_FORMAT, core_region_alignment());
|
||||||
// The max allowed size for CDS archive. We use this to limit SharedBaseAddress
|
// The max allowed size for CDS archive. We use this to limit SharedBaseAddress
|
||||||
// to avoid address space wrap around.
|
// to avoid address space wrap around.
|
||||||
size_t cds_max;
|
size_t cds_max;
|
||||||
const size_t reserve_alignment = MetaspaceShared::reserved_space_alignment();
|
const size_t reserve_alignment = core_region_alignment();
|
||||||
|
|
||||||
#ifdef _LP64
|
#ifdef _LP64
|
||||||
const uint64_t UnscaledClassSpaceMax = (uint64_t(max_juint) + 1);
|
const uint64_t UnscaledClassSpaceMax = (uint64_t(max_juint) + 1);
|
||||||
@ -491,7 +505,7 @@ void VM_PopulateDumpSharedSpace::doit() {
|
|||||||
|
|
||||||
// Write the archive file
|
// Write the archive file
|
||||||
FileMapInfo* mapinfo = new FileMapInfo(true);
|
FileMapInfo* mapinfo = new FileMapInfo(true);
|
||||||
mapinfo->populate_header(os::vm_allocation_granularity());
|
mapinfo->populate_header(MetaspaceShared::core_region_alignment());
|
||||||
mapinfo->set_serialized_data(serialized_data);
|
mapinfo->set_serialized_data(serialized_data);
|
||||||
mapinfo->set_cloned_vtables(cloned_vtables);
|
mapinfo->set_cloned_vtables(cloned_vtables);
|
||||||
mapinfo->open_for_write();
|
mapinfo->open_for_write();
|
||||||
@ -854,6 +868,7 @@ void MetaspaceShared::initialize_runtime_shared_and_meta_spaces() {
|
|||||||
FileMapInfo* dynamic_mapinfo = NULL;
|
FileMapInfo* dynamic_mapinfo = NULL;
|
||||||
|
|
||||||
if (static_mapinfo != NULL) {
|
if (static_mapinfo != NULL) {
|
||||||
|
log_info(cds)("Core region alignment: " SIZE_FORMAT, static_mapinfo->core_region_alignment());
|
||||||
dynamic_mapinfo = open_dynamic_archive();
|
dynamic_mapinfo = open_dynamic_archive();
|
||||||
|
|
||||||
// First try to map at the requested address
|
// First try to map at the requested address
|
||||||
@ -974,7 +989,7 @@ MapArchiveResult MetaspaceShared::map_archives(FileMapInfo* static_mapinfo, File
|
|||||||
assert(class_space_rs.base() >= archive_space_rs.end(),
|
assert(class_space_rs.base() >= archive_space_rs.end(),
|
||||||
"class space should follow the cds archive space");
|
"class space should follow the cds archive space");
|
||||||
assert(is_aligned(archive_space_rs.base(),
|
assert(is_aligned(archive_space_rs.base(),
|
||||||
MetaspaceShared::reserved_space_alignment()),
|
core_region_alignment()),
|
||||||
"Archive space misaligned");
|
"Archive space misaligned");
|
||||||
assert(is_aligned(class_space_rs.base(),
|
assert(is_aligned(class_space_rs.base(),
|
||||||
Metaspace::reserve_alignment()),
|
Metaspace::reserve_alignment()),
|
||||||
@ -982,9 +997,9 @@ MapArchiveResult MetaspaceShared::map_archives(FileMapInfo* static_mapinfo, File
|
|||||||
}
|
}
|
||||||
#endif // ASSERT
|
#endif // ASSERT
|
||||||
|
|
||||||
log_debug(cds)("Reserved archive_space_rs [" INTPTR_FORMAT " - " INTPTR_FORMAT "] (" SIZE_FORMAT ") bytes",
|
log_info(cds)("Reserved archive_space_rs [" INTPTR_FORMAT " - " INTPTR_FORMAT "] (" SIZE_FORMAT ") bytes",
|
||||||
p2i(archive_space_rs.base()), p2i(archive_space_rs.end()), archive_space_rs.size());
|
p2i(archive_space_rs.base()), p2i(archive_space_rs.end()), archive_space_rs.size());
|
||||||
log_debug(cds)("Reserved class_space_rs [" INTPTR_FORMAT " - " INTPTR_FORMAT "] (" SIZE_FORMAT ") bytes",
|
log_info(cds)("Reserved class_space_rs [" INTPTR_FORMAT " - " INTPTR_FORMAT "] (" SIZE_FORMAT ") bytes",
|
||||||
p2i(class_space_rs.base()), p2i(class_space_rs.end()), class_space_rs.size());
|
p2i(class_space_rs.base()), p2i(class_space_rs.end()), class_space_rs.size());
|
||||||
|
|
||||||
if (MetaspaceShared::use_windows_memory_mapping()) {
|
if (MetaspaceShared::use_windows_memory_mapping()) {
|
||||||
@ -1146,7 +1161,7 @@ char* MetaspaceShared::reserve_address_space_for_archives(FileMapInfo* static_ma
|
|||||||
ReservedSpace& class_space_rs) {
|
ReservedSpace& class_space_rs) {
|
||||||
|
|
||||||
address const base_address = (address) (use_archive_base_addr ? static_mapinfo->requested_base_address() : NULL);
|
address const base_address = (address) (use_archive_base_addr ? static_mapinfo->requested_base_address() : NULL);
|
||||||
const size_t archive_space_alignment = MetaspaceShared::reserved_space_alignment();
|
const size_t archive_space_alignment = core_region_alignment();
|
||||||
|
|
||||||
// Size and requested location of the archive_space_rs (for both static and dynamic archives)
|
// Size and requested location of the archive_space_rs (for both static and dynamic archives)
|
||||||
assert(static_mapinfo->mapping_base_offset() == 0, "Must be");
|
assert(static_mapinfo->mapping_base_offset() == 0, "Must be");
|
||||||
@ -1204,8 +1219,7 @@ char* MetaspaceShared::reserve_address_space_for_archives(FileMapInfo* static_ma
|
|||||||
const size_t gap_size = ccs_begin_offset - archive_space_size;
|
const size_t gap_size = ccs_begin_offset - archive_space_size;
|
||||||
|
|
||||||
const size_t total_range_size =
|
const size_t total_range_size =
|
||||||
align_up(archive_space_size + gap_size + class_space_size,
|
align_up(archive_space_size + gap_size + class_space_size, core_region_alignment());
|
||||||
os::vm_allocation_granularity());
|
|
||||||
|
|
||||||
assert(total_range_size > ccs_begin_offset, "must be");
|
assert(total_range_size > ccs_begin_offset, "must be");
|
||||||
if (use_windows_memory_mapping() && use_archive_base_addr) {
|
if (use_windows_memory_mapping() && use_archive_base_addr) {
|
||||||
@ -1248,7 +1262,7 @@ char* MetaspaceShared::reserve_address_space_for_archives(FileMapInfo* static_ma
|
|||||||
// Now split up the space into ccs and cds archive. For simplicity, just leave
|
// Now split up the space into ccs and cds archive. For simplicity, just leave
|
||||||
// the gap reserved at the end of the archive space. Do not do real splitting.
|
// the gap reserved at the end of the archive space. Do not do real splitting.
|
||||||
archive_space_rs = total_space_rs.first_part(ccs_begin_offset,
|
archive_space_rs = total_space_rs.first_part(ccs_begin_offset,
|
||||||
(size_t)os::vm_allocation_granularity());
|
(size_t)archive_space_alignment);
|
||||||
class_space_rs = total_space_rs.last_part(ccs_begin_offset);
|
class_space_rs = total_space_rs.last_part(ccs_begin_offset);
|
||||||
MemTracker::record_virtual_memory_split_reserved(total_space_rs.base(), total_space_rs.size(),
|
MemTracker::record_virtual_memory_split_reserved(total_space_rs.base(), total_space_rs.size(),
|
||||||
ccs_begin_offset);
|
ccs_begin_offset);
|
||||||
@ -1299,10 +1313,9 @@ MapArchiveResult MetaspaceShared::map_archive(FileMapInfo* mapinfo, char* mapped
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapinfo->set_is_mapped(false);
|
mapinfo->set_is_mapped(false);
|
||||||
|
if (mapinfo->core_region_alignment() != (size_t)core_region_alignment()) {
|
||||||
if (mapinfo->alignment() != (size_t)os::vm_allocation_granularity()) {
|
log_info(cds)("Unable to map CDS archive -- core_region_alignment() expected: " SIZE_FORMAT
|
||||||
log_info(cds)("Unable to map CDS archive -- os::vm_allocation_granularity() expected: " SIZE_FORMAT
|
" actual: " SIZE_FORMAT, mapinfo->core_region_alignment(), core_region_alignment());
|
||||||
" actual: %d", mapinfo->alignment(), os::vm_allocation_granularity());
|
|
||||||
return MAP_ARCHIVE_OTHER_FAILURE;
|
return MAP_ARCHIVE_OTHER_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,9 @@ class MetaspaceShared : AllStatic {
|
|||||||
static bool linking_required(InstanceKlass* ik) NOT_CDS_RETURN_(false);
|
static bool linking_required(InstanceKlass* ik) NOT_CDS_RETURN_(false);
|
||||||
|
|
||||||
#if INCLUDE_CDS
|
#if INCLUDE_CDS
|
||||||
static size_t reserved_space_alignment();
|
// Alignment for the 3 core CDS regions (MC/RW/RO) only.
|
||||||
|
// (Heap region alignments are decided by GC).
|
||||||
|
static size_t core_region_alignment();
|
||||||
static void rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread* thread, InstanceKlass* ik);
|
static void rewrite_nofast_bytecodes_and_calculate_fingerprints(Thread* thread, InstanceKlass* ik);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @requires vm.cds
|
||||||
|
* @requires vm.gc != "Z"
|
||||||
|
* @summary Testing handling of CDS region alignment
|
||||||
|
* @comment ZGC may exit the VM if -XX:+UseLargePages is specified but
|
||||||
|
* unavailable. Since this test is independent of the actual GC type, let's
|
||||||
|
* disable it if ZGC is used.
|
||||||
|
* @bug 8236847
|
||||||
|
* @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes
|
||||||
|
* @build Hello
|
||||||
|
* @run driver ClassFileInstaller -jar hello.jar Hello
|
||||||
|
* @run driver SharedRegionAlignmentTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
|
||||||
|
public class SharedRegionAlignmentTest {
|
||||||
|
static String appJar = ClassFileInstaller.getJarPath("hello.jar");
|
||||||
|
static String mainClass = "Hello";
|
||||||
|
static String logArg = "-Xlog:cds";
|
||||||
|
|
||||||
|
static void testCombo() throws Exception {
|
||||||
|
// Test the following combinations:
|
||||||
|
// Dump (3 combinations): largePageArgs
|
||||||
|
// Run (3 combinations): largePageArgs
|
||||||
|
String UseLargePages = "-XX:+UseLargePages";
|
||||||
|
|
||||||
|
String [][] largePageArgs = {
|
||||||
|
{}, // default
|
||||||
|
{UseLargePages}
|
||||||
|
};
|
||||||
|
|
||||||
|
final String logFor64K = "core_region_alignment = 65535";
|
||||||
|
|
||||||
|
int dumpCase = 0;
|
||||||
|
for (String[] dumpLP: largePageArgs) {
|
||||||
|
dumpCase ++;
|
||||||
|
System.out.println("============================================================");
|
||||||
|
System.out.println("dump case (" + dumpCase + "): " + formatLargePageArgs(dumpLP));
|
||||||
|
System.out.println("============================================================");
|
||||||
|
|
||||||
|
OutputAnalyzer out = TestCommon.dump(appJar,
|
||||||
|
TestCommon.list(mainClass),
|
||||||
|
TestCommon.concat(dumpLP, logArg));
|
||||||
|
out.shouldContain("Dumping shared data to file");
|
||||||
|
boolean is_aligned_64k = out.getStdout().contains(logFor64K);
|
||||||
|
|
||||||
|
int runCase = 0;
|
||||||
|
for (String[] runLP: largePageArgs) {
|
||||||
|
runCase++;
|
||||||
|
System.out.println("--------------------------------------------------");
|
||||||
|
System.out.println("run case (" + dumpCase + "." + runCase + "):" + formatLargePageArgs(runLP));
|
||||||
|
System.out.println("--------------------------------------------------");
|
||||||
|
|
||||||
|
TestCommon.run(TestCommon.concat(runLP, "-cp", appJar, logArg, mainClass))
|
||||||
|
.assertNormalExit(output -> {
|
||||||
|
if (is_aligned_64k) {
|
||||||
|
output.shouldContain(logFor64K);
|
||||||
|
}
|
||||||
|
output.shouldContain("Hello World");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatLargePageArgs(String args[]) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String a : args) {
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(a);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
testCombo();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user