diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp
index ba687bea8bac7..6a442c454ca0f 100644
--- a/src/hotspot/share/cds/metaspaceShared.cpp
+++ b/src/hotspot/share/cds/metaspaceShared.cpp
@@ -214,30 +214,9 @@ void MetaspaceShared::dump_loaded_classes(const char* file_name, TRAPS) {
   }
 }
 
-// If p is not aligned, move it up to the next address that's aligned with alignment.
-// If this is not possible (because p is too high), return nullptr. Example:
-//     p = 0xffffffffffff0000, alignment= 0x10000    => return nullptr.
-static char* align_up_or_null(char* p, size_t alignment) {
-  assert(p != nullptr, "sanity");
-  if (is_aligned(p, alignment)) {
-    return p;
-  }
-
-  char* down = align_down(p, alignment);
-  if (max_uintx - uintx(down) < uintx(alignment)) {
-    // Run out of address space to align up.
-    return nullptr;
-  }
-
-  char* aligned = align_up(p, alignment);
-  assert(aligned >= p, "sanity");
-  assert(aligned != nullptr, "sanity");
-  return aligned;
-}
-
 static bool shared_base_too_high(char* specified_base, char* aligned_base, size_t cds_max) {
-  // Caller should have checked if align_up_or_null( returns nullptr (comparing specified_base
-  // with nullptr is UB).
+  // Caller should have checked that aligned_base was successfully aligned and is not nullptr.
+  // Comparing specified_base with nullptr is UB.
   assert(aligned_base != nullptr, "sanity");
   assert(aligned_base >= specified_base, "sanity");
 
@@ -261,7 +240,9 @@ static char* compute_shared_base(size_t cds_max) {
     return specified_base;
   }
 
-  char* aligned_base = align_up_or_null(specified_base, alignment);
+  char* aligned_base = can_align_up(specified_base, alignment)
+                           ? align_up(specified_base, alignment)
+                           : nullptr;
 
   if (aligned_base != specified_base) {
     log_info(cds)("SharedBaseAddress (" INTPTR_FORMAT ") aligned up to " INTPTR_FORMAT,
diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp
index ab74d3468e19e..a32897946b9e4 100644
--- a/src/hotspot/share/gc/parallel/psOldGen.cpp
+++ b/src/hotspot/share/gc/parallel/psOldGen.cpp
@@ -189,8 +189,12 @@ bool PSOldGen::expand(size_t bytes) {
   assert_locked_or_safepoint(Heap_lock);
   assert(bytes > 0, "precondition");
 #endif
+  const size_t remaining_bytes = virtual_space()->uncommitted_size();
+  if (remaining_bytes == 0) {
+    return false;
+  }
   const size_t alignment = virtual_space()->alignment();
-  size_t aligned_bytes  = align_up(bytes, alignment);
+  size_t aligned_bytes = align_up(MIN2(bytes, remaining_bytes), alignment);
   size_t aligned_expand_bytes = align_up(MinHeapDeltaBytes, alignment);
 
   if (UseNUMA) {
@@ -198,13 +202,6 @@ bool PSOldGen::expand(size_t bytes) {
     // providing a page per lgroup. Alignment is larger or equal to the page size.
     aligned_expand_bytes = MAX2(aligned_expand_bytes, alignment * os::numa_get_groups_num());
   }
-  if (aligned_bytes == 0) {
-    // The alignment caused the number of bytes to wrap.  A call to expand
-    // implies a best effort to expand by "bytes" but not a guarantee.  Align
-    // down to give a best effort.  This is likely the most that the generation
-    // can expand since it has some capacity to start with.
-    aligned_bytes = align_down(bytes, alignment);
-  }
 
   bool success = false;
   if (aligned_expand_bytes > aligned_bytes) {
diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp
index 11c13fe3c9e19..040eb4695fbd8 100644
--- a/src/hotspot/share/runtime/globals.hpp
+++ b/src/hotspot/share/runtime/globals.hpp
@@ -1420,7 +1420,7 @@ const int ObjectAlignmentInBytes = 8;
                                                                             \
   product(size_t, MinHeapDeltaBytes, ScaleForWordSize(128*K),               \
           "The minimum change in heap space due to GC (in bytes)")          \
-          range(0, max_uintx)                                               \
+          range(0, max_uintx / 2 + 1)                                       \
                                                                             \
   product(size_t, MinMetaspaceExpansion, ScaleForWordSize(256*K),           \
           "The minimum expansion of Metaspace (in bytes)")                  \
diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp
index d266b632ed1c3..be9b0eef45535 100644
--- a/src/hotspot/share/runtime/os.cpp
+++ b/src/hotspot/share/runtime/os.cpp
@@ -2011,10 +2011,10 @@ char* os::attempt_reserve_memory_between(char* min, char* max, size_t bytes, siz
   const size_t alignment_adjusted = MAX2(alignment, system_allocation_granularity);
 
   // Calculate first and last possible attach points:
-  char* const lo_att = align_up(MAX2(absolute_min, min), alignment_adjusted);
-  if (lo_att == nullptr) {
+  if (!can_align_up(MAX2(absolute_min, min), alignment_adjusted)) {
     return nullptr; // overflow
   }
+  char* const lo_att = align_up(MAX2(absolute_min, min), alignment_adjusted);
 
   char* const hi_end = MIN2(max, absolute_max);
   if ((uintptr_t)hi_end <= bytes) {
diff --git a/src/hotspot/share/utilities/align.hpp b/src/hotspot/share/utilities/align.hpp
index b67e61036a007..69dd1a3052bc6 100644
--- a/src/hotspot/share/utilities/align.hpp
+++ b/src/hotspot/share/utilities/align.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2025, 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
@@ -70,8 +70,15 @@ constexpr T align_down(T size, A alignment) {
   return result;
 }
 
+// Checks whether it is possible to align size to alignment without overflowing.
+template<typename T, typename A, ENABLE_IF(std::is_integral<T>::value)>
+constexpr bool can_align_up(T size, A alignment) {
+  return align_down(std::numeric_limits<T>::max(), alignment) >= size;
+}
+
 template<typename T, typename A, ENABLE_IF(std::is_integral<T>::value)>
 constexpr T align_up(T size, A alignment) {
+  assert(can_align_up(size, alignment), "precondition");
   T adjusted = checked_cast<T>(size + alignment_mask(alignment));
   return align_down(adjusted, alignment);
 }
@@ -85,6 +92,12 @@ constexpr T align_down_bounded(T size, A alignment) {
 
 // Align pointers and check for alignment.
 
+template <typename A>
+inline bool can_align_up(void* ptr, A alignment) {
+  static_assert(sizeof(ptr) == sizeof(uintptr_t), "assumption");
+  return can_align_up((uintptr_t)ptr, alignment);
+}
+
 template <typename T, typename A>
 inline T* align_up(T* ptr, A alignment) {
   return (T*)align_up((uintptr_t)ptr, alignment);
diff --git a/test/hotspot/gtest/utilities/test_align.cpp b/test/hotspot/gtest/utilities/test_align.cpp
index 3c03fd5f24d9f..7413e58e7ab44 100644
--- a/test/hotspot/gtest/utilities/test_align.cpp
+++ b/test/hotspot/gtest/utilities/test_align.cpp
@@ -29,7 +29,7 @@
 #include <limits>
 
 // A few arbitrarily chosen values to test the align functions on.
-static constexpr uint64_t values[] = {1, 3, 10, 345, 1023, 1024, 1025, 23909034, INT_MAX, uint64_t(-1) / 2, uint64_t(-1) / 2 + 100, uint64_t(-1)};
+static constexpr uint64_t values[] = {1, 3, 10, 345, 1023, 1024, 1025, 23909034, INT_MAX, uint64_t(-1) / 2, uint64_t(-1) / 2 + 100, ~(uint64_t(1) << 62)};
 
 template <typename T>
 static constexpr T max_alignment() {
@@ -194,3 +194,78 @@ TEST(Align, alignments) {
 
   test_alignments<int8_t, int8_t>();
 }
+
+template<typename T, typename A>
+static constexpr void test_can_align_up() {
+  int alignment_value = 4;
+  int small_value = 63;
+  A alignment = static_cast<A>(alignment_value);
+
+  ASSERT_TRUE(can_align_up(static_cast<T>(small_value), alignment));
+  ASSERT_TRUE(can_align_up(static_cast<T>(-small_value), alignment));
+  ASSERT_TRUE(can_align_up(std::numeric_limits<T>::min(), alignment));
+  ASSERT_FALSE(can_align_up(std::numeric_limits<T>::max(), alignment));
+  ASSERT_FALSE(can_align_up(std::numeric_limits<T>::max() - 1, alignment));
+  ASSERT_TRUE(can_align_up(align_down(std::numeric_limits<T>::max(), alignment), alignment));
+  ASSERT_FALSE(can_align_up(align_down(std::numeric_limits<T>::max(), alignment) + 1, alignment));
+  if (std::is_signed<T>::value) {
+    ASSERT_TRUE(can_align_up(static_cast<T>(-1), alignment));
+    ASSERT_TRUE(can_align_up(align_down(static_cast<T>(-1), alignment), alignment));
+    ASSERT_TRUE(can_align_up(align_down(static_cast<T>(-1) + 1, alignment), alignment));
+  }
+}
+
+TEST(Align, test_can_align_up_int32_int32) {
+  test_can_align_up<int32_t, int32_t>();
+}
+
+TEST(Align, test_can_align_up_uint32_uint32) {
+  test_can_align_up<uint32_t, uint32_t>();
+}
+
+TEST(Align, test_can_align_up_int32_uint32) {
+  test_can_align_up<int32_t, uint32_t>();
+}
+
+TEST(Align, test_can_align_up_uint32_int32) {
+  test_can_align_up<uint32_t, int32_t>();
+}
+
+TEST(Align, test_can_align_up_ptr) {
+  uint alignment = 4;
+  char buffer[8];
+
+  ASSERT_TRUE(can_align_up(buffer, alignment));
+  ASSERT_FALSE(can_align_up(reinterpret_cast<void*>(UINTPTR_MAX), alignment));
+}
+
+#ifdef ASSERT
+template <typename T, typename A>
+static void test_fail_alignment() {
+  A alignment = max_alignment<A>();
+  T value = align_down(std::numeric_limits<T>::max(), alignment) + 1;
+  // Aligning value to alignment would now overflow.
+  // Assert inside align_up expected.
+  T aligned = align_up(value, alignment);
+}
+
+TEST_VM_ASSERT(Align, fail_alignments_same_size) {
+  test_fail_alignment<uint64_t, uint64_t>();
+}
+
+TEST_VM_ASSERT(Align, fail_alignments_unsigned_signed) {
+  test_fail_alignment<uint32_t, int32_t>();
+}
+
+TEST_VM_ASSERT(Align, fail_alignments_signed_unsigned) {
+  test_fail_alignment<int64_t, uint32_t>();
+}
+
+TEST_VM_ASSERT(Align, fail_alignments_small_large) {
+  test_fail_alignment<uint8_t, uint64_t>();
+}
+
+TEST_VM_ASSERT(Align, fail_alignments_large_small) {
+  test_fail_alignment<uint64_t, uint8_t>();
+}
+#endif // ASSERT