diff --git a/.github/actions/upload-bundles/action.yml b/.github/actions/upload-bundles/action.yml
index 94308002ea7a..ab6413aca401 100644
--- a/.github/actions/upload-bundles/action.yml
+++ b/.github/actions/upload-bundles/action.yml
@@ -47,13 +47,14 @@ runs:
id: bundles
run: |
# Rename bundles to consistent names
- jdk_bundle_zip="$(ls build/*/bundles/jdk-*_bin${{ inputs.debug-suffix }}.zip 2> /dev/null || true)"
- jdk_bundle_tar_gz="$(ls build/*/bundles/jdk-*_bin${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
- static_jdk_bundle_zip="$(ls build/*/bundles/static-jdk-*_bin${{ inputs.debug-suffix }}.zip 2> /dev/null || true)"
- static_jdk_bundle_tar_gz="$(ls build/*/bundles/static-jdk-*_bin${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
- symbols_bundle="$(ls build/*/bundles/jdk-*_bin${{ inputs.debug-suffix }}-symbols.tar.gz 2> /dev/null || true)"
- tests_bundle="$(ls build/*/bundles/jdk-*_bin-tests${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
- static_libs_bundle="$(ls build/*/bundles/jdk-*_bin-static-libs${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
+ # SapMachine 2020-11-04: Adapt bundle names
+ jdk_bundle_zip="$(ls build/*/bundles/sapmachine-jdk-*_bin${{ inputs.debug-suffix }}.zip 2> /dev/null || true)"
+ jdk_bundle_tar_gz="$(ls build/*/bundles/sapmachine-jdk-*_bin${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
+ static_jdk_bundle_zip="$(ls build/*/bundles/sapmachine-static-jdk-*_bin${{ inputs.debug-suffix }}.zip 2> /dev/null || true)"
+ static_jdk_bundle_tar_gz="$(ls build/*/bundles/sapmachine-static-jdk-*_bin${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
+ symbols_bundle="$(ls build/*/bundles/sapmachine-jdk-*_bin${{ inputs.debug-suffix }}-symbols.tar.gz 2> /dev/null || true)"
+ tests_bundle="$(ls build/*/bundles/sapmachine-jdk-*_bin-tests${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
+ static_libs_bundle="$(ls build/*/bundles/sapmachine-jdk-*_bin-static-libs${{ inputs.debug-suffix }}.tar.gz 2> /dev/null || true)"
mkdir bundles
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d3f63784ecec..916173756f05 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,5 +1,15 @@
+
+
+Description
----------
-- [ ] I confirm that I make this contribution in accordance with the [OpenJDK Interim AI Policy](https://openjdk.org/legal/ai).
+
+fixes #Issue
diff --git a/.github/workflows/build-alpine-linux.yml b/.github/workflows/build-alpine-linux.yml
index 6863da9016e4..0abc83b3b5f0 100644
--- a/.github/workflows/build-alpine-linux.yml
+++ b/.github/workflows/build-alpine-linux.yml
@@ -87,6 +87,7 @@ jobs:
with:
platform: alpine-linux-x64
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives
- name: 'Configure'
run: >
bash configure
@@ -98,6 +99,7 @@ jobs:
--with-jmod-compress=zip-1
--with-external-symbols-in-bundles=none
--with-native-debug-symbols-level=1
+ --with-vendor-name="SAP SE"
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-cross-compile.yml b/.github/workflows/build-cross-compile.yml
index c80f676864e0..6941143d5ace 100644
--- a/.github/workflows/build-cross-compile.yml
+++ b/.github/workflows/build-cross-compile.yml
@@ -159,6 +159,7 @@ jobs:
sudo rm -rf sysroot/
if: steps.create-sysroot.outcome != 'success' && steps.get-cached-sysroot.outputs.cache-hit != 'true'
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives
- name: 'Configure'
run: >
bash configure
@@ -174,6 +175,7 @@ jobs:
--with-jmod-compress=zip-1
--with-external-symbols-in-bundles=none
--with-native-debug-symbols-level=1
+ --with-vendor-name="SAP SE"
CC=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-gcc-${{ inputs.gcc-major-version }}
CXX=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-g++-${{ inputs.gcc-major-version }}
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
diff --git a/.github/workflows/build-gh-pages.yml b/.github/workflows/build-gh-pages.yml
new file mode 100644
index 000000000000..d0d23ac3cbe0
--- /dev/null
+++ b/.github/workflows/build-gh-pages.yml
@@ -0,0 +1,71 @@
+# Workflow for building the sapmachine.io site and deploying it to GitHub Pages
+name: Build and deploy sapmachine.io page
+
+on:
+ # Allows to run this workflow manually from the Actions tab
+ workflow_dispatch:
+ inputs:
+ event_type:
+ description: 'Event type'
+ required: false
+ default: 'gh-page-build'
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+jobs:
+ # Build job
+ build:
+ environment:
+ name: github-pages
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo from GitHub Tools SAP
+ uses: actions/checkout@v5
+ with:
+ repository: SapMachine/SapMachineIOPage
+ token: ${{ secrets.GHE_PAT }}
+ github-server-url: https://github.tools.sap
+ ref: 'main'
+ sparse-checkout: |
+ redirect_index.html
+ redirect_404.html
+ assets/
+ jfrevents/
+ html-include/
+ sparse-checkout-cone-mode: true
+ - name: Prepare site folder
+ run: |
+ mkdir site
+ mv redirect_index.html site/index.html
+ mv redirect_404.html site/404.html
+ mv assets site/
+ mv jfrevents site/
+ mv html-include site/
+ - name: Setup Pages
+ uses: actions/configure-pages@v5
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: site
+
+ # Deployment job
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index a77ebece7e28..e9bf15f7fc91 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -140,6 +140,7 @@ jobs:
${{ inputs.apt-extra-packages }}
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${{ inputs.gcc-major-version }} 100 --slave /usr/bin/g++ g++ /usr/bin/g++-${{ inputs.gcc-major-version }}
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives
- name: 'Configure'
run: >
bash configure
@@ -153,6 +154,7 @@ jobs:
--with-jmod-compress=zip-1
--with-external-symbols-in-bundles=none
--with-native-debug-symbols-level=1
+ --with-vendor-name="SAP SE"
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
index 435576f4afd7..40c3ffd3907a 100644
--- a/.github/workflows/build-macos.yml
+++ b/.github/workflows/build-macos.yml
@@ -99,6 +99,7 @@ jobs:
# This will make GNU make available as 'make' and not only as 'gmake'
echo '/usr/local/opt/make/libexec/gnubin' >> $GITHUB_PATH
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives
- name: 'Configure'
run: >
bash configure
@@ -112,6 +113,7 @@ jobs:
--with-jmod-compress=zip-1
--with-external-symbols-in-bundles=none
--with-native-debug-symbols-level=1
+ --with-vendor-name="SAP SE"
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
index 002cbe7cd560..3f683f511d4a 100644
--- a/.github/workflows/build-windows.yml
+++ b/.github/workflows/build-windows.yml
@@ -151,6 +151,7 @@ jobs:
--add Microsoft.VisualStudio.Component.VC.${{ inputs.msvc-toolset-version }}.ARM64
if: ${{ ( inputs.architecture == 'ARM64') && (steps.toolchain-check-arm64.outputs.toolchain-installed != 'true') }}
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives
- name: 'Configure'
run: >
bash configure
@@ -163,6 +164,7 @@ jobs:
--with-msvc-toolset-version=${{ inputs.msvc-toolset-version }}
--with-jmod-compress=zip-1
--with-external-symbols-in-bundles=none
+ --with-vendor-name="SAP SE"
${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || (
echo "Dumping config.log:" &&
cat config.log &&
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index bcb9ea6e0b8b..3ad0d700a012 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,12 +23,20 @@
# questions.
#
-name: 'OpenJDK GHA Sanity Checks'
+# SapMachine 2022-06-22: Change the name of the GitHub Action
+name: 'SapMachine GHA Sanity Checks'
on:
push:
branches-ignore:
- pr/*
+ paths-ignore:
+ - '**/*.md'
+ - 'doc/**'
+ # SapMachine 2020-11-04: Trigger on pull request
+ pull_request:
+ branches:
+ - sapmachine
workflow_dispatch:
inputs:
platforms:
@@ -57,6 +65,8 @@ jobs:
prepare:
name: 'Prepare the run'
+ # SapMachine 2022-06-23: On 'pull_request' we only want to run GHA if the PR comes from a remote repo. Otherwise we have the run on 'push' already as a check.
+ if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }}
runs-on: ubuntu-24.04
env:
# List of platforms to exclude by default
@@ -103,14 +113,15 @@ jobs:
function check_platform() {
if [[ $GITHUB_EVENT_NAME == workflow_dispatch ]]; then
input='${{ github.event.inputs.platforms }}'
- elif [[ $GITHUB_EVENT_NAME == push ]]; then
- if [[ '${{ !secrets.JDK_SUBMIT_FILTER || startsWith(github.ref, 'refs/heads/submit/') }}' == 'false' ]]; then
+ # SapMachine 2022-06-24: Also handle 'pull_request' event.
+ elif [[ $GITHUB_EVENT_NAME == push ]] || [[ $GITHUB_EVENT_NAME == pull_request ]]; then
+ if [[ 'startsWith(github.ref, 'refs/heads/submit/') }}' == 'false' ]]; then
# If JDK_SUBMIT_FILTER is set, and this is not a "submit/" branch, don't run anything
>&2 echo 'JDK_SUBMIT_FILTER is set and not a "submit/" branch'
echo 'false'
return
else
- input='${{ secrets.JDK_SUBMIT_PLATFORMS }}'
+ input=''
fi
fi
@@ -165,11 +176,23 @@ jobs:
return
fi
+ # SapMachine 2025-07-14: Add sapmachine branch
+ if [[ $BRANCH == "sapmachine" ]]; then
+ echo 'true'
+ return
+ fi
+
# ...same for stabilization branches
if [[ $BRANCH =~ "jdk(.*)" ]]; then
echo 'true'
return
fi
+
+ # SapMachine 2025-07-14: Add sapmachine* branches
+ if [[ $BRANCH =~ "sapmachine([0-9]+)" ]]; then
+ echo 'true'
+ return
+ fi
fi
echo 'false'
@@ -180,10 +203,12 @@ jobs:
echo "linux-aarch64=$(check_platform linux-aarch64 linux aarch64)" >> $GITHUB_OUTPUT
echo "linux-cross-compile=$(check_platform linux-cross-compile cross-compile)" >> $GITHUB_OUTPUT
echo "alpine-linux-x64=$(check_platform alpine-linux-x64 alpine-linux x64)" >> $GITHUB_OUTPUT
- echo "macos-x64=$(check_platform macos-x64 macos x64)" >> $GITHUB_OUTPUT
+ # SapMachine 2026-05-14: Disabling GHA for platforms that are not delivered
+ echo "macos-x64=false" >> $GITHUB_OUTPUT
echo "macos-aarch64=$(check_platform macos-aarch64 macos aarch64)" >> $GITHUB_OUTPUT
echo "windows-x64=$(check_platform windows-x64 windows x64)" >> $GITHUB_OUTPUT
- echo "windows-aarch64=$(check_platform windows-aarch64 windows aarch64)" >> $GITHUB_OUTPUT
+ # SapMachine 2026-05-14: Disabling GHA for platforms that are not delivered
+ echo "windows-aarch64=false" >> $GITHUB_OUTPUT
echo "docs=$(check_platform docs)" >> $GITHUB_OUTPUT
echo "dry-run=$(check_dry_run)" >> $GITHUB_OUTPUT
diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 000000000000..1f844028e624
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,33 @@
+# Runs update-wiki action every day at 20:00 UTC
+
+name: 'Wiki Update'
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 20 * * *'
+
+jobs:
+ wiki:
+ if: ${{ github.event_name != 'schedule' || github.repository == 'SAP/SapMachine' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout SapMachine Wiki source
+ uses: actions/checkout@v4
+ with:
+ repository: 'SAP/SapMachine.wiki.git'
+ ref: 'master'
+ - name: Configure git
+ run: |
+ git config user.name "SapMachine Github Actions Bot"
+ git config user.email "sapmachine@sap.com"
+ git remote set-url origin https://github.com/SAP/SapMachine.wiki.git
+ - name: Update Wiki
+ run: |
+ pip3 install feedparser
+ python3 scripts/update_blogs.py update
+ git commit -a -m "Update blogs" || echo "No updates"
+ - name: Push changes
+ run: git push origin master
+ working-directory: .
+ shell: bash
diff --git a/README.md b/README.md
index e939f6a9ca4b..b2d70ab7361f 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,35 @@
-# Welcome to the JDK!
+
+
+
-For build instructions please see the
-[online documentation](https://git.openjdk.org/jdk/blob/master/doc/building.md),
-or either of these files:
+
-- [doc/building.html](doc/building.html) (html version)
-- [doc/building.md](doc/building.md) (markdown version)
+# [](#SapMachine) SapMachine
+SapMachine is a downstream fork of the [OpenJDK](https://openjdk.org/) project, aimed at providing a binary distribution of OpenJDK for SAP customers and partners.
-See for more information about the OpenJDK
-Community and the JDK and see for JDK issue
-tracking.
+SAP is committed to the ongoing success of the Java platform and the OpenJDK project, maintaining SapMachine in an OpenJDK-upstream-first model. Additional information regarding SAP's engagement in OpenJDK is available on the [SAP OpenJDK Engagement page](https://sapmachine.io/docs/sap-in-openjdk).
+
+
+details on App settings SapMachine, including *installation instructions*, *frequently asked questions*, *the maintenance and support statement*, can be found on the [documentation pages]
+gcloud projects add-iam-policy-binding
+resonant-tower-497421-f0 \ --member="user:Countryiview@gmail.com" \ --role="roles/iam.serviceAccountViewer"
+
+## Issues
+For SapMachine-specific concerns, please file a [new issue](https://github.com/SAP/SapMachine/issues/new).
+
+General JVM/JDK bugs are managed in the [OpenJDK Bug System](https://bugs.openjdk.org/). A SapMachine issue can be opened with reference to an existing OpenJDK bug for requesting resolution or backporting the fix to a specific SapMachine version. If a general JVM/JDK bug is found in SapMachine without editor access to the OpenJDK Bug System, an issue can be opened here, and a corresponding OpenJDK bug will be filed by us.
+
+Since SapMachine tracks the OpenJDK, every SapMachine release contains all the fixes/changes of the corresponding OpenJDK release on which it is based.
+
+## Contributing
+External contributions to this project are currently not accepted. For code improvements or bug fixes, contributions should be directed to the upstream [OpenJDK](https://openjdk.org/contribute/) project. Repositories are regularly synced with OpenJDK, ensuring that upstream improvements are realized in SapMachine.
+
+## License
+This project is run under the same licensing terms as the upstream OpenJDK project. Additional information is available in the [LICENSE](LICENSE) file in the top-level directory.
diff --git a/make/Bundles.gmk b/make/Bundles.gmk
index 925149d4cc76..7f6de8058b9d 100644
--- a/make/Bundles.gmk
+++ b/make/Bundles.gmk
@@ -165,9 +165,10 @@ ifeq ($(call isTargetOs, macosx)+$(DEBUG_LEVEL), true+release)
else
JDK_IMAGE_HOMEDIR := $(JDK_IMAGE_DIR)
JRE_IMAGE_HOMEDIR := $(JRE_IMAGE_DIR)
- JDK_BUNDLE_SUBDIR := jdk-$(VERSION_NUMBER)
- JRE_BUNDLE_SUBDIR := jre-$(VERSION_NUMBER)
- STATIC_JDK_BUNDLE_SUBDIR := static-jdk-$(VERSION_NUMBER)
+ # SapMachine 2020-11-04: Adapt bundle names
+ JDK_BUNDLE_SUBDIR := sapmachine-jdk-$(VERSION_NUMBER)
+ JRE_BUNDLE_SUBDIR := sapmachine-jre-$(VERSION_NUMBER)
+ STATIC_JDK_BUNDLE_SUBDIR := sapmachine-static-jdk-$(VERSION_NUMBER)
ifneq ($(DEBUG_LEVEL), release)
JDK_BUNDLE_SUBDIR := $(JDK_BUNDLE_SUBDIR)/$(DEBUG_LEVEL)
JRE_BUNDLE_SUBDIR := $(JRE_BUNDLE_SUBDIR)/$(DEBUG_LEVEL)
diff --git a/make/Images.gmk b/make/Images.gmk
index 8008cfa67791..e5642aad517a 100644
--- a/make/Images.gmk
+++ b/make/Images.gmk
@@ -43,8 +43,9 @@ ALL_MODULES := $(call FindAllModules) $(EXTRA_MODULES)
$(eval $(call ReadImportMetaData))
+# SapMachine 2021-09-24: add jcmd to JRE
JRE_MODULES += $(filter $(ALL_MODULES), $(BOOT_MODULES) \
- $(PLATFORM_MODULES) jdk.jdwp.agent)
+ $(PLATFORM_MODULES) jdk.jdwp.agent jdk.jcmd)
JDK_MODULES += $(ALL_MODULES)
JRE_MODULES_LIST := $(call CommaList, $(JRE_MODULES))
@@ -196,19 +197,20 @@ ifeq ($(BUILD_CDS_ARCHIVE), true)
$(eval $(call CreateCDSArchive,$v,)) \
)
- ifeq ($(call isTargetCpuBits, 64), true)
- $(foreach v, $(JVM_VARIANTS), \
- $(eval $(call CreateCDSArchive,$v,_nocoops)) \
- )
- ifeq ($(BUILD_CDS_ARCHIVE_NOCOH), true)
- $(foreach v, $(JVM_VARIANTS), \
- $(eval $(call CreateCDSArchive,$v,_nocoh)) \
- )
- $(foreach v, $(JVM_VARIANTS), \
- $(eval $(call CreateCDSArchive,$v,_nocoops_nocoh)) \
- )
- endif
- endif
+ # SapMachine 2025-06-11: reduce number of cds/jsa archives, keep only the default jsa file
+ #ifeq ($(call isTargetCpuBits, 64), true)
+ # $(foreach v, $(JVM_VARIANTS), \
+ # $(eval $(call CreateCDSArchive,$v,_nocoops)) \
+ # )
+ # ifeq ($(BUILD_CDS_ARCHIVE_NOCOH), true)
+ # $(foreach v, $(JVM_VARIANTS), \
+ # $(eval $(call CreateCDSArchive,$v,_nocoh)) \
+ # )
+ # $(foreach v, $(JVM_VARIANTS), \
+ # $(eval $(call CreateCDSArchive,$v,_nocoops_nocoh)) \
+ # )
+ # endif
+ #endif
endif
################################################################################
@@ -264,6 +266,41 @@ ifeq ($(GCOV_ENABLED), true)
endif
+################################################################################
+# SapMachine 2024-09-13: Async profiler import
+
+ifeq ($(call isTargetOs, linux macosx)+$(ASYNC_PROFILER_IMPORT_ENABLED), true+true)
+
+ $(eval $(call SetupCopyFiles, COPY_ASYNC_PROFILER_BIN_TO_JDK, \
+ SRC := $(ASYNC_PROFILER_IMPORT_PATH), \
+ DEST := $(JDK_IMAGE_DIR), \
+ FILES := bin/asprof lib/libasyncProfiler$(SHARED_LIBRARY_SUFFIX), \
+ MACRO := install-file-and-sign, \
+ ))
+
+ $(eval $(call SetupCopyFiles, COPY_ASYNC_PROFILER_BIN_TO_JRE, \
+ SRC := $(ASYNC_PROFILER_IMPORT_PATH), \
+ DEST := $(JRE_IMAGE_DIR), \
+ FILES := bin/asprof lib/libasyncProfiler$(SHARED_LIBRARY_SUFFIX), \
+ MACRO := install-file-and-sign, \
+ ))
+
+ $(eval $(call SetupCopyFiles, COPY_ASYNC_PROFILER_TO_JDK, \
+ SRC := $(ASYNC_PROFILER_IMPORT_PATH), \
+ DEST := $(JDK_IMAGE_DIR), \
+ FILES := lib/async-profiler.jar legal/async/CHANGELOG.md legal/async/LICENSE legal/async/README.md, \
+ ))
+
+ $(eval $(call SetupCopyFiles, COPY_ASYNC_PROFILER_TO_JRE, \
+ SRC := $(ASYNC_PROFILER_IMPORT_PATH), \
+ DEST := $(JRE_IMAGE_DIR), \
+ FILES := lib/async-profiler.jar legal/async/CHANGELOG.md legal/async/LICENSE legal/async/README.md, \
+ ))
+
+ JDK_TARGETS += $(COPY_ASYNC_PROFILER_BIN_TO_JDK) $(COPY_ASYNC_PROFILER_TO_JDK)
+ JRE_TARGETS += $(COPY_ASYNC_PROFILER_BIN_TO_JRE) $(COPY_ASYNC_PROFILER_TO_JRE)
+endif
+
################################################################################
# Debug symbols
# Since debug symbols are not included in the jmod files, they need to be copied
diff --git a/make/MacBundles.gmk b/make/MacBundles.gmk
index f80ca1e27351..83049a8204b5 100644
--- a/make/MacBundles.gmk
+++ b/make/MacBundles.gmk
@@ -41,7 +41,12 @@ ifeq ($(call isTargetOs, macosx), true)
MACOSX_PLIST_SRC := $(TOPDIR)/make/data/bundle
- BUNDLE_NAME := $(MACOSX_BUNDLE_NAME_BASE) $(VERSION_SHORT)
+ # SapMachine 2023-06-24: ea bundles should have build number in CFBundleName
+ ifeq ($(VERSION_PRE), ea)
+ BUNDLE_NAME := $(MACOSX_BUNDLE_NAME_BASE) $(VERSION_STRING)
+ else
+ BUNDLE_NAME := $(MACOSX_BUNDLE_NAME_BASE) $(VERSION_SHORT)
+ endif
BUNDLE_INFO := $(MACOSX_BUNDLE_NAME_BASE) $(VERSION_STRING)
ifeq ($(COMPANY_NAME), N/A)
BUNDLE_VENDOR := UNDEFINED
diff --git a/make/StaticLibs.gmk b/make/StaticLibs.gmk
index ca74590938da..cb41df85ab32 100644
--- a/make/StaticLibs.gmk
+++ b/make/StaticLibs.gmk
@@ -75,6 +75,9 @@ else ifeq ($(call isTargetOs, aix), true)
BROKEN_STATIC_LIBS += splashscreen
endif
+# SapMachine 2025-03-19: Fix static libs target, dt_filesocket defines jdwpTransport_OnLoad which conflicts with dt_socket
+BROKEN_STATIC_LIBS += dt_filesocket
+
$(foreach module, $(STATIC_LIB_MODULES), \
$(eval LIBS_$(module) := $(filter-out $(BROKEN_STATIC_LIBS), $(shell cat \
$(SUPPORT_OUTPUTDIR)/modules_static-libs/$(module)/module-included-libs.txt))) \
diff --git a/make/TestImage.gmk b/make/TestImage.gmk
index 869cce1d5581..ef1a0a2701a9 100644
--- a/make/TestImage.gmk
+++ b/make/TestImage.gmk
@@ -43,6 +43,14 @@ $(README):
TARGETS += $(BUILD_INFO_PROPERTIES) $(README)
+# SapMachine 2025-08-11: Copy test jar for SAP JMC agent if needed.
+ifeq ($(SAP_JMC_AGENT_ENABLED), true)
+ $(TEST_IMAGE_DIR)/jars/agent-tests.jar: $(SAP_JMC_AGENT_PATH)/agent-$(SAP_JMC_AGENT_VERSION)-tests.jar
+ $(call install-file)
+
+ TARGETS += $(TEST_IMAGE_DIR)/jars/agent-tests.jar
+endif
+
################################################################################
prepare-test-image: $(TARGETS)
diff --git a/make/autoconf/configure.ac b/make/autoconf/configure.ac
index 6d65ad93c408..c00a2098352b 100644
--- a/make/autoconf/configure.ac
+++ b/make/autoconf/configure.ac
@@ -215,6 +215,9 @@ JDKOPT_SETUP_CODE_COVERAGE
# AddressSanitizer
JDKOPT_SETUP_ADDRESS_SANITIZER
+# SapMachine 2025-08-11: Setup SAP JMC agent if requested
+JDKOPT_SETUP_SAP_JMC_AGENT
+
# UndefinedBehaviorSanitizer
JDKOPT_SETUP_UNDEFINED_BEHAVIOR_SANITIZER
diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
index 465e06ab39d8..e014c76b8288 100644
--- a/make/autoconf/jdk-options.m4
+++ b/make/autoconf/jdk-options.m4
@@ -234,6 +234,21 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JDK_OPTIONS],
fi
AC_SUBST(COPYRIGHT_YEAR)
+ # SapMachine 2024-09-13: import async profiler binaries
+ AC_ARG_WITH(async-profiler-import-path, [AS_HELP_STRING([--with-async-profiler-import-path],
+ [Set import path for downloaded async profiler binaries])])
+ if test "x$with_async_profiler_import_path" != x; then
+ ASYNC_PROFILER_IMPORT_PATH="$with_async_profiler_import_path"
+ if test -f "$ASYNC_PROFILER_IMPORT_PATH/bin/asprof"; then
+ ASYNC_PROFILER_IMPORT_ENABLED=true
+ AC_MSG_NOTICE([asprof exists, enabling async-profiler import])
+ else
+ AC_MSG_ERROR([async-profiler import path was set, but asprof was not found])
+ fi
+ fi
+ AC_SUBST(ASYNC_PROFILER_IMPORT_PATH)
+ AC_SUBST(ASYNC_PROFILER_IMPORT_ENABLED)
+
# Override default library path
AC_ARG_WITH([jni-libpath], [AS_HELP_STRING([--with-jni-libpath],
[override default JNI library search path])])
@@ -372,6 +387,29 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_SYMBOLS],
AC_SUBST(SHIP_DEBUG_SYMBOLS)
])
+# SapMachine 2025-08-11: import JMC agent jars
+AC_DEFUN_ONCE([JDKOPT_SETUP_SAP_JMC_AGENT],
+[
+ AC_ARG_WITH(sap-jmc-agent, [AS_HELP_STRING([--with-sap-jmc-agent],
+ [Set import path for the SAP JMC agent jars])])
+ SAP_JMC_AGENT_ENABLED=false
+ if test "x$with_sap_jmc_agent" != x; then
+ SAP_JMC_AGENT_PATH="$with_sap_jmc_agent"
+ UTIL_FIXUP_PATH(SAP_JMC_AGENT_PATH)
+ if test -f "$SAP_JMC_AGENT_PATH"/agent-*-boot.jar; then
+ SAP_JMC_AGENT_VERSION=$($BASENAME "$SAP_JMC_AGENT_PATH"/agent-*-boot.jar | $SED -e 's/^agent-//' -e 's/-boot.jar$//')
+ SAP_JMC_AGENT_ENABLED=true
+ JVM_CFLAGS="$JVM_CFLAGS -DWITH_SAP_JMC_AGENT=1"
+ AC_MSG_NOTICE([SAP JMC agent found at $SAP_JMC_AGENT_PATH, version $SAP_JMC_AGENT_VERSION])
+ else
+ AC_MSG_ERROR([SAP JMC agent was set, but the agent jars were not found])
+ fi
+ fi
+ AC_SUBST(SAP_JMC_AGENT_PATH)
+ AC_SUBST(SAP_JMC_AGENT_VERSION)
+ AC_SUBST(SAP_JMC_AGENT_ENABLED)
+])
+
################################################################################
#
# Native and Java code coverage
diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template
index ecfd5dd0a922..c042c3644472 100644
--- a/make/autoconf/spec.gmk.template
+++ b/make/autoconf/spec.gmk.template
@@ -462,6 +462,15 @@ CACERTS_SRC := @CACERTS_SRC@
# Enable unlimited crypto policy
UNLIMITED_CRYPTO := @UNLIMITED_CRYPTO@
+# SapMachine 2024-09-13: import async profiler binaries
+ASYNC_PROFILER_IMPORT_PATH=@ASYNC_PROFILER_IMPORT_PATH@
+ASYNC_PROFILER_IMPORT_ENABLED=@ASYNC_PROFILER_IMPORT_ENABLED@
+
+# SapMachine 2025-08-11: import SAP JMC agent jars
+SAP_JMC_AGENT_PATH=@SAP_JMC_AGENT_PATH@
+SAP_JMC_AGENT_VERSION=@SAP_JMC_AGENT_VERSION@
+SAP_JMC_AGENT_ENABLED=@SAP_JMC_AGENT_ENABLED@
+
GCOV_ENABLED := @GCOV_ENABLED@
JCOV_ENABLED := @JCOV_ENABLED@
JCOV_HOME := @JCOV_HOME@
@@ -892,16 +901,18 @@ STATIC_LIBS_IMAGE_SUBDIR := static-libs
STATIC_LIBS_IMAGE_DIR := $(IMAGES_OUTPUTDIR)/$(STATIC_LIBS_IMAGE_SUBDIR)
# Macosx bundles directory definitions
-JDK_MACOSX_BUNDLE_SUBDIR := jdk-bundle
-JRE_MACOSX_BUNDLE_SUBDIR := jre-bundle
-JDK_MACOSX_BUNDLE_SUBDIR_SIGNED := jdk-bundle-signed
-JRE_MACOSX_BUNDLE_SUBDIR_SIGNED := jre-bundle-signed
+# SapMachine 2020-11-04: Adapt bundle names
+JDK_MACOSX_BUNDLE_SUBDIR := sapmachine-jdk-bundle
+JRE_MACOSX_BUNDLE_SUBDIR := sapmachine-jre-bundle
+JDK_MACOSX_BUNDLE_SUBDIR_SIGNED := sapmachine-jdk-bundle-signed
+JRE_MACOSX_BUNDLE_SUBDIR_SIGNED := sapmachine-jre-bundle-signed
JDK_MACOSX_BUNDLE_DIR = $(IMAGES_OUTPUTDIR)/$(JDK_MACOSX_BUNDLE_SUBDIR)
JRE_MACOSX_BUNDLE_DIR = $(IMAGES_OUTPUTDIR)/$(JRE_MACOSX_BUNDLE_SUBDIR)
JDK_MACOSX_BUNDLE_DIR_SIGNED = $(IMAGES_OUTPUTDIR)/$(JDK_MACOSX_BUNDLE_SUBDIR_SIGNED)
JRE_MACOSX_BUNDLE_DIR_SIGNED = $(IMAGES_OUTPUTDIR)/$(JRE_MACOSX_BUNDLE_SUBDIR_SIGNED)
-JDK_MACOSX_BUNDLE_TOP_SUBDIR = jdk-$(VERSION_NUMBER).jdk
-JRE_MACOSX_BUNDLE_TOP_SUBDIR = jre-$(VERSION_NUMBER).jre
+# SapMachine 2020-11-04: Adapt bundle names
+JDK_MACOSX_BUNDLE_TOP_SUBDIR = sapmachine-jdk-$(VERSION_NUMBER).jdk
+JRE_MACOSX_BUNDLE_TOP_SUBDIR = sapmachine-jre-$(VERSION_NUMBER).jre
JDK_MACOSX_CONTENTS_SUBDIR = $(JDK_MACOSX_BUNDLE_TOP_SUBDIR)/Contents
JRE_MACOSX_CONTENTS_SUBDIR = $(JRE_MACOSX_BUNDLE_TOP_SUBDIR)/Contents
JDK_MACOSX_CONTENTS_DIR = $(JDK_MACOSX_BUNDLE_DIR)/$(JDK_MACOSX_CONTENTS_SUBDIR)
@@ -926,13 +937,19 @@ ifeq ($(OPENJDK_TARGET_OS), windows)
else
JDK_BUNDLE_EXTENSION := tar.gz
endif
-JDK_BUNDLE_NAME := jdk-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
-JRE_BUNDLE_NAME := jre-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
-JDK_SYMBOLS_BUNDLE_NAME := jdk-$(BASE_NAME)_bin$(DEBUG_PART)-symbols.tar.gz
-TEST_DEMOS_BUNDLE_NAME := jdk-$(BASE_NAME)_bin-tests-demos$(DEBUG_PART).tar.gz
-TEST_BUNDLE_NAME := jdk-$(BASE_NAME)_bin-tests$(DEBUG_PART).tar.gz
-DOCS_JDK_BUNDLE_NAME := jdk-$(BASE_NAME)_doc-api-spec$(DEBUG_PART).tar.gz
+# SapMachine 2020-11-04: Adapt bundle names
+JDK_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
+JRE_BUNDLE_NAME := sapmachine-jre-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
+JDK_SYMBOLS_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_bin$(DEBUG_PART)-symbols.tar.gz
+TEST_DEMOS_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_bin-tests-demos$(DEBUG_PART).tar.gz
+TEST_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_bin-tests$(DEBUG_PART).tar.gz
+DOCS_JDK_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_doc-api-spec$(DEBUG_PART).tar.gz
DOCS_JAVASE_BUNDLE_NAME := javase-$(BASE_NAME)_doc-api-spec$(DEBUG_PART).tar.gz
+DOCS_REFERENCE_BUNDLE_NAME := sapmachine-jdk-reference-$(BASE_NAME)_doc-api-spec$(DEBUG_PART).tar.gz
+STATIC_LIBS_BUNDLE_NAME := sapmachine-jdk-$(BASE_NAME)_bin-static-libs$(DEBUG_PART).tar.gz
+STATIC_LIBS_GRAAL_BUNDLE_NAME := sapmachine-$(BASE_NAME)_bin-static-libs-graal$(DEBUG_PART).tar.gz
+STATIC_JDK_BUNDLE_NAME := sapmachine-static-jdk-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
+JCOV_BUNDLE_NAME := sapmachine-jdk-jcov-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
DOCS_REFERENCE_BUNDLE_NAME := jdk-reference-$(BASE_NAME)_doc-api-spec$(DEBUG_PART).tar.gz
STATIC_LIBS_BUNDLE_NAME := jdk-$(BASE_NAME)_bin-static-libs$(DEBUG_PART).tar.gz
STATIC_JDK_BUNDLE_NAME := static-jdk-$(BASE_NAME)_bin$(DEBUG_PART).$(JDK_BUNDLE_EXTENSION)
diff --git a/make/common/FileUtils.gmk b/make/common/FileUtils.gmk
index fa934797fc73..95cf7abac1bb 100644
--- a/make/common/FileUtils.gmk
+++ b/make/common/FileUtils.gmk
@@ -137,11 +137,35 @@ ifeq ($(call isTargetOs, macosx), true)
$(XATTR) -cs '$(call DecodeSpace, $@)'; \
fi
endef
+
+ # SapMachine 2024-09-13: import async profiler binaries
+ ifeq ($(MACOSX_CODESIGN_MODE), disabled)
+ define install-file-and-sign
+ $(install-file)
+ $(ECHO) No Async profiler codesigning, codesign mode is $(MACOSX_CODESIGN_MODE)
+ endef
+ else
+ ifeq ($(MACOSX_CODESIGN_MODE), hardened)
+ CODESIGN_APOPTS="$(MACOSX_CODESIGN_IDENTITY)" --timestamp --options runtime
+ else ifeq ($(MACOSX_CODESIGN_MODE), debug)
+ CODESIGN_APOPTS=-
+ PLIST_APOPT=-debug
+ endif
+ define install-file-and-sign
+ $(install-file)
+ $(CODESIGN) --remove-signature '$(call DecodeSpace, $@)'
+ $(CODESIGN) -f -s $(CODESIGN_APOPTS) --entitlements $(TOPDIR)/make/data/macosxsigning/default$(PLIST_APOPT).plist '$(call DecodeSpace, $@)'
+ endef
+ endif
else
define install-file
$(call MakeTargetDir)
$(CP) -fP '$(call DecodeSpace, $<)' '$(call DecodeSpace, $@)'
endef
+ # SapMachine 2024-09-13: import async profiler binaries
+ define install-file-and-sign
+ $(install-file)
+ endef
endif
# Variant of install file that does not preserve symlinks
diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf
index 9aee8e87e3c4..c0848d6492ab 100644
--- a/make/conf/github-actions.conf
+++ b/make/conf/github-actions.conf
@@ -29,26 +29,28 @@ GTEST_VERSION=1.14.0
JTREG_VERSION=8.2.1+1
LINUX_X64_BOOT_JDK_EXT=tar.gz
-LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_linux-x64_bin.tar.gz
-LINUX_X64_BOOT_JDK_SHA256=83c78367f8c81257beef72aca4bbbf8e6dac8ca2b3a4546a85879a09e6e4e128
+LINUX_X64_BOOT_JDK_URL=https://github.com/SAP/SapMachine/releases/download/sapmachine-26.0.1/sapmachine-jdk-26.0.1_linux-x64_bin.tar.gz
+LINUX_X64_BOOT_JDK_SHA256=4422a642da9764419477a80bea1d614391c40b71060f0de1610467d4c70807a0
LINUX_AARCH64_BOOT_JDK_EXT=tar.gz
LINUX_AARCH64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_linux-aarch64_bin.tar.gz
LINUX_AARCH64_BOOT_JDK_SHA256=403ccf451e88d0be9e1dec129fcb9318de9752121e0eb92dfa9a8cf06f249007
ALPINE_LINUX_X64_BOOT_JDK_EXT=tar.gz
-ALPINE_LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin26-binaries/releases/download/jdk-26%2B35/OpenJDK26U-jdk_x64_alpine-linux_hotspot_26_35.tar.gz
-ALPINE_LINUX_X64_BOOT_JDK_SHA256=c105e581fdccb4e7120d889235d1ad8d5b2bed0af4972bc881e0a8ba687c94a4
+ALPINE_LINUX_X64_BOOT_JDK_URL=https://github.com/SAP/SapMachine/releases/download/sapmachine-26.0.1/sapmachine-jdk-26.0.1_linux-x64-musl_bin.tar.gz
+ALPINE_LINUX_X64_BOOT_JDK_SHA256=58dad03eaf3ec5d40bc2f94d8c1bd5f6e47c650d2d6b52fe3572926285f62e04
MACOS_AARCH64_BOOT_JDK_EXT=tar.gz
-MACOS_AARCH64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_macos-aarch64_bin.tar.gz
-MACOS_AARCH64_BOOT_JDK_SHA256=254586bcd1bf6dcd125ad667ac32562cb1e2ab1abf3a61fb117b6fabb571e765
+MACOS_AARCH64_BOOT_JDK_URL=https://github.com/SAP/SapMachine/releases/download/sapmachine-26.0.1/sapmachine-jdk-26.0.1_macos-aarch64_bin.tar.gz
+MACOS_AARCH64_BOOT_JDK_SHA256=3663faab53b08e20c87112166bae3399340ead434d8e3a58cbb2a28ef1e0c584
MACOS_X64_BOOT_JDK_EXT=tar.gz
MACOS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_macos-x64_bin.tar.gz
MACOS_X64_BOOT_JDK_SHA256=8642b89d889c14ede2c446fd5bbe3621c8a3082e3df02013fd1658e39f52929a
WINDOWS_X64_BOOT_JDK_EXT=zip
+WINDOWS_X64_BOOT_JDK_URL=https://github.com/SAP/SapMachine/releases/download/sapmachine-26.0.1/sapmachine-jdk-26.0.1_windows-x64_bin.zip
+WINDOWS_X64_BOOT_JDK_SHA256=1b7ff2fe4bb201047a7e43b83c318b9e0bf53bd8a9ea8e8ac95df2917ff0cbd8
WINDOWS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_windows-x64_bin.zip
WINDOWS_X64_BOOT_JDK_SHA256=2dd2d92c9374cd49a120fe9d916732840bf6bb9f0e0cc29794917a3c08b99c5f
diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf
index 35b9345ed8f9..b56a0113e5ed 100644
--- a/make/conf/module-loader-map.conf
+++ b/make/conf/module-loader-map.conf
@@ -78,6 +78,9 @@ PLATFORM_MODULES= \
jdk.httpserver \
jdk.localedata \
jdk.naming.dns \
+ # SapMachine 2024-06-12: add extension module
+PLATFORM_MODULES+= \
+ jdk.sapext \
jdk.security.auth \
jdk.security.jgss \
jdk.xml.dom \
@@ -110,6 +113,9 @@ NATIVE_ACCESS_MODULES= \
jdk.management \
jdk.management.agent \
jdk.net \
+ # SapMachine 2024-06-12: add extension module
+NATIVE_ACCESS_MODULES+= \
+ jdk.sapext \
jdk.sctp \
jdk.security.auth \
#
diff --git a/make/modules/java.base/Copy.gmk b/make/modules/java.base/Copy.gmk
index 43b9db651e0f..3fb33fedfe8b 100644
--- a/make/modules/java.base/Copy.gmk
+++ b/make/modules/java.base/Copy.gmk
@@ -239,3 +239,27 @@ $(eval $(call SetupTextFileProcessing, CREATE_CLASSFILE_CONSTANTS_H, \
TARGETS += $(CREATE_CLASSFILE_CONSTANTS_H)
################################################################################
+# SapMachine 2023-11-28: Copy mallochook.h
+
+ifeq ($(call isTargetOs, linux macosx), true)
+
+ $(eval $(call SetupCopyFiles, CREATE_MALLOC_HOOKS_H, \
+ FILES := $(TOPDIR)/src/java.base/unix/native/libmallochooks/mallochooks.h, \
+ DEST := $(SUPPORT_OUTPUTDIR)/modules_include/java.base/, \
+ ))
+
+ TARGETS += $(CREATE_MALLOC_HOOKS_H)
+endif
+
+################################################################################
+# SapMachine 2025-08-11: Copy JMC agent jars
+ifeq ($(SAP_JMC_AGENT_ENABLED), true)
+ $(LIB_DST_DIR)/agent.jar: $(SAP_JMC_AGENT_PATH)/agent-$(SAP_JMC_AGENT_VERSION).jar
+ $(call install-file)
+ $(LIB_DST_DIR)/agent-boot.jar: $(SAP_JMC_AGENT_PATH)/agent-$(SAP_JMC_AGENT_VERSION)-boot.jar
+ $(call install-file)
+
+ TARGETS += $(LIB_DST_DIR)/agent.jar $(LIB_DST_DIR)/agent-boot.jar
+endif
+
+################################################################################
diff --git a/make/modules/java.base/lib/CoreLibraries.gmk b/make/modules/java.base/lib/CoreLibraries.gmk
index 316103be4cd9..c34edb0b0dfa 100644
--- a/make/modules/java.base/lib/CoreLibraries.gmk
+++ b/make/modules/java.base/lib/CoreLibraries.gmk
@@ -201,6 +201,17 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJLI, \
TARGETS += $(BUILD_LIBJLI)
+# SapMachine 2023-11-28: build libmallochooks
+ifeq ($(call isTargetOs, linux macosx), true)
+ $(eval $(call SetupJdkLibrary, BUILD_LIBMALLOCHOOKS, \
+ NAME := mallochooks, \
+ OPTIMIZATION := LOW, \
+ LIBS := -ldl, \
+ ))
+
+ TARGETS += $(BUILD_LIBMALLOCHOOKS)
+endif
+
################################################################################
endif # include guard
diff --git a/make/modules/jdk.jdwp.agent/Lib.gmk b/make/modules/jdk.jdwp.agent/Lib.gmk
index ce58e145f59b..1c76cc9203c9 100644
--- a/make/modules/jdk.jdwp.agent/Lib.gmk
+++ b/make/modules/jdk.jdwp.agent/Lib.gmk
@@ -49,6 +49,22 @@ TARGETS += $(BUILD_LIBDT_SOCKET)
## Build libjdwp
################################################################################
+# SapMachine 2024-02-16: Add dt_filesocket implementation
+$(eval $(call SetupJdkLibrary, BUILD_LIBDT_FILESOCKET, \
+ NAME := dt_filesocket, \
+ OPTIMIZATION := LOW, \
+ DISABLED_WARNINGS_clang_fileSocketTransport.c := format-nonliteral, \
+ EXTRA_HEADER_DIRS := \
+ include \
+ libjdwp/export, \
+ LIBS_windows := ws2_32.lib Advapi32.lib, \
+))
+
+# Include file socket transport with JDWP agent to allow for safe local debugging
+TARGETS += $(BUILD_LIBDT_FILESOCKET)
+
+################################################################################
+
# JDWP_LOGGING causes log messages to be compiled into the library.
$(eval $(call SetupJdkLibrary, BUILD_LIBJDWP, \
NAME := jdwp, \
diff --git a/make/modules/jdk.sapext/Lib.gmk b/make/modules/jdk.sapext/Lib.gmk
new file mode 100644
index 000000000000..43d6ef370d4e
--- /dev/null
+++ b/make/modules/jdk.sapext/Lib.gmk
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2024 SAP SE. 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. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# 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.
+#
+
+include LibCommon.gmk
+
+##############################################################################
+## Build libjdksapext
+##############################################################################
+
+$(eval $(call SetupJdkLibrary, BUILD_LIBJDKSAPEXT, \
+ NAME := jdksapext, \
+ OPTIMIZATION := LOW, \
+))
+
+TARGETS += $(BUILD_LIBJDKSAPEXT)
diff --git a/make/test/JtregNativeHotspot.gmk b/make/test/JtregNativeHotspot.gmk
index 207f82241aa7..4a7385f233ea 100644
--- a/make/test/JtregNativeHotspot.gmk
+++ b/make/test/JtregNativeHotspot.gmk
@@ -69,6 +69,9 @@ ifeq ($(call isTargetOs, linux), true)
HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ldl
HOTSPOT_JTREG_LIBRARIES_LIBS_libAsyncGetCallTraceTest := -ldl
HOTSPOT_JTREG_LIBRARIES_LDFLAGS_libfast-math := -ffast-math
+ # SapMachine 2023-10-04: Added link flags for malloc hooks tests
+ HOTSPOT_JTREG_EXECUTABLES_LIBS_exetestmallochooks := -ldl
+ HOTSPOT_JTREG_LIBRARIES_LIBS_libtestmallochooks := -ldl
else
HOTSPOT_JTREG_EXCLUDE += libtest-rw.c libtest-rwx.c \
exeinvoke.c exestack-gap.c exestack-tls.c libAsyncGetCallTraceTest.cpp
diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk
index 6774e708f99a..39ef6a3c48ce 100644
--- a/make/test/JtregNativeJdk.gmk
+++ b/make/test/JtregNativeJdk.gmk
@@ -66,6 +66,9 @@ ifeq ($(call isTargetOs, windows), true)
libChangeSignalDisposition.c exePrintSignalDisposition.c \
libConcNativeFork.c libPipesCloseOnExec.c
+ #SapMachine 2024-06-12: Exclude libCreateNewProcessGroupOnSpawnTest.c from native compilation on Windows
+ BUILD_JDK_JTREG_EXCLUDE += libCreateNewProcessGroupOnSpawnTest.c
+
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerTest := $(LIBCXX)
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exerevokeall := advapi32.lib
BUILD_JDK_JTREG_EXECUTABLES_CFLAGS_exeNullCallerTest := /EHsc
diff --git a/src/hotspot/os/aix/vitals_aix.cpp b/src/hotspot/os/aix/vitals_aix.cpp
new file mode 100644
index 000000000000..450293ea1957
--- /dev/null
+++ b/src/hotspot/os/aix/vitals_aix.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitals_internals.hpp"
+
+namespace sapmachine_vitals {
+
+bool platform_columns_initialize() {
+ return true;
+}
+
+void sample_platform_values(Sample* record) {
+}
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/os/bsd/vitals_bsd.cpp b/src/hotspot/os/bsd/vitals_bsd.cpp
new file mode 100644
index 000000000000..450293ea1957
--- /dev/null
+++ b/src/hotspot/os/bsd/vitals_bsd.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitals_internals.hpp"
+
+namespace sapmachine_vitals {
+
+bool platform_columns_initialize() {
+ return true;
+}
+
+void sample_platform_values(Sample* record) {
+}
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp
index 90e1e5e5f3f0..a75d3a5faa7e 100644
--- a/src/hotspot/os/linux/globals_linux.hpp
+++ b/src/hotspot/os/linux/globals_linux.hpp
@@ -58,6 +58,41 @@
"resulting in file-backed shared mappings of the process to " \
"be dumped into the corefile.") \
\
+ /* SapMachine 2022-05-01: HiMemReport */ \
+ product(bool, HiMemReport, false, \
+ "VM writes a high memory report and optionally execute " \
+ "additional jcmds if its rss+swap reaches 66%, 75% or 90% of " \
+ "a maximum. If the VM is containerized, that maximum is the " \
+ "container memory limit at VM start. If the VM is not " \
+ "containerized, that maximum is half of the total physical " \
+ "memory. The maximum can be overridden with " \
+ "-XX:HiMemReportMaximum=. Per default, the report is " \
+ "printed to stderr. If HiMemReportDir is specified, that " \
+ "report is redirected to \"/" \
+ "_pid_.log\".") \
+ product(size_t, HiMemReportMax, 0, \
+ "Overrides the maximum reference size for HiMemReport.") \
+ product(ccstr, HiMemReportDir, nullptr, \
+ "Specifies a directory into which reports are written. Gets " \
+ "created (one level only) if it does not exist.") \
+ product(ccstr, HiMemReportExec, nullptr, \
+ "Specifies one or more jcmds to be executed after a high " \
+ "memory report has been written. Multiple commands are " \
+ "separated by ';'. Command output is written to stderr. If " \
+ "HiMemReportDir is specified, command output is redirected to " \
+ "\"/_pid_timestamp.(out|err)\"." \
+ "If one of the commands is \"GC.heap_dump\" and its " \
+ "arguments are omitted, the heap dump is written as " \
+ "\"GC.heap_dump_pid_timestamp\" to either report " \
+ "directory or current directory if HiMemReportDir is " \
+ "omitted.\n" \
+ "Example: \"-XX:HiMemReportExec=GC.class_histogram -all;GC.heap_dump\"") \
+ \
+ /* SapMachine 2025-11-24: Configurable limit of malloc arenas */ \
+ product(int, GlibcMallocArenas, 1, \
+ "Limit glibc malloc arenas, 0 means use OS default, " \
+ "1 minimizes memory utilization (our default)") \
+ \
product(bool, UseCpuAllocPath, false, DIAGNOSTIC, \
"Use CPU_ALLOC code path in os::active_processor_count ") \
\
diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp
index 511c7bebff76..ae97cd3dd586 100644
--- a/src/hotspot/os/linux/osContainer_linux.cpp
+++ b/src/hotspot/os/linux/osContainer_linux.cpp
@@ -38,6 +38,24 @@ bool OSContainer::_is_initialized = false;
bool OSContainer::_is_containerized = false;
CgroupSubsystem* cgroup_subsystem;
+// SapMachine 2022-05-01: Vitals
+// This is an ugly hack aimed at having as little merge surface as possible across JDK versions.
+// We use the OsContainer class just for its ability to figure out the controller path; we expose
+// that and read the data ourselves (since OsContainer omits certain data and does things I don't
+// want to happen in the Vitals sampler thread).
+extern const char* sapmachine_get_memory_controller_path() {
+ if (cgroup_subsystem != nullptr) {
+ CachingCgroupController* cc = cgroup_subsystem->memory_controller();
+ if (cc != nullptr) {
+ CgroupMemoryController* c = cc->controller();
+ if (c != nullptr) {
+ return c->subsystem_path();
+ }
+ }
+ }
+ return nullptr;
+}
+
/* init
*
* Initialize the container support and determine if
diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp
index 6927f5108acc..2c07cf8c9386 100644
--- a/src/hotspot/os/linux/os_linux.cpp
+++ b/src/hotspot/os/linux/os_linux.cpp
@@ -2185,6 +2185,9 @@ void os::print_os_info(outputStream* st) {
VM_Version::print_platform_virtualization_info(st);
+ // SapMachine 2019-07-02: 8225345: Provide Cloud IAAS related info on Linux in the hs_err file
+ os::Linux::print_cloud_info(st);
+
os::Linux::print_steal_info(st);
}
@@ -2622,6 +2625,87 @@ bool os::Linux::print_container_info(outputStream* st) {
return true;
}
+// SapMachine 2019-07-02: 8225345: Provide Cloud IAAS related info on Linux in the hs_err file
+static int check_matching_lines_from_file(const char* filename, const char* keywords_to_match[]) {
+ char line[500];
+ FILE* fp = fopen(filename, "r");
+ if (fp == nullptr) {
+ return -1;
+ }
+
+ while (fgets(line, sizeof(line), fp) != nullptr) {
+ int i = 0;
+ while (keywords_to_match[i] != nullptr) {
+ if (strstr(line, keywords_to_match[i]) != nullptr) {
+ fclose(fp);
+ return i;
+ }
+ i++;
+ }
+ }
+ fclose(fp);
+ return -1;
+}
+
+// SapMachine 2019-07-02: 8225345: Provide Cloud IAAS related info on Linux in the hs_err file
+// Add Cloud information where possible, a basic detection can be done by using dmi info
+// Google GCP: /sys/class/dmi/id/product_name contains 'Google Compute Engine' (or just 'Google')
+// Alibaba : /sys/class/dmi/id/product_name contains 'Alibaba Cloud ECS'
+// OpenStack : /sys/class/dmi/id/product_name contains 'OpenStack' e.g. 'OpenStack Nova'
+// Azure : /sys/class/dmi/id/chassis_asset_tag contains '7783-7084-3265-9085-8269-3286-77' (means ASCII-encoded: 'MS AZURE VM')
+// AWS KVM/Baremetal : /sys/class/dmi/id/chassis_asset_tag contains 'Amazon EC2'
+// AWS Xen : /sys/class/dmi/id/bios_version and /sys/class/dmi/id/product_version contain amazon (plus some more info)
+// /sys/class/dmi/id/bios_vendor and /sys/class/dmi/id/chassis_vendor contain 'Xen'
+void os::Linux::print_cloud_info(outputStream* st) {
+ // dmidir is /sys/class/dmi/id
+ const char* filename = "/sys/class/dmi/id/product_name";
+ const char* kwcld[] = { "Google", "Google Compute Engine", "Alibaba Cloud", "OpenStack", nullptr };
+ int res = check_matching_lines_from_file(filename, kwcld);
+ if (res != -1) { // a matching Cloud identifier has been found
+ st->print("Cloud infrastructure detected:");
+ if (res == 0 || res == 1) {
+ st->print_cr("Google cloud");
+ }
+ if (res == 2) {
+ st->print_cr("Alibaba cloud");
+ }
+ if (res == 3) {
+ st->print_cr("OpenStack based cloud");
+ // output version info too, e.g. "16.1.6-16.1.6~dev5"
+ _print_ascii_file("/sys/class/dmi/id/product_version", st);
+ }
+ return;
+ }
+ // AWS KVM/Baremetal
+ const char* filename2 = "/sys/class/dmi/id/chassis_asset_tag";
+ const char* kwaws[] = { "Amazon EC2", "7783-7084-3265-9085-8269-3286-77", nullptr };
+ res = check_matching_lines_from_file(filename2, kwaws);
+ if (res != -1) {
+ st->print("Cloud infrastructure detected:");
+ if (res == 0) {
+ st->print_cr("Amazon EC2 cloud");
+ }
+ if (res == 1) {
+ st->print_cr("Microsoft Azure");
+ }
+ return;
+ }
+ // AWS Xen is a bit tricky, it might not contain a "nice" product name
+ const char* chassis_vendor_file = "/sys/class/dmi/id/chassis_vendor";
+ const char* bios_vendor_file = "/sys/class/dmi/id/bios_vendor";
+ const char* kwxen[] = { "Xen", nullptr };
+ int res1 = check_matching_lines_from_file(chassis_vendor_file, kwxen);
+ int res2 = check_matching_lines_from_file(bios_vendor_file, kwxen);
+ if (res1 != -1 || res2 != -1) {
+ const char* pvfile = "/sys/class/dmi/id/product_version";
+ const char* kwam[] = { "amazon", nullptr };
+ res = check_matching_lines_from_file(pvfile, kwam);
+ if (res != -1) {
+ st->print_cr("Cloud infrastructure detected: Amazon Xen-based cloud");
+ }
+ }
+}
+
void os::Linux::print_steal_info(outputStream* st) {
if (has_initial_tick_info) {
CPUPerfTicks pticks;
@@ -3960,6 +4044,17 @@ void os::Linux::large_page_init() {
// Query OS information first.
HugePages::initialize();
+ // SapMachine 2025-12-10 Enable UseTransparentHugePages if supported and the huge pages
+ // are not extremely large and no other configuration is selected by flags.
+ // Some GCs have additional requirements, are optimized for ultra-low latency or
+ // have limitations regarding configuration parameters with small heap sizes.
+ if (FLAG_IS_DEFAULT(UseLargePages) && FLAG_IS_DEFAULT(UseTransparentHugePages) &&
+ HugePages::supports_thp() && HugePages::thp_pagesize() <= 2*M &&
+ !UseZGC && !UseShenandoahGC &&
+ (FLAG_IS_DEFAULT(MaxHeapSize) || MaxHeapSize > 128*M)) {
+ _thp_requested = UseTransparentHugePages = true;
+ }
+
// If THPs are unconditionally enabled (THP mode "always"), khugepaged may attempt to
// coalesce small pages in thread stacks to huge pages. That costs a lot of memory and
// is usually unwanted for thread stacks. Therefore we attempt to prevent THP formation in
@@ -4589,6 +4684,21 @@ jint os::init_2(void) {
os::Posix::init_2();
+ // SapMachine 2025-11-24:
+ // By default, glibc allocates a new 64 (or 128) MB malloc arena for every
+ // thread (up to a certain limit which is typically 8 * processor count).
+ // This is good for few threads which perform a lot of concurrent mallocs,
+ // but it doesn't fit well to the JVM which has its own memory management
+ // and rather allocates fewer and larger chunks.
+ // Using only one arena significantly reduces virtual memory footprint and
+ // fragmentation. Saving memory seems to be more valuable for the JVM than
+ // optimizing concurrent mallocs.
+#ifdef __GLIBC__
+ if (GlibcMallocArenas > 0) {
+ mallopt(M_ARENA_MAX, GlibcMallocArenas);
+ }
+#endif
+
if (PosixSignals::init() == JNI_ERR) {
return JNI_ERR;
}
diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp
index 41b5afbf5c3f..e87638e284a0 100644
--- a/src/hotspot/os/linux/os_linux.hpp
+++ b/src/hotspot/os/linux/os_linux.hpp
@@ -70,6 +70,8 @@ class os::Linux {
static void print_process_memory_info(outputStream* st);
static void print_system_memory_info(outputStream* st);
+ // SapMachine 2019-07-02: 8225345: Provide Cloud IAAS related info on Linux in the hs_err file
+ static void print_cloud_info(outputStream* st);
static bool print_container_info(outputStream* st);
static void print_steal_info(outputStream* st);
static void print_distro_info(outputStream* st);
diff --git a/src/hotspot/os/linux/vitals_linux.cpp b/src/hotspot/os/linux/vitals_linux.cpp
new file mode 100644
index 000000000000..bb0c5b318770
--- /dev/null
+++ b/src/hotspot/os/linux/vitals_linux.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "jvm_io.h"
+#include "logging/log.hpp"
+#include "osContainer_linux.hpp"
+#include "runtime/globals.hpp"
+#include "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+#include "vitals/vitals_internals.hpp"
+#include "vitals_linux_oswrapper.hpp"
+
+namespace sapmachine_vitals {
+
+/////// Columns ////////
+
+// A special class to display cpu time
+class CPUTimeColumn: public Column {
+
+ long _clk_tck;
+ int _num_cores;
+
+ int do_print0(outputStream* st, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const {
+ // CPU values may overflow, so the delta may be negative.
+ if (last_value > value) {
+ return 0;
+ }
+ int l = 0;
+ if (last_value != INVALID_VALUE) {
+
+ // If the last sample is less than one second old, we omit calculating the cpu
+ // usage.
+ if (last_value_age > 0) {
+
+ // Values are in ticks. Convert to ms.
+ const uint64_t value_ms = (value * 1000) / _clk_tck;
+ const uint64_t last_value_ms = (last_value * 1000) / _clk_tck;
+ const uint64_t delta_ms = value_ms - last_value_ms;
+
+ // Calculate the number of wallclock milliseconds for the delta interval...
+ const uint64_t age_ms = last_value_age * 1000;
+
+ // times number of available cores.
+ const uint64_t total_cpu_time_ms = age_ms * _num_cores;
+
+ // Put the spent cpu time in reference to the total available cpu time.
+ const double percentage = (100.0f * delta_ms) / total_cpu_time_ms;
+
+ char buf[32];
+ l = jio_snprintf(buf, sizeof(buf), "%.0f", percentage);
+ if (st != nullptr) {
+ st->print_raw(buf);
+ }
+ }
+ }
+ return l;
+ }
+
+public:
+ CPUTimeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {
+ _clk_tck = ::sysconf(_SC_CLK_TCK);
+ _num_cores = os::active_processor_count();
+ }
+
+};
+
+static bool g_show_system_memavail = false;
+static Column* g_col_system_memavail = nullptr;
+static Column* g_col_system_memcommitted = nullptr;
+static Column* g_col_system_memcommitted_ratio = nullptr;
+static Column* g_col_system_swap = nullptr;
+
+static Column* g_col_system_pages_swapped_in = nullptr;
+static Column* g_col_system_pages_swapped_out = nullptr;
+
+static Column* g_col_system_num_procs = nullptr;
+static Column* g_col_system_num_threads = nullptr;
+
+static Column* g_col_system_num_procs_running = nullptr;
+static Column* g_col_system_num_procs_blocked = nullptr;
+
+static bool g_show_cgroup_info = false;
+static Column* g_col_system_cgrp_limit_in_bytes = nullptr;
+static Column* g_col_system_cgrp_soft_limit_in_bytes = nullptr;
+static Column* g_col_system_cgrp_usage_in_bytes = nullptr;
+static Column* g_col_system_cgrp_kmem_usage_in_bytes = nullptr;
+
+static Column* g_col_system_cpu_user = nullptr;
+static Column* g_col_system_cpu_system = nullptr;
+static Column* g_col_system_cpu_idle = nullptr;
+static Column* g_col_system_cpu_steal = nullptr;
+static Column* g_col_system_cpu_guest = nullptr;
+
+static Column* g_col_process_virt = nullptr;
+
+static bool g_show_rss_detail_info = false;
+static Column* g_col_process_rss = nullptr;
+static Column* g_col_process_rssanon = nullptr;
+static Column* g_col_process_rssfile = nullptr;
+static Column* g_col_process_rssshmem = nullptr;
+
+static Column* g_col_process_swapped_out = nullptr;
+
+static Column* g_col_process_chp_used = nullptr;
+static Column* g_col_process_chp_free = nullptr;
+
+static Column* g_col_process_cpu_user = nullptr;
+static Column* g_col_process_cpu_system = nullptr;
+
+static Column* g_col_process_num_of = nullptr;
+static Column* g_col_process_io_bytes_read = nullptr;
+static Column* g_col_process_io_bytes_written = nullptr;
+
+static Column* g_col_process_num_threads = nullptr;
+
+bool platform_columns_initialize() {
+
+ const char* const system_cat = "system";
+ const char* const process_cat = "process";
+
+ Legend::the_legend()->add_footnote(" [host]: values are host-global (not containerized).");
+ Legend::the_legend()->add_footnote(" [cgrp]: if containerized or running in systemd slice");
+ Legend::the_legend()->add_footnote(" [krn]: depends on kernel version");
+ Legend::the_legend()->add_footnote(" [glibc]: only shown for glibc-based distros");
+
+ // Update values once, to get up-to-date readings. Some of those we need to decide whether to show or hide certain columns
+ OSWrapper::initialize();
+ OSWrapper::update_if_needed();
+
+ // syst-avail depends on kernel version.
+ g_show_system_memavail = OSWrapper::syst_avail() != INVALID_VALUE;
+ g_col_system_memavail =
+ define_column(system_cat, nullptr, "avail", "Memory available without swapping [host] [krn]", g_show_system_memavail, MIN);
+ g_col_system_memcommitted =
+ define_column(system_cat, nullptr, "comm", "Committed memory [host]", true);
+ g_col_system_memcommitted_ratio =
+ define_column(system_cat, nullptr, "crt", "Committed-to-Commit-Limit ratio (percent) [host]", true);
+ g_col_system_swap =
+ define_column(system_cat, nullptr, "swap", "Swap space used [host]", true);
+
+ g_col_system_pages_swapped_in =
+ define_column(system_cat, nullptr, "si", "Number of pages swapped in [host] [delta]", true);
+ g_col_system_pages_swapped_out =
+ define_column(system_cat, nullptr, "so", "Number of pages pages swapped out [host] [delta]", true);
+
+ g_col_system_num_procs =
+ define_column(system_cat, nullptr, "p", "Number of processes", true);
+ g_col_system_num_threads =
+ define_column(system_cat, nullptr, "t", "Number of threads", true);
+
+ g_col_system_num_procs_running =
+ define_column(system_cat, nullptr, "tr", "Number of threads running", true);
+ g_col_system_num_procs_blocked =
+ define_column(system_cat, nullptr, "tb", "Number of threads blocked on disk IO", true);
+
+ g_col_system_cpu_user =
+ define_column(system_cat, "cpu", "us", "CPU user time [host]", true);
+ g_col_system_cpu_system =
+ define_column(system_cat, "cpu", "sy", "CPU system time [host]", true);
+ g_col_system_cpu_idle =
+ define_column(system_cat, "cpu", "id", "CPU idle time [host]", true);
+ g_col_system_cpu_steal =
+ define_column(system_cat, "cpu", "st", "CPU time stolen [host]", true);
+ g_col_system_cpu_guest =
+ define_column(system_cat, "cpu", "gu", "CPU time spent on guest [host]", true);
+
+ // I show cgroup information if the container layer thinks we are containerized OR we have limits established
+ // (which should come out as the same, but you never know
+ g_show_cgroup_info = OSContainer::is_containerized() || (OSWrapper::syst_cgro_lim() != INVALID_VALUE || OSWrapper::syst_cgro_limsw() != INVALID_VALUE);
+ g_col_system_cgrp_limit_in_bytes =
+ define_column(system_cat, "cgroup", "lim", "cgroup memory limit [cgrp]", g_show_cgroup_info, MIN);
+ g_col_system_cgrp_soft_limit_in_bytes =
+ define_column(system_cat, "cgroup", "slim", "cgroup memory soft limit [cgrp]", g_show_cgroup_info, MIN);
+ g_col_system_cgrp_usage_in_bytes =
+ define_column(system_cat, "cgroup", "usg", "cgroup memory usage [cgrp]", g_show_cgroup_info);
+ g_col_system_cgrp_kmem_usage_in_bytes =
+ define_column(system_cat, "cgroup", "kusg", "cgroup kernel memory usage (cgroup v1 only) [cgrp]", g_show_cgroup_info);
+
+ // Process
+
+ g_col_process_virt =
+ define_column(process_cat, nullptr, "virt", "Virtual size", true);
+
+ // RSS detail needs kernel >= 4.5
+ g_show_rss_detail_info = OSWrapper::proc_rss_anon() != INVALID_VALUE;
+ g_col_process_rss =
+ define_column(process_cat, "rss", "all", "Resident set size, total", true);
+ g_col_process_rssanon =
+ define_column(process_cat, "rss", "anon", "Resident set size, anonymous memory [krn]", g_show_rss_detail_info);
+ g_col_process_rssfile =
+ define_column(process_cat, "rss", "file", "Resident set size, file mappings [krn]", g_show_rss_detail_info);
+ g_col_process_rssshmem =
+ define_column(process_cat, "rss", "shm", "Resident set size, shared memory [krn]", g_show_rss_detail_info);
+
+ g_col_process_swapped_out =
+ define_column(process_cat, nullptr, "swdo", "Memory swapped out", true);
+
+ // glibc heap info depends on, obviously, glibc.
+#ifdef __GLIBC__
+ const bool show_glibc_heap_info = true;
+#else
+ const bool show_glibc_heap_info = false;
+#endif
+ g_col_process_chp_used =
+ define_column(process_cat, "cheap", "usd", "C-Heap, in-use allocations (may be unavailable if RSS > 4G) [glibc]", show_glibc_heap_info);
+ g_col_process_chp_free =
+ define_column(process_cat, "cheap", "free", "C-Heap, bytes in free blocks (may be unavailable if RSS > 4G) [glibc]", show_glibc_heap_info);
+
+ g_col_process_cpu_user =
+ define_column(process_cat, "cpu", "us", "Process cpu user time", true);
+
+ g_col_process_cpu_system =
+ define_column(process_cat, "cpu", "sy", "Process cpu system time", true);
+
+ g_col_process_num_of =
+ define_column(process_cat, "io", "of", "Number of open files", true);
+
+ g_col_process_io_bytes_read =
+ define_column(process_cat, "io", "rd", "IO bytes read from storage or cache", true);
+
+ g_col_process_io_bytes_written =
+ define_column(process_cat, "io", "wr", "IO bytes written", true);
+
+ g_col_process_num_threads =
+ define_column(process_cat, nullptr, "thr", "Number of native threads", true);
+
+ return true;
+}
+
+static void set_value_in_sample(Column* col, Sample* sample, value_t val) {
+ if (col != nullptr) {
+ int index = col->index();
+ sample->set_value(index, val);
+ }
+}
+
+void sample_platform_values(Sample* sample) {
+
+ int idx = 0;
+
+ OSWrapper::update_if_needed();
+
+ if (g_show_system_memavail) {
+ set_value_in_sample(g_col_system_memavail, sample, OSWrapper::syst_avail());
+ }
+ set_value_in_sample(g_col_system_swap, sample, OSWrapper::syst_swap());
+
+ set_value_in_sample(g_col_system_memcommitted, sample, OSWrapper::syst_comm());
+ set_value_in_sample(g_col_system_memcommitted_ratio, sample, OSWrapper::syst_crt());
+
+ set_value_in_sample(g_col_system_pages_swapped_in, sample, OSWrapper::syst_si());
+ set_value_in_sample(g_col_system_pages_swapped_out, sample, OSWrapper::syst_so());
+
+ set_value_in_sample(g_col_system_cpu_user, sample, OSWrapper::syst_cpu_us());
+ set_value_in_sample(g_col_system_cpu_system, sample, OSWrapper::syst_cpu_sy());
+ set_value_in_sample(g_col_system_cpu_idle, sample, OSWrapper::syst_cpu_id());
+ set_value_in_sample(g_col_system_cpu_steal, sample, OSWrapper::syst_cpu_st());
+ set_value_in_sample(g_col_system_cpu_guest, sample, OSWrapper::syst_cpu_gu());
+
+ set_value_in_sample(g_col_system_num_procs_running, sample, OSWrapper::syst_tr());
+ set_value_in_sample(g_col_system_num_procs_blocked, sample, OSWrapper::syst_tb());
+
+ // cgroups business
+ if (g_show_cgroup_info) {
+ set_value_in_sample(g_col_system_cgrp_usage_in_bytes, sample, OSWrapper::syst_cgro_usg());
+ // set_value_in_sample(g_col_system_cgrp_memsw_usage_in_bytes, sample, OSWrapper::syst_cgro_usgsw());
+ set_value_in_sample(g_col_system_cgrp_kmem_usage_in_bytes, sample, OSWrapper::syst_cgro_kusg());
+ set_value_in_sample(g_col_system_cgrp_limit_in_bytes, sample, OSWrapper::syst_cgro_lim());
+ set_value_in_sample(g_col_system_cgrp_soft_limit_in_bytes, sample, OSWrapper::syst_cgro_slim());
+ // set_value_in_sample(g_col_system_cgrp_memsw_limit_in_bytes, sample, OSWrapper::syst_cgro_limsw());
+ }
+
+ set_value_in_sample(g_col_system_num_procs, sample, OSWrapper::syst_p());
+ set_value_in_sample(g_col_system_num_threads, sample, OSWrapper::syst_t());
+
+ set_value_in_sample(g_col_process_virt, sample, OSWrapper::proc_virt());
+ set_value_in_sample(g_col_process_swapped_out, sample, OSWrapper::proc_swdo());
+ set_value_in_sample(g_col_process_rss, sample, OSWrapper::proc_rss_all());
+
+ if (g_show_rss_detail_info) {
+ set_value_in_sample(g_col_process_rssanon, sample, OSWrapper::proc_rss_anon());
+ set_value_in_sample(g_col_process_rssfile, sample, OSWrapper::proc_rss_file());
+ set_value_in_sample(g_col_process_rssshmem, sample, OSWrapper::proc_rss_shm());
+ }
+
+ set_value_in_sample(g_col_process_num_threads, sample, OSWrapper::proc_thr());
+ set_value_in_sample(g_col_process_num_of, sample, OSWrapper::proc_io_of());
+
+ set_value_in_sample(g_col_process_io_bytes_read, sample, OSWrapper::proc_io_rd());
+ set_value_in_sample(g_col_process_io_bytes_written, sample, OSWrapper::proc_io_wr());
+
+ set_value_in_sample(g_col_process_cpu_user, sample, OSWrapper::proc_cpu_us());
+ set_value_in_sample(g_col_process_cpu_system, sample, OSWrapper::proc_cpu_sy());
+
+#ifdef __GLIBC__
+ set_value_in_sample(g_col_process_chp_used, sample, OSWrapper::proc_chea_usd());
+ set_value_in_sample(g_col_process_chp_free, sample, OSWrapper::proc_chea_free());
+#endif // __GLIBC__
+
+} // end: sample_platform_values
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/os/linux/vitals_linux_himemreport.cpp b/src/hotspot/os/linux/vitals_linux_himemreport.cpp
new file mode 100644
index 000000000000..86e68fce0b32
--- /dev/null
+++ b/src/hotspot/os/linux/vitals_linux_himemreport.cpp
@@ -0,0 +1,937 @@
+/*
+ * Copyright (c) 2022, 2025 SAP SE. 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.
+ *
+ */
+
+#include "jvm_io.h"
+#include "logging/log.hpp"
+#include "memory/allStatic.hpp"
+#include "nmt/memBaseline.hpp"
+#include "nmt/memReporter.hpp"
+#include "nmt/memTracker.hpp"
+#include "runtime/arguments.hpp"
+#include "runtime/globals.hpp"
+#include "runtime/globals_extension.hpp"
+#include "runtime/os.hpp"
+#include "runtime/vmOperations.hpp"
+#include "runtime/vmThread.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/macros.hpp"
+#include "utilities/ostream.hpp"
+#include "vitals/vitals_internals.hpp"
+#include "vitals_linux_himemreport.hpp"
+#include "vitals_linux_oswrapper.hpp"
+
+#include
+#include
+#include
+#include
+
+// Newer JDKS: NMT is always on and this macro does not exist
+// Older JDKs: NMT can be off at compile time; in that case INCLUDE_NMT
+// will be defined=0 via CFLAGS; or on, in that case it will be defined=1 in macros.hpp.
+#ifndef INCLUDE_NMT
+#define INCLUDE_NMT 1
+#endif
+
+// Logging and output:
+// We log during initialization phase to UL using the "vitals" tag.
+// In the high memory detection thread itself, when triggering the report, we write strictly to
+// stderr, directly. We don't use tty since we want to bypass ttylock. Sub command output also
+// gets written to stderr.
+
+// We print to the stderr stream directly in this code (since we want to bypass ttylock)
+static fdStream stderr_stream(2);
+
+namespace sapmachine_vitals {
+
+static const int HiMemReportDecaySeconds = 60 * 5;
+
+//////////// pretty printing stuff //////////////////////////////////
+
+#define STRFTIME_FROM_TIME_T(st, fmt, t) \
+ char buf[32]; \
+ struct tm timeinfo; \
+ if (::localtime_r(&t, &timeinfo) != nullptr && \
+ ::strftime(buf, sizeof(buf), fmt, &timeinfo) != 0) { \
+ st->print_raw(buf); \
+ } else { \
+ st->print_raw("unknown_date"); \
+ }
+
+
+static void print_date_and_time(outputStream *st, time_t t) {
+ STRFTIME_FROM_TIME_T(st, "%F %T", t);
+}
+
+// For use in file names
+static void print_date_and_time_underscored(outputStream *st, time_t t) {
+ STRFTIME_FROM_TIME_T(st, "%Y_%m_%d_%H_%M_%S", t);
+}
+
+static void print_current_date_and_time(outputStream *st) {
+ time_t t;
+ time(&t);
+ print_date_and_time(st, t);
+}
+
+//////////// Alert state ////////////////////////////////////////////
+
+class AlertState : public CHeapObj {
+
+ // this is 100%
+ const size_t _maximum;
+
+ // Alert percentages per level
+ static const int _alvl_perc[5];
+
+ // alert level: 0: all is well, 1..6: we are at x percent
+ int _alvl;
+
+ // time when alert level was increased last (for decay)
+ time_t _last_avlv_increase;
+
+ // We count spikes. A spike is a single increase to at least the lowest
+ // alert level, followed by a reset because we recovered.
+ int _spike_no;
+
+ int calc_percentage(size_t size) const {
+ return (int)((100.0f * (double)size)/(double)_maximum);
+ }
+
+ int calc_alvl(int percentage) const {
+ int i = 0;
+ while ((_alvl_perc[i + 1] != -1) && (_alvl_perc[i + 1] <= percentage)) {
+ i ++;
+ }
+ return i;
+ }
+
+public:
+
+ AlertState(size_t maximum) :
+ _maximum(maximum), _alvl(0), _last_avlv_increase(0), _spike_no(0) {
+ assert(_maximum > 0, "sanity");
+ }
+
+ size_t maximum() const {
+ return _maximum;
+ }
+
+ int current_spike_no() const {
+ return _spike_no;
+ }
+
+ int current_alert_level() const {
+ return _alvl;
+ }
+
+ static int alert_level_percentage(int alvl) {
+ assert(alvl >= 0 && alvl < (int)(sizeof(_alvl_perc) / sizeof(int)), "oob");
+ return _alvl_perc[alvl];
+ }
+
+ int current_alert_level_percentage() const {
+ return alert_level_percentage(_alvl);
+ }
+
+ // Update the state.
+ // If we changed the alert level (either increased it or reset it after decay),
+ // return true.
+ bool update(size_t current_size) {
+ const int percentage = calc_percentage(current_size);
+ const int new_alvl = calc_alvl(percentage);
+ // If we reached a new alert level, hold information and inform caller.
+ if (new_alvl > _alvl) {
+ // If we increased from zero, it means we entered a new spike, so
+ // increase spike number
+ if (_alvl == 0) {
+ _spike_no ++;
+ }
+ _alvl = new_alvl;
+ ::time(&_last_avlv_increase);
+ return true;
+ }
+ // If all is well now, but we had an alert situation before, and enough
+ // time has passed, reset alert level
+ if (new_alvl == 0 && _alvl > 0) {
+ time_t t;
+ time(&t);
+ if ((t - _last_avlv_increase) >= HiMemReportDecaySeconds) {
+ _alvl = 0;
+ _last_avlv_increase = 0;
+ return true;
+ }
+ }
+ return false;
+ }
+
+};
+
+const int AlertState::_alvl_perc[5] = { 0, 66, 75, 90, -1 };
+
+static AlertState* g_alert_state = nullptr;
+
+// What do we test?
+enum class compare_type {
+ compare_rss_vs_phys = 0, // We compare rss+swap vs total physical memory
+ compare_rss_vs_cgroup_limit = 1, // We compare rss+swap vs the cgroup limit
+ compare_rss_vs_manual_limit = 2, // HiMemReportMaximum is set, we compare rss+swap with that limit
+ compare_none
+};
+
+static compare_type g_compare_what = compare_type::compare_none;
+
+static const char* describe_maximum_by_compare_type(compare_type t) {
+ const char* s = "";
+ switch (g_compare_what) {
+ case compare_type::compare_rss_vs_cgroup_limit: s = "cgroup memory limit"; break;
+ case compare_type::compare_rss_vs_phys: s = "the half of total physical memory"; break;
+ case compare_type::compare_rss_vs_manual_limit: s = "HiMemReportMaximum"; break;
+ default: ShouldNotReachHere();
+ }
+ return s;
+}
+
+//////////// NMT stuff //////////////////////////////////////////////
+
+// NMT is nice, but the interface is unnecessary convoluted. For now, to keep merge surface small,
+// we work with what we have
+
+#if INCLUDE_NMT
+class NMTStuff : public AllStatic {
+
+ static MemBaseline _baseline;
+ static time_t _baseline_time;
+
+ // Fill a given baseline
+ static void fill_baseline(MemBaseline& baseline) {
+ const NMT_TrackingLevel lvl = MemTracker::tracking_level();
+ if (lvl >= NMT_summary) {
+ const bool summary_only = (lvl == NMT_summary);
+ baseline.baseline(summary_only);
+ }
+ }
+
+public:
+
+ static bool is_enabled() {
+ const NMT_TrackingLevel lvl = MemTracker::tracking_level();
+ // Note: I avoid assumptions about numerical values of NMT_TrackingLevel
+ // (e.g. "lvl >= NMT_summary") since their order changed over time and we
+ // want to be JDK-version-agnostic here.
+ return lvl == NMT_summary || lvl == NMT_detail;
+ }
+
+ // Capture a baseline right now
+ static void capture_baseline() {
+ fill_baseline(_baseline);
+ time(&_baseline_time);
+ }
+
+ // Do the best possible report with the given NMT tracking level.
+ // If we are at summary level, do a summary level report
+ // If we are at detail level, do a detail level report
+ // If we have a baseline captured, do a diff level report
+ static void report_as_best_as_possible(outputStream* st) {
+
+ if (NMTStuff::is_enabled()) {
+
+ // Get the state now
+ MemBaseline baseline_now;
+ fill_baseline(baseline_now);
+
+ // prepare and print suitable report
+ if (_baseline.baseline_type() == baseline_now.baseline_type()) {
+ // We already captured a baseline, and its type fits us (nobody changed NMT levels inbetween calls)
+ time_t t;
+ time(&t);
+ st->print("(diff against baseline taken at ");
+ print_date_and_time(st, _baseline_time);
+ st->print_cr(", %d seconds ago)", (int)(t - _baseline_time));
+ st->cr();
+ const bool summary_only = (baseline_now.baseline_type() == MemBaseline::Summary_baselined);
+ if (summary_only) {
+ MemSummaryDiffReporter rpt(_baseline, baseline_now, st, K);
+ rpt.report_diff();
+ } else {
+ MemDetailDiffReporter rpt(_baseline, baseline_now, st, K);
+ rpt.report_diff();
+ }
+ } else {
+ // We don't have a baseline yet. Just report the raw numbers
+ const bool summary_only = (baseline_now.baseline_type() == MemBaseline::Summary_baselined);
+ if (summary_only) {
+ MemSummaryReporter rpt(baseline_now, st, K);
+ rpt.report();
+ } else {
+ MemDetailReporter rpt(baseline_now, st, K);
+ rpt.report();
+ }
+ }
+ } else {
+ st->print_cr("NMT is disabled, nothing to print");
+ }
+
+ }
+
+ // If the situation calmed down, reset (clear the base line)
+ static void reset() {
+ _baseline_time = 0;
+ _baseline.reset();
+ }
+
+};
+
+MemBaseline NMTStuff::_baseline;
+time_t NMTStuff::_baseline_time = 0;
+#endif // INCLUDE_NMT
+
+//////////// Reporting //////////////////////////////////////////////
+
+class ReportDir : public CHeapObj {
+ // absolute, always ends with slash
+ stringStream _dir;
+
+public:
+
+ const char* path() const { return _dir.base(); }
+
+ bool initialize(const char* d) {
+
+ assert(d != nullptr && strlen(d) > 0, "sanity");
+ assert(_dir.size() == 0, "Only initialize once");
+
+ // Set _dir from d. Resolve relative path if d is relative, and ensure it always
+ // ends with "/"
+ if (d[0] != '/') {
+ char path[PATH_MAX];
+ const char* cwd = os::get_current_directory(path, sizeof(path));
+ if (cwd == nullptr) {
+ log_warning(vitals)("HiMemReportDir: Failed to resolve current directory (%d)", errno);
+ return false;
+ }
+ _dir.print("%s/", cwd);
+ }
+ _dir.print_raw(d);
+ const size_t l = ::strlen(d);
+ if (d[l - 1] != '/') {
+ _dir.put('/');
+ }
+
+ // Create the report directory (just the leaf dir, I don't bother creating the whole hierarchy)
+ struct stat s;
+ if (::stat(path(), &s) == -1) {
+ if (::mkdir(path(), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != -1) {
+ log_info(vitals)("HiMemReportDir: Created report directory \"%s\"", path());
+ } else {
+ log_warning(vitals)("HiMemReportDir: Failed to create report directory \"%s\" (%d)", path(), errno);
+ return false;
+ }
+ } else {
+ if (S_ISDIR(s.st_mode)) {
+ log_info(vitals)("HiMemReportDir: Found existing report directory at \"%s\"", path());
+ } else {
+ log_warning(vitals)("HiMemReportDir: \"%s\" exists, but its not a directory.", path());
+ return false;
+ }
+ }
+ // Test access by touching a file in this dir. For convenience, we leave the touched file in it
+ // and write the VM start time and some other info into it.
+ stringStream testfile;
+ testfile.print("%sVM_start.pid%d.log", path(), os::current_process_id());
+ fileStream fs(testfile.base());
+ if (!fs.is_open()) {
+ log_warning(os)("HiMemReportDir: Cannot write to \"%s\" (%d)", testfile.base(), errno);
+ return false;
+ }
+ print_current_date_and_time(&fs);
+ return true;
+ }
+};
+
+static ReportDir* g_report_dir = nullptr;
+
+static void print_high_memory_report_header(outputStream* st, const char* message, int pid, time_t t) {
+ char tmp[255];
+ st->print_cr("############");
+ st->print_cr("#");
+ st->print_cr("# High Memory Report:");
+ st->print_cr("# pid: %d thread id: %zd", pid, os::current_thread_id());
+ st->print_cr("# %s", message);
+ st->print_raw("# "); print_date_and_time(st, t); st->cr();
+ st->print_cr("# Spike number: %d", g_alert_state->current_spike_no());
+ st->print_cr("#");
+ st->flush();
+}
+
+static void print_high_memory_report(outputStream* st) {
+
+ // Note that this report may be interrupted by VM death, e.g. OOM killed.
+ // Therefore we frequently flush, and print the most important things first.
+
+ char buf[O_BUFLEN];
+
+ st->print_cr("vm_info: %s", VM_Version::internal_vm_info_string());
+
+ st->cr();
+ st->cr();
+ st->flush();
+
+ Arguments::print_summary_on(st);
+ st->cr();
+ st->cr();
+ st->flush();
+
+ st->print_cr("--- Vitals ---");
+ sapmachine_vitals::print_info_t info;
+ sapmachine_vitals::default_settings(&info);
+ info.sample_now = true;
+ info.no_legend = true;
+ sapmachine_vitals::print_report(st, &info);
+ st->print_cr("--- /Vitals ---");
+
+ st->cr();
+ st->cr();
+ st->flush();
+
+#if INCLUDE_NMT
+ st->cr();
+ st->print_cr("--- NMT report ---");
+ NMTStuff::report_as_best_as_possible(st);
+ st->print_cr("--- /NMT report ---");
+#endif
+
+ st->cr();
+ st->cr();
+ st->flush();
+
+ st->print_cr("#");
+ st->print_cr("# END: High Memory Report");
+ st->print_cr("#");
+
+ st->flush();
+}
+
+// Create a file name into the report directory: /.__.
+// (leave dir nullptr to just get a file name)
+static void print_file_name(stringStream* ss, const char* name, int pid, time_t timestamp, const char* suffix) {
+ assert(g_report_dir != nullptr, "must be");
+ const char* dir = g_report_dir->path();
+ // Should already have been made absolute, and should end with / (see ReportDir::initialize()).
+ assert(dir[0] == '/' && dir[::strlen(dir) - 1] == '/',
+ "bad value for report dir? %s", dir);
+ ss->print("%s", dir);
+ ss->print("%s_pid%d_", name, pid);
+ print_date_and_time_underscored(ss, timestamp);
+ ss->print("%s", suffix);
+}
+
+///////////////////// JCmd support //////////////////////////////////////////
+
+class ParsedCommand {
+
+ stringStream _name; // command name without args
+ stringStream _args; // arguments
+
+public:
+
+ ParsedCommand(const char* command) {
+ // trim front
+ const char* p = command;
+ while (isspace(*p)) {
+ p ++;
+ }
+ if ((*p) != '\0') {
+ // read name
+ while (!isspace(*p) && (*p) != '\0') {
+ _name.put(*p);
+ p++;
+ }
+ // find start of args; read args
+ while (isspace(*p)) {
+ p ++;
+ }
+ _args.print_raw(p);
+ }
+ }
+
+ bool is_empty() const { return _name.size() == 0; }
+ const char* name() const { return _name.base(); }
+
+ bool has_arguments() const { return _args.size() > 0; }
+ const char* args() const { return _args.base(); }
+
+ // Unfortunately, the DCmd framework lacks the ability to check DCmd without
+ // executing them. Here, we do some simple basic checks. Failing them will
+ // exit the VM right away, but passing them does still not mean the command
+ // is well formed since we don't check the arguments.
+ bool is_valid() const {
+ static const char* valid_prefixes[] = { "Compiler", "GC", "JFR", "JVMTI",
+ "Management", "System", "Thread",
+ "VM", "help", nullptr };
+ if (_name.size() > 0) {
+ for (const char** p = valid_prefixes; (*p) != nullptr; p ++) {
+ if (::strncasecmp(_name.base(), *p, ::strlen(*p)) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+};
+
+// Helper structures for posix_spawn_file_actions_t and posix_spawnattr_t where
+// cleanup depends on successful initialization.
+// Helper structures for posix_spawn_file_actions_t and posix_spawnattr_t where
+// cleanup depends on successful initialization.
+struct PosixSpawnFileActions {
+ posix_spawn_file_actions_t v;
+ const bool ok;
+ PosixSpawnFileActions() : ok(::posix_spawn_file_actions_init(&v) == 0) {}
+ ~PosixSpawnFileActions() { ok && ::posix_spawn_file_actions_destroy(&v); }
+};
+
+struct PosixSpawnAttr {
+ posix_spawnattr_t v;
+ const bool ok;
+ PosixSpawnAttr() : ok(::posix_spawnattr_init(&v) == 0) {}
+ ~PosixSpawnAttr() { ok && ::posix_spawnattr_destroy(&v); }
+};
+
+// Call jcmd. If outFile and errFile are not Null, redirect stdout and stderr, otherwise
+// print both stdout and stderr to VMs stderr.
+// Returns true if command was executed successfully and exitcode was 0, false otherwise.
+// If command failed, err_msg will contain an error string.
+static bool spawn_command(const char** argv, const char* outFile, const char* errFile, stringStream* err_msg) {
+
+ // I want vfork, but use posix_spawn, since vfork() is becoming obsolete and compilers
+ // will warn. Its also safer, and with modern glibcs it is as cheap as vfork.
+ PosixSpawnFileActions fa;
+ PosixSpawnAttr atr;
+
+ bool rc = fa.ok && atr.ok;
+
+ if (outFile != nullptr) { // Redirect stdout, stderr to files
+ assert(errFile != nullptr, "Require both");
+ rc = rc && (::posix_spawn_file_actions_addopen(&fa.v, 1, outFile, O_WRONLY | O_CREAT | O_TRUNC, 0664) == 0) &&
+ (::posix_spawn_file_actions_addopen(&fa.v, 2, errFile, O_WRONLY | O_CREAT | O_TRUNC, 0664) == 0);
+ } else { // Dup stdout to stderr
+ rc = rc && (::posix_spawn_file_actions_adddup2 (&fa.v, 2, 1) == 0);
+ }
+ pid_t child_pid = -1;
+
+ // Hint toward vfork. Note that newer glibcs (2.24+) will ignore this, but they use clone(),
+ // so its alright.
+ rc = rc && (posix_spawnattr_setflags(&atr.v, POSIX_SPAWN_USEVFORK) == 0);
+
+ if (rc == false) {
+ err_msg->print("Error during posix_spawn setup");
+ return false;
+ }
+
+ // Note about inheriting file descriptors: in theory, posix_spawn should close all stray descriptors:
+ // "If file_actions is a null pointer, then file descriptors open in the calling process shall remain open
+ // in the child process, except for those whose close-on- exec flag FD_CLOEXEC is set (see fcntl)."
+ // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnp.html)
+ // - which I assume means they get closed if we specify a file actions object, which we do.
+ rc = rc && (::posix_spawn(&child_pid, argv[0], &fa.v, &atr.v, (char**)argv, os::get_environ()) == 0);
+
+ if (rc) {
+ int status;
+ rc = (::waitpid(child_pid, &status, 0) != -1) &&
+ (WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ if (rc == false) {
+ err_msg->print("Command failed or crashed");
+ }
+ } else {
+ err_msg->print("posix_spawn failed (%s)", os::strerror(errno));
+ }
+
+ return rc;
+}
+
+// Calls a single jcmd via posix_spawn. Output is written to /---
+// if HiMemReportDir is given; to stdout if not.
+static void call_single_jcmd(const ParsedCommand* cmd, int pid, time_t t) {
+
+ // if report dir is given, calc .out and .err file names
+ const char* out_file = nullptr;
+ const char* err_file = nullptr;
+ stringStream out_file_ss, err_file_ss;
+ if (g_report_dir != nullptr) {
+ // output files are named _pid_.(out|err), e.g. "VM.info_4711_2022_08_01_07_52_22.out".
+ print_file_name(&out_file_ss, cmd->name(), pid, t, ".out");
+ out_file = out_file_ss.base();
+ print_file_name(&err_file_ss, cmd->name(), pid, t, ".err");
+ err_file = err_file_ss.base();
+ }
+
+ stringStream jcmd_executable;
+ jcmd_executable.print("%s/bin/jcmd", Arguments::get_java_home());
+
+ stringStream target_pid;
+ target_pid.print("%d", pid);
+
+ stringStream jcmd_command;
+ jcmd_command.print_raw(cmd->name());
+ if (cmd->has_arguments()) {
+ jcmd_command.put(' ');
+ jcmd_command.print_raw(cmd->args());
+ }
+
+ // Special consideration for GC.heap_dump: if the command was given without arguments, we append
+ // a file name for the heap dump ("/heapdump_pid_.dump")
+ if (!cmd->has_arguments() && ::strcmp(cmd->name(), "GC.heap_dump") == 0) {
+ jcmd_command.put(' ');
+ print_file_name(&jcmd_command, "GC.heap_dump", pid, t, ".dump");
+ }
+
+ const char* argv[4];
+ argv[0] = jcmd_executable.base();
+ argv[1] = target_pid.base();
+ argv[2] = jcmd_command.base();
+ argv[3] = nullptr;
+
+ stringStream err_msg;
+ const jlong t1 = os::javaTimeNanos();
+ if (spawn_command(argv, out_file, err_file, &err_msg)) {
+ const jlong t2 = os::javaTimeNanos();
+ const int command_time_ms = (t2 - t1) / (1000 * 1000);
+ stderr_stream.print("HiMemReport: Successfully executed \"%s\" (%d ms)", jcmd_command.base(), command_time_ms);
+ if (out_file != nullptr) {
+ stderr_stream.print(", output redirected to report dir");
+ }
+ stderr_stream.cr();
+ } else {
+ stderr_stream.print("HiMemReport: Failed to execute \"%s\" (%s)", jcmd_command.base(), err_msg.base());
+ }
+}
+
+// Helper, trims string
+static char* trim_string(char* s) {
+ char* p = s;
+ while (::isspace(*p)) p++;
+ char* p2 = p + ::strlen(p) - 1;
+ while (p2 > p && ::isspace(*p2)) {
+ *p2 = '\0';
+ p2--;
+ }
+ return p;
+}
+
+struct JcmdClosure {
+ virtual bool do_it(const char* cmd) = 0;
+};
+
+static bool iterate_exec_string(const char* exec_string, JcmdClosure* closure) {
+ char* exec_copy = os::strdup(exec_string);
+ char* save = nullptr;
+ for (char* tok = strtok_r(exec_copy, ";", &save);
+ tok != nullptr; tok = ::strtok_r(nullptr, ";", &save)) {
+ const char* p = trim_string(tok);
+ if (::strlen(p) > 0 && !closure->do_it(p)) {
+ os::free(exec_copy);
+ return false;
+ }
+ }
+ os::free(exec_copy);
+ return true;
+}
+
+class CallJCmdClosure : public JcmdClosure {
+ const pid_t _pid;
+ const time_t _time;
+public:
+ CallJCmdClosure(int pid, time_t time) : _pid(pid), _time(time) {}
+ bool do_it(const char* command_string) override {
+ ParsedCommand cmd(command_string);
+ assert(cmd.is_valid(), "Invalid command");
+ call_single_jcmd(&cmd, _pid, _time);
+ return true;
+ }
+};
+
+struct VerifyJCmdClosure : public JcmdClosure {
+ bool do_it(const char* command_string) override {
+ log_info(vitals)("HiMemReportExec: storing command \"%s\".", command_string);
+ if (!ParsedCommand(command_string).is_valid()) {
+ // We print a warning here, fingerpointing the specific command that failed, then exit the VM later.
+ log_warning(vitals)("HiMemReportExec: Command \"%s\" invalid.", command_string);
+ return false;
+ }
+ return true;
+ }
+};
+
+//////////////////// alert handling and reporting ///////////////////////////////////////////////////////////////
+
+static int g_num_alerts = 0;
+
+// We don't want to flood the report directory if the footprint of the VM wobbles strongly. We will give up
+// after a reasonable amount of reports have been printed.
+static const int max_spikes = 32;
+
+static void trigger_high_memory_report(int alvl, int spikeno, int percentage, size_t triggering_size) {
+
+ if (spikeno >= max_spikes) {
+ if (spikeno == max_spikes) {
+ stderr_stream.print_cr("# HiMemReport: Too many spikes encountered. Further reports will be omitted.");
+ }
+ return;
+ }
+
+ g_num_alerts ++;
+
+ stringStream reason;
+ reason.print("rss+swap (%zu K) larger than %d%% of %s (%zu K).",
+ triggering_size / K, percentage, describe_maximum_by_compare_type(g_compare_what),
+ g_alert_state->maximum() / K);
+ const char* message = reason.base();
+
+ const int pid = os::current_process_id();
+ time_t t;
+ time(&t);
+
+ bool printed = false;
+
+ print_high_memory_report_header(&stderr_stream, message, pid, t);
+
+ if (g_report_dir != nullptr) {
+ // Dump to file in report dir
+ stringStream ss;
+ print_file_name(&ss, "sapmachine_himemalert", pid, t, ".log");
+ fileStream fs(ss.base());
+ if (fs.is_open()) {
+ stderr_stream.print_cr("# Printing to %s", ss.base());
+ print_high_memory_report_header(&fs, message, pid, t);
+ print_high_memory_report(&fs);
+ printed = true;
+ } else {
+ stderr_stream.print_cr("# Failed to open %s. Printing to stderr instead.", ss.base());
+ stderr_stream.cr();
+ }
+ stderr_stream.flush();
+ }
+
+ if (!printed) {
+ print_high_memory_report(&stderr_stream);
+ }
+
+ stderr_stream.print_cr("# Done.");
+ stderr_stream.print_raw("#");
+ stderr_stream.cr();
+ stderr_stream.flush();
+
+ if (HiMemReportExec != nullptr) {
+ CallJCmdClosure closure(pid, t);
+ iterate_exec_string(HiMemReportExec, &closure);
+ }
+
+}
+
+///////////////// Monitor thread /////////////////////////////////////////
+
+void pulse_himem_report() {
+ assert(HiMemReport, "only call for +HiMemReport");
+ assert(g_compare_what != compare_type::compare_none && g_alert_state != nullptr, "Not initialized");
+
+ OSWrapper::update_if_needed();
+
+ const value_t rss = OSWrapper::proc_rss_all();
+ const value_t swap = OSWrapper::proc_swdo();
+ if (rss != INVALID_VALUE && swap != INVALID_VALUE) {
+ const size_t rss_swap = (size_t)rss + (size_t)swap;
+ const int old_alvl = g_alert_state->current_alert_level();
+ g_alert_state->update(rss_swap);
+ const int new_alvl = g_alert_state->current_alert_level();
+ const int spikeno = g_alert_state->current_spike_no();
+
+ if (new_alvl > old_alvl) {
+ const int new_percentage = g_alert_state->current_alert_level_percentage();
+ stderr_stream.print_cr("HiMemoryReport: rss+swap=%zu K - alert level increased to %d (>=%d%%).",
+ rss_swap / K, new_alvl, new_percentage);
+ int skipped = 0;
+ for (int i = old_alvl + 1; i < new_alvl; i ++) {
+ skipped ++;
+ // We may have missed some intermediary steps because the pulse interval was too large.
+ stderr_stream.print_cr("HiMemoryReport: ... seems we passed alert level %d (%d%%) without noticing.",
+ i, AlertState::alert_level_percentage(i));
+ }
+ // If the alert level increased to a new value, trigger a new report
+ trigger_high_memory_report(new_alvl, spikeno, new_percentage, rss_swap);
+#if INCLUDE_NMT
+ // Upon first alert, do a NMT baseline
+ if (old_alvl == 0 && new_alvl > 0) {
+ if (NMTStuff::is_enabled()) {
+ NMTStuff::capture_baseline();
+ stderr_stream.print_cr("HiMemoryReport: ... captured NMT baseline");
+ }
+ }
+#endif // INCLUDE_NMT
+ } else if (old_alvl > 0 && new_alvl == 0){
+ // Memory usage recovered, and we hit the decay time, and now all is well again.
+ stderr_stream.print_cr("HiMemoryReport: rss+swap=%zu K - seems we recovered. Resetting alert level.",
+ rss_swap / K);
+#if INCLUDE_NMT
+ NMTStuff::reset();
+#endif
+ }
+ }
+}
+
+class HiMemReportThread: public NamedThread {
+
+ static const int interval_seconds = 2;
+
+public:
+
+ HiMemReportThread() : NamedThread() {
+ this->set_name("himem reporter");
+ }
+
+ virtual void run() {
+ record_stack_base_and_size();
+ for (;;) {
+ pulse_himem_report();
+ os::naked_sleep(interval_seconds * 1000);
+ }
+ }
+
+};
+
+static HiMemReportThread* g_reporter_thread = nullptr;
+
+static bool initialize_reporter_thread() {
+ g_reporter_thread = new HiMemReportThread();
+ if (g_reporter_thread != nullptr) {
+ if (os::create_thread(g_reporter_thread, os::os_thread)) {
+ os::start_thread(g_reporter_thread);
+ }
+ return true;
+ }
+ return false;
+}
+
+///////////////// Externals //////////////////////////////////////////////
+
+extern void initialize_himem_report_facility() {
+
+ static bool initialized = false;
+ assert(initialized == false, "HiMemReport already initialized");
+ initialized = true;
+
+ // Note:
+ // unrecoverable errors:
+ // - errors the user can easily correct (bad arguments) cause exit right away
+ // - errors which are subject to environment and cannot be dealt with/are unpredictable
+ // cause facility to be disabled (with UL warning)
+
+ assert(HiMemReport, "only call for +HiMemReport");
+
+ assert(g_compare_what == compare_type::compare_none && g_alert_state == nullptr, "Only initialize once");
+
+ // Verify the exec string
+ VerifyJCmdClosure closure;
+ if (HiMemReportExec != nullptr && iterate_exec_string(HiMemReportExec, &closure) == false) {
+ vm_exit_during_initialization("Vitals HiMemReportExec: One or more Exec commands were invalid");
+ }
+
+ // We need to decide what we will compare with what. To do that, we get the current system values.
+ // - If user manually specified a maximum, we will compare rss+swap with that maximum
+ // - If we live inside a cgroup with a memory limit, we will compare process rss+swap vs this limit
+ // (snapshotted at VM start; maybe later we can react to dynamic limit changes, but for the moment I don't care)
+ // - If we do not live in a cgroup, or in a cgroup with no limit, compare process rss+swap vs the
+ // physical memory of the machine.
+ size_t limit = 0;
+ if (HiMemReportMax != 0) {
+ g_compare_what = compare_type::compare_rss_vs_manual_limit;
+ limit = HiMemReportMax;
+ log_info(vitals)("Vitals HiMemReport: Setting limit to HiMemReportMax (%zu K).", limit / K);
+ } else {
+ OSWrapper::update_if_needed();
+ if (OSWrapper::syst_cgro_lim() != INVALID_VALUE) {
+ // limit against cgroup limit
+ g_compare_what = compare_type::compare_rss_vs_cgroup_limit;
+ limit = (size_t)OSWrapper::syst_cgro_lim();
+ log_info(vitals)("Vitals HiMemReport: Setting limit to cgroup memory limit (%zu K).", limit / K);
+ } else if (OSWrapper::syst_phys() != INVALID_VALUE) {
+ // limit against total physical memory
+ g_compare_what = compare_type::compare_rss_vs_phys;
+ limit = (size_t)OSWrapper::syst_phys() / 2;
+ log_info(vitals)("Vitals HiMemReport: Setting limit to half of total physical memory (%zu K).", limit / K);
+ }
+ }
+
+ if (limit == 0) {
+ log_warning(vitals)("Vitals HiMemReport: limit could not be established; will disable high memory reports "
+ "(specify -XX:HiMemReportMax= to establish a manual limit).");
+ FLAG_SET_ERGO(HiMemReport, false);
+ return;
+ }
+
+ // HiMemReportDir:
+ // We fix up the report directory when VM starts, so if its relative, it refers to the initial current directory.
+ // If it cannot be established, we treat it as predictable argument error and exit the VM.
+ if (HiMemReportDir != nullptr && ::strlen(HiMemReportDir) > 0) {
+ g_report_dir = new ReportDir();
+ if (!g_report_dir->initialize(HiMemReportDir)) {
+ log_warning(vitals)("Vitals: Cannot access HiMemReportDir %s.", g_report_dir->path());
+ vm_exit_during_initialization("Vitals HiMemReport: Failed to create or access HiMemReportDir \"%s\".", g_report_dir->path());
+ return;
+ }
+ }
+
+ g_alert_state = new AlertState(limit);
+
+ if (!initialize_reporter_thread()) {
+ log_warning(vitals)("Vitals HiMemReport: Failed to start monitor thread. Will disable.");
+ FLAG_SET_ERGO(HiMemReport, false);
+ return;
+ }
+
+ log_info(vitals)("Vitals: HiMemReport subsystem initialized.");
+
+}
+
+extern void print_himemreport_state(outputStream* st) {
+ if (g_alert_state != nullptr) {
+ st->print("HiMemReport: monitoring rss+swap vs %s (%zu K)",
+ describe_maximum_by_compare_type(g_compare_what),
+ g_alert_state->maximum() / K);
+ if (g_alert_state->current_alert_level() == 0) {
+ st->print(", all is well");
+ } else {
+ st->print(", current level: %d (%d%%)", g_alert_state->current_alert_level(),
+ g_alert_state->current_alert_level_percentage());
+ }
+ st->print(", spikes: %d, alerts: %d", g_alert_state->current_spike_no(), g_num_alerts);
+ } else {
+ st->print("HiMemReport: not monitoring.");
+ }
+}
+
+// For printing in thread lists only.
+extern const Thread* himem_reporter_thread() { return g_reporter_thread; }
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/os/linux/vitals_linux_himemreport.hpp b/src/hotspot/os/linux/vitals_linux_himemreport.hpp
new file mode 100644
index 000000000000..cc173b1b853b
--- /dev/null
+++ b/src/hotspot/os/linux/vitals_linux_himemreport.hpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 SAP SE. All rights reserved.
+ * Copyright (c) 2022, 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.
+ *
+ */
+
+#ifndef OS_LINUX_VITALS_LINUX_HELPERS_HPP
+#define OS_LINUX_VITALS_LINUX_HELPERS_HPP
+
+#include "vitals/vitals_internals.hpp"
+
+namespace sapmachine_vitals {
+
+void initialize_himem_report_facility();
+
+void print_himemreport_state(outputStream* st);
+
+// For printing in thread lists only.
+extern const Thread* himem_reporter_thread();
+
+} // namespace sapmachine_vitals
+
+#endif // OS_LINUX_VITALS_LINUX_HELPERS_HPP
+
diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp
new file mode 100644
index 000000000000..639e77841ebd
--- /dev/null
+++ b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (c) 2022, 2025 SAP SE. 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.
+ *
+ */
+
+#include "jvm_io.h"
+#include "logging/log.hpp"
+#include "osContainer_linux.hpp"
+#include "runtime/os.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitals_internals.hpp"
+#include "vitals_linux_oswrapper.hpp"
+
+#include
+#include
+#include
+
+
+#define LOG_HERE_F(msg, ...) { printf("[%d] ", (int)::getpid()); ::printf(msg, __VA_ARGS__); printf("\n"); fflush(stdout); }
+#define LOG_HERE(msg) { printf("[%d] ", (int)::getpid()); ::printf("%s", msg); printf("\n"); fflush(stdout); }
+
+extern const char* sapmachine_get_memory_controller_path();
+
+namespace sapmachine_vitals {
+
+#define DEFINE_VARIABLE(name) \
+ value_t OSWrapper::_##name = INVALID_VALUE;
+
+ALL_VALUES_DO(DEFINE_VARIABLE)
+
+#undef DEFINE_VARIABLE
+
+time_t OSWrapper::_last_update = 0;
+
+static const int num_seconds_until_update = 1;
+
+///////////// procfs stuff //////////////////////////////////////////////////
+
+class ProcFile {
+ char* _buf;
+
+ // To keep the code simple, I just use a fixed sized buffer.
+ enum { bufsize = 64*K };
+
+public:
+
+ ProcFile() : _buf(nullptr) {
+ _buf = (char*)os::malloc(bufsize, mtInternal);
+ }
+
+ ~ProcFile () {
+ os::free(_buf);
+ }
+
+ bool read(const char* filename) {
+
+ FILE* f = ::fopen(filename, "r");
+ if (f == nullptr) {
+ log_debug(vitals)("Failed to fopen %s (%d)", filename, errno);
+ return false;
+ }
+
+ size_t bytes_read = ::fread(_buf, 1, bufsize - 1, f);
+ _buf[bytes_read] = '\0';
+
+ ::fclose(f);
+
+ return bytes_read > 0 && bytes_read < bufsize;
+ }
+
+ const char* text() const { return _buf; }
+
+ // Utility function; parse a number string as value_t
+ static value_t as_value(const char* text, size_t scale = 1) {
+ value_t value;
+ errno = 0;
+ char* endptr = nullptr;
+ value = (value_t)::strtoll(text, &endptr, 10);
+ if (endptr == text || errno != 0) {
+ value = INVALID_VALUE;
+ } else {
+ value *= scale;
+ }
+ return value;
+ }
+
+ // Return the start of the file, as number. Useful for proc files which
+ // contain a single number. Returns INVALID_VALUE if value did not parse
+ value_t as_value(size_t scale = 1) const {
+ return as_value(_buf, scale);
+ }
+
+ const char* get_prefixed_line(const char* prefix) const {
+ return ::strstr(_buf, prefix);
+ }
+
+ value_t parsed_prefixed_value(const char* prefix, size_t scale = 1) const {
+ value_t value = INVALID_VALUE;
+ const char* const s = get_prefixed_line(prefix);
+ if (s != nullptr) {
+ errno = 0;
+ const char* p = s + ::strlen(prefix);
+ return as_value(p, scale);
+ }
+ return value;
+ }
+
+};
+
+struct cpu_values_t {
+ value_t user;
+ value_t nice;
+ value_t system;
+ value_t idle;
+ value_t iowait;
+ value_t steal;
+ value_t guest;
+ value_t guest_nice;
+};
+
+static void parse_proc_stat_cpu_line(const char* line, cpu_values_t* out) {
+ // Note: existence of some of these values depends on kernel version
+ out->user = out->nice = out->system = out->idle = out->iowait = out->steal = out->guest = out->guest_nice =
+ INVALID_VALUE;
+ uint64_t user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
+ int num = ::sscanf(line,
+ "cpu "
+ UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " "
+ UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " " UINT64_FORMAT " ",
+ &user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice);
+ if (num >= 4) {
+ out->user = user;
+ out->nice = nice;
+ out->system = system;
+ out->idle = idle;
+ if (num >= 5) { // iowait (5) (since Linux 2.5.41)
+ out->iowait = iowait;
+ if (num >= 8) { // steal (8) (since Linux 2.6.11)
+ out->steal = steal;
+ if (num >= 9) { // guest (9) (since Linux 2.6.24)
+ out->guest = guest;
+ if (num >= 10) { // guest (9) (since Linux 2.6.33)
+ out->guest_nice = guest_nice;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+#ifdef __GLIBC__
+// We use either mallinfo (which may be obsolete or removed in newer glibc versions) or mallinfo2
+// (which does not exist prior to glibc 2.34).
+
+#define MALLINFO_MEMBER_DO(f) \
+ f(arena) \
+ f(ordblks) \
+ f(smblks) \
+ f(hblks) \
+ f(hblkhd) \
+ f(usmblks) \
+ f(fsmblks) \
+ f(uordblks) \
+ f(fordblks) \
+ f(keepcost)
+
+struct glibc_mallinfo {
+#define DEF_MALLINFO_MEMBER(f) int f;
+ MALLINFO_MEMBER_DO(DEF_MALLINFO_MEMBER)
+#undef DEF_MALLINFO_MEMBER
+};
+
+struct glibc_mallinfo2 {
+#define DEF_MALLINFO2_MEMBER(f) size_t f;
+ MALLINFO_MEMBER_DO(DEF_MALLINFO2_MEMBER)
+#undef DEF_MALLINFO2_MEMBER
+};
+
+typedef struct glibc_mallinfo (*mallinfo_func_t)(void);
+typedef struct glibc_mallinfo2 (*mallinfo2_func_t)(void);
+
+static mallinfo_func_t g_mallinfo = nullptr;
+static mallinfo2_func_t g_mallinfo2 = nullptr;
+
+static void mallinfo_init() {
+ g_mallinfo = CAST_TO_FN_PTR(mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo"));
+ g_mallinfo2 = CAST_TO_FN_PTR(mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2"));
+}
+
+#undef MALLINFO_MEMBER_DO
+
+#endif // __GLIBC__
+
+// Helper function, returns true if string is a numerical id
+static bool is_numerical_id(const char* s) {
+ const char* p = s;
+ while(*p >= '0' && *p <= '9') {
+ p ++;
+ }
+ return *p == '\0' ? true : false;
+}
+
+/////////////// cgroup stuff
+// We use part of the hotspot cgroup wrapper, but not all of it.
+// The reason:
+// - wrapper uses UL heavily, which I don't want to happen in a sampler thread (I only log in initialization, which is ok)
+// - wrapper does not expose all metrics I need (eg kmem)
+// What the wrapper does very nicely is the parse stuff, which I don't want to re-invent, therefore
+// I use the wrapper to get the controller path.
+
+class CGroups : public AllStatic {
+
+ static bool _containerized;
+ static const char* _file_usg;
+ static const char* _file_usgsw;
+ static const char* _file_lim;
+ static const char* _file_limsw;
+ static const char* _file_slim;
+ static const char* _file_kusg;
+
+public:
+
+ static bool initialize() {
+
+ // For the heck of it, I go through with initialization even if we are not
+ // containerized, since I like to know controller paths even for those cases.
+
+ _containerized = OSContainer::is_containerized();
+ log_debug(vitals)("Vitals cgroup initialization: containerized = %d", _containerized);
+
+ const char* controller_path = sapmachine_get_memory_controller_path();
+ if (controller_path == nullptr) {
+ log_debug(vitals)("Vitals cgroup initialization: controller path nullptr");
+ return false;
+ }
+ size_t pathlen = ::strlen(controller_path);
+ if (pathlen == 0) {
+ log_debug(vitals)("Vitals cgroup initialization: controller path empty?");
+ return false;
+ }
+ stringStream path;
+ if (controller_path[pathlen - 1] == '/') {
+ path.print("%s", controller_path);
+ } else {
+ path.print("%s/", controller_path);
+ }
+
+ log_debug(vitals)("Vitals cgroup initialization: controller path: %s", path.base());
+
+ // V1 or V2?
+ stringStream ss;
+ ss.print("%smemory.usage_in_bytes", path.base());
+ struct stat s;
+ const bool isv1 = os::file_exists(ss.base());
+ if (isv1) {
+ log_debug(vitals)("Vitals cgroup initialization: v1");
+ } else {
+ ss.reset();
+ ss.print("%smemory.current", path.base());
+ if (os::file_exists(ss.base())) {
+ // okay, its v2
+ log_debug(vitals)("Vitals cgroup initialization: v2");
+ } else {
+ log_debug(vitals)("Vitals cgroup initialization: no clue. Giving up.");
+ return false;
+ }
+ }
+
+ _file_usg = os::strdup(ss.base()); // so, we have that.
+
+#define STORE_PATH(variable, filename) \
+ ss.reset(); ss.print("%s%s", path.base(), filename); variable = os::strdup(ss.base());
+
+ if (isv1) {
+ STORE_PATH(_file_usgsw, "memory.memsw.usage_in_bytes");
+ STORE_PATH(_file_kusg, "memory.kmem.usage_in_bytes");
+ STORE_PATH(_file_lim, "memory.limit_in_bytes");
+ STORE_PATH(_file_limsw, "memory.memsw.limit_in_bytes");
+ STORE_PATH(_file_slim, "memory.soft_limit_in_bytes");
+ } else {
+ STORE_PATH(_file_usgsw, "memory.swap.current");
+ STORE_PATH(_file_kusg, "memory.kmem.usage_in_bytes");
+ STORE_PATH(_file_lim, "memory.max");
+ STORE_PATH(_file_limsw, "memory.swap.max");
+ STORE_PATH(_file_slim, "memory.low");
+ }
+#undef STORE_PATH
+
+#define LOG_PATH(variable) \
+ log_debug(vitals)("Vitals: %s=%s", #variable, variable == nullptr ? "" : variable);
+ LOG_PATH(_file_usg)
+ LOG_PATH(_file_usgsw)
+ LOG_PATH(_file_kusg)
+ LOG_PATH(_file_lim)
+ LOG_PATH(_file_limsw)
+ LOG_PATH(_file_slim)
+#undef LOG_PATH
+
+ // Initialization went through. We show columns if we are containerized.
+ return _containerized;
+ }
+
+ struct cgroup_values_t {
+ value_t lim;
+ value_t limsw;
+ value_t slim;
+ value_t usg;
+ value_t usgsw;
+ value_t kusg;
+ };
+
+ static bool get_stats(cgroup_values_t* v) {
+ v->lim = v->limsw = v->slim = v->usg = v->usgsw = v->kusg = INVALID_VALUE;
+ ProcFile pf;
+#define GET_VALUE(var) \
+ { \
+ const char* what = _file_ ## var; \
+ if (what != nullptr && pf.read(what)) { \
+ v-> var = pf.as_value(1); \
+ } \
+ }
+ GET_VALUE(usg);
+ GET_VALUE(usgsw);
+ GET_VALUE(kusg);
+ GET_VALUE(lim);
+ GET_VALUE(limsw);
+ GET_VALUE(slim);
+#undef GET_VALUE
+ // Cgroup limits defaults to PAGE_COUNTER_MAX in the kernel; so a very large number means "no limit"
+ // Note that on 64-bit, the default is LONG_MAX aligned down to pagesize; but I am not sure this is
+ // always true, so I just assume a very high value.
+ const size_t practically_infinite = LP64_ONLY(128 * K * G) NOT_LP64(4 * G);
+ if (v->lim > practically_infinite) v->lim = INVALID_VALUE;
+ if (v->slim > practically_infinite) v->slim = INVALID_VALUE;
+ if (v->limsw > practically_infinite) v->limsw = INVALID_VALUE;
+ return true;
+
+ } // end: CGroups::get_stats()
+
+}; // end: CGroups
+
+bool CGroups::_containerized = false;
+const char* CGroups::_file_usg = nullptr;
+const char* CGroups::_file_usgsw = nullptr;
+const char* CGroups::_file_lim = nullptr;
+const char* CGroups::_file_limsw = nullptr;
+const char* CGroups::_file_slim = nullptr;
+const char* CGroups::_file_kusg = nullptr;
+
+void OSWrapper::update_if_needed() {
+
+ time_t t;
+ time(&t);
+ if (t != (time_t)-1 &&
+ t < (_last_update + num_seconds_until_update)) {
+ return; // still good
+ }
+ _last_update = t;
+
+ static bool first_call = true;
+
+ // Update Values from ProcFS (and elsewhere)
+#define RESETVAL(name) _ ## name = INVALID_VALUE;
+ALL_VALUES_DO(RESETVAL)
+#undef RESETVAL
+
+ ProcFile bf;
+ if (bf.read("/proc/meminfo")) {
+
+ if (first_call) {
+ log_trace(vitals)("Read /proc/meminfo: \n%s", bf.text());
+ }
+
+ // All values in /proc/meminfo are in KB
+ const size_t scale = K;
+
+ _syst_phys = bf.parsed_prefixed_value("MemTotal:", scale);
+ _syst_avail = bf.parsed_prefixed_value("MemAvailable:", scale);
+
+ const value_t swap_total = bf.parsed_prefixed_value("SwapTotal:", scale);
+ const value_t swap_free = bf.parsed_prefixed_value("SwapFree:", scale);
+ if (swap_total != INVALID_VALUE && swap_free != INVALID_VALUE) {
+ _syst_swap = swap_total - swap_free;
+ }
+
+ // Calc committed ratio. Values > 100% indicate overcommitment.
+ const value_t commitlimit = bf.parsed_prefixed_value("CommitLimit:", scale);
+ const value_t committed = bf.parsed_prefixed_value("Committed_AS:", scale);
+ if (commitlimit != INVALID_VALUE && commitlimit != 0 && committed != INVALID_VALUE) {
+ _syst_comm = committed;
+ const value_t ratio = (committed * 100) / commitlimit;
+ _syst_crt = ratio;
+ }
+
+ }
+
+ if (bf.read("/proc/vmstat")) {
+ _syst_si = bf.parsed_prefixed_value("pswpin");
+ _syst_so = bf.parsed_prefixed_value("pswpout");
+ }
+
+ if (bf.read("/proc/stat")) {
+ // Read and parse global cpu values
+ cpu_values_t values;
+ const char* line = bf.get_prefixed_line("cpu");
+ parse_proc_stat_cpu_line(line, &values);
+
+ _syst_cpu_us = values.user + values.nice;
+ _syst_cpu_sy = values.system;
+ _syst_cpu_id = values.idle;
+ _syst_cpu_st = values.steal;
+ _syst_cpu_gu = values.guest + values.guest_nice;
+
+ // procs_running: this is actually number of threads running
+ // procs_blocked: number of threads blocked on real disk IO
+ // See https://utcc.utoronto.ca/~cks/space/blog/linux/ProcessStatesAndProcStat
+ // and https://lore.kernel.org/lkml/12601530441257@xenotime.net/#t
+ // and the canonical man page description at https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+ _syst_tr = bf.parsed_prefixed_value("procs_running");
+ _syst_tb = bf.parsed_prefixed_value("procs_blocked");
+ }
+
+ // cgroups business
+ CGroups::cgroup_values_t v;
+ if (CGroups::get_stats(&v)) {
+ _syst_cgro_usg = v.usg;
+ _syst_cgro_usgsw = v.usgsw;
+ _syst_cgro_kusg = v.kusg;
+ _syst_cgro_lim = v.lim;
+ _syst_cgro_limsw = v.limsw;
+ _syst_cgro_slim = v.slim;
+ }
+
+ if (bf.read("/proc/self/status")) {
+
+ _proc_virt = bf.parsed_prefixed_value("VmSize:", K);
+ _proc_swdo = bf.parsed_prefixed_value("VmSwap:", K);
+ _proc_rss_all = bf.parsed_prefixed_value("VmRSS:", K);
+ _proc_rss_anon = bf.parsed_prefixed_value("RssAnon:", K);
+ _proc_rss_file = bf.parsed_prefixed_value("RssFile:", K);
+ _proc_rss_shm = bf.parsed_prefixed_value("RssShmem:", K);
+
+ _proc_thr = bf.parsed_prefixed_value("Threads:");
+
+ }
+
+ // Number of open files: iterate over /proc/self/fd and count.
+ {
+ DIR* d = ::opendir("/proc/self/fd");
+ if (d != nullptr) {
+ value_t v = 0;
+ struct dirent* en = nullptr;
+ do {
+ en = ::readdir(d);
+ if (en != nullptr) {
+ v ++;
+ }
+ } while(en != nullptr);
+ ::closedir(d);
+ assert(v >= 2, "should have read at least '.' and '..'");
+ v -= 2; // We discount . and ..
+ _proc_io_of = v;
+ }
+ }
+
+ // Number of processes: iterate over /proc/ and count.
+ // Number of threads: read "num_threads" from /proc//stat
+ {
+ DIR* d = ::opendir("/proc");
+ if (d != nullptr) {
+ value_t v_p = 0;
+ value_t v_t = 0;
+ struct dirent* en = nullptr;
+ do {
+ en = ::readdir(d);
+ if (en != nullptr) {
+ if (is_numerical_id(en->d_name)) {
+ v_p ++;
+ char tmp[128];
+ jio_snprintf(tmp, sizeof(tmp), "/proc/%s/stat", en->d_name);
+ if (bf.read(tmp)) {
+ const char* text = bf.text();
+ // See man proc(5)
+ // (20) num_threads %ld
+ long num_threads = 0;
+ ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %*d %*d %ld", &num_threads);
+ v_t += num_threads;
+ }
+ }
+ }
+ } while(en != nullptr);
+ ::closedir(d);
+ _syst_p = v_p;
+ _syst_t = v_t;
+ }
+ }
+
+ if (bf.read("/proc/self/io")) {
+ _proc_io_rd = bf.parsed_prefixed_value("rchar:");
+ _proc_io_wr = bf.parsed_prefixed_value("wchar:");
+ }
+
+ if (bf.read("/proc/self/stat")) {
+ const char* text = bf.text();
+ // See man proc(5)
+ // (14) utime %lu
+ // (15) stime %lu
+ long unsigned cpu_utime = 0;
+ long unsigned cpu_stime = 0;
+ ::sscanf(text, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &cpu_utime, &cpu_stime);
+ _proc_cpu_us = cpu_utime;
+ _proc_cpu_sy = cpu_stime;
+ }
+
+#ifdef __GLIBC__
+ // Note "glibc heap used", from experiments and glibc source code reading, would be aprox. the sum
+ // of mmaped data area size (contains large allocations) and the small block sizes.
+ if (g_mallinfo2 != nullptr) {
+ glibc_mallinfo2 mi = g_mallinfo2();
+ _proc_chea_usd = mi.uordblks + mi.hblkhd;
+ _proc_chea_free = mi.fordblks;
+ } else if (g_mallinfo != nullptr) {
+ // disregard output from old style mallinfo if rss > 4g, since we cannot
+ // know whether we wrapped. For rss < 4g, we know values in mallinfo cannot
+ // have wrapped.
+ if (LP64_ONLY(_proc_rss_all < (4 * G)) NOT_LP64(true)) {
+ glibc_mallinfo mi = g_mallinfo();
+ _proc_chea_usd = (value_t)(unsigned)mi.uordblks + (value_t)(unsigned)mi.hblkhd;
+ _proc_chea_free = (value_t)(unsigned)mi.fordblks;
+ }
+ }
+#endif // __GLIBC__
+
+ first_call = false;
+
+}
+
+bool OSWrapper::initialize() {
+#ifdef __GLIBC__
+ mallinfo_init();
+#endif
+ return CGroups::initialize();
+}
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.hpp b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp
new file mode 100644
index 000000000000..f58813dda0e5
--- /dev/null
+++ b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2022 SAP SE. All rights reserved.
+ * Copyright (c) 2022, 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.
+ *
+ */
+
+#ifndef OS_LINUX_VITALS_LINUX_PROCFS_HPP
+#define OS_LINUX_VITALS_LINUX_PROCFS_HPP
+
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitals_internals.hpp"
+
+namespace sapmachine_vitals {
+
+class OSWrapper {
+
+ static time_t _last_update;
+
+#define ALL_VALUES_DO(f) \
+ f(syst_phys) \
+ f(syst_avail) \
+ f(syst_comm) \
+ f(syst_crt) \
+ f(syst_swap) \
+ f(syst_si) \
+ f(syst_so) \
+ f(syst_p) \
+ f(syst_t) \
+ f(syst_tr) \
+ f(syst_tb) \
+ f(syst_cpu_us) \
+ f(syst_cpu_sy) \
+ f(syst_cpu_id) \
+ f(syst_cpu_st) \
+ f(syst_cpu_gu) \
+ f(syst_cgro_lim) \
+ f(syst_cgro_limsw) \
+ f(syst_cgro_slim) \
+ f(syst_cgro_usg) \
+ f(syst_cgro_usgsw) \
+ f(syst_cgro_kusg) \
+ f(proc_virt) \
+ f(proc_rss_all) \
+ f(proc_rss_anon) \
+ f(proc_rss_file) \
+ f(proc_rss_shm) \
+ f(proc_swdo) \
+ f(proc_chea_usd) \
+ f(proc_chea_free) \
+ f(proc_cpu_us) \
+ f(proc_cpu_sy) \
+ f(proc_io_of) \
+ f(proc_io_rd) \
+ f(proc_io_wr) \
+ f(proc_thr) \
+
+#define DECLARE_VARIABLE(name) \
+ static value_t _##name;
+
+ALL_VALUES_DO(DECLARE_VARIABLE)
+
+#undef DECLARE_VARIABLE
+
+public:
+
+#define DEFINE_GETTER(name) \
+ static value_t name() { return _ ## name; }
+
+ALL_VALUES_DO(DEFINE_GETTER)
+
+#undef DEFINE_GETTER
+
+ static void update_if_needed();
+
+ static bool initialize();
+
+};
+
+} // namespace sapmachine_vitals
+
+#endif // OS_LINUX_VITALS_LINUX_HELPERS_HPP
diff --git a/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp b/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp
new file mode 100644
index 000000000000..748b3a88c5cd
--- /dev/null
+++ b/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp
@@ -0,0 +1,2611 @@
+/*
+ * Copyright (c) 2024, 2025 SAP SE. 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.
+ *
+ */
+
+#if defined(LINUX) || defined(__APPLE__)
+
+#include "code/codeCache.hpp"
+#include "jvm_io.h"
+#include "mallochooks.h"
+#include "malloctrace/mallocTracePosix.hpp"
+#include "runtime/arguments.hpp"
+#include "runtime/atomicAccess.hpp"
+#include "runtime/interfaceSupport.inline.hpp"
+#include "runtime/timer.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/powerOfTwo.hpp"
+#include "utilities/ticks.hpp"
+
+#include
+#include
+
+// To test in jtreg tests use
+// JTREG="JAVA_OPTIONS=-XX:+UseMallocHooks -XX:+MallocTraceAtStartup -XX:MallocTraceDumpCount=10 -XX:MallocTraceDumpInterval=10s -XX:MallocTraceDumpDelay=10s -XX:MallocTraceDumpOutput=`pwd`/mtrace_@pid.txt -XX:ErrorFile=`pwd`/hs_err%p.log"
+
+// A simple smoke test
+// jconsole -J-XX:+UseMallocHooks -J-XX:+MallocTraceAtStartup -J-XX:MallocTraceDumpCount=10 -J-XX:MallocTraceStackDepth=12 -J-XX:MallocTraceDumpInterval=10s -J-XX:MallocTraceDumpDelay=10s
+
+
+// Some compile time constants for the maps.
+
+constexpr double MAX_STACK_MAP_LOAD = 0.5;
+constexpr int STACK_MAP_INIT_SIZE = 1024;
+static_assert(is_power_of_2(STACK_MAP_INIT_SIZE), "stack map size must be power of 2");
+
+constexpr double MAX_ALLOC_MAP_LOAD = 0.5;
+constexpr int ALLOC_MAP_INIT_SIZE = 1024;
+static_assert(is_power_of_2(ALLOC_MAP_INIT_SIZE), "alloc map size must be power of 2");
+
+constexpr int MAX_FRAMES = 31;
+static_assert(is_power_of_2(MAX_FRAMES + 1), "max frames must be power of 2 minus 1");
+
+// The number of top frames to skip.
+constexpr int FRAMES_TO_SKIP = 0;
+
+constexpr int NR_OF_STACK_MAPS = 16;
+static_assert(is_power_of_2(NR_OF_STACK_MAPS), "nr of stack maps must be power of 2");
+
+constexpr int NR_OF_ALLOC_MAPS = 32;
+static_assert(is_power_of_2(NR_OF_ALLOC_MAPS), "nr of alloc maps must be power of 2");
+
+namespace sap {
+
+// The real allocation funcstions to use. This is be initialized later.
+static real_malloc_funcs_t* real_malloc_funcs = nullptr;
+
+static bool is_non_empty_string(char const* str) {
+ return (str != nullptr) && (str[0] != '\0');
+}
+
+static uint64_t parse_timespan_part(char const* start, char const* end, char const** error) {
+ char buf[32];
+
+ // Strip trailing spaces.
+ while ((end > start) && (end[-1] == ' ')) {
+ end--;
+ }
+
+ if (start == end) {
+ *error = "empty time";
+ return 0;
+ }
+
+ size_t size = (size_t) (end - start);
+
+ if (size >= sizeof(buf)) {
+ *error = "time too long";
+ return 0;
+ }
+
+ memcpy(buf, start, size);
+ buf[size] = '\0';
+
+ char* found_end;
+ int64_t result = (int64_t) strtoll(buf, &found_end, 10);
+
+ if ((found_end != end) && (*found_end != '\0')) {
+ *error = "Could not parse integer";
+ } else if (result < 0) {
+ *error = "negative time";
+ }
+
+ return (uint64_t) result;
+}
+
+static uint64_t parse_timespan(char const* spec, char const** error = nullptr) {
+ uint64_t result = 0;
+ char const* start = spec;
+ char const* pos = start;
+ char const* backup_error;
+ uint64_t limit_in_days = 365;
+
+ if (error == nullptr) {
+ error = &backup_error;
+ }
+
+ *error = nullptr;
+
+ while (*pos != '\0') {
+ switch (*pos) {
+ case ' ':
+ if (pos == start) {
+ start++;
+ }
+ break;
+
+ case 's':
+ result += parse_timespan_part(start, pos, error);
+ start = pos + 1;
+ break;
+
+ case 'm':
+ result += 60 * parse_timespan_part(start, pos, error);
+ start = pos + 1;
+ break;
+
+ case 'h':
+ result += 60 * 60 * parse_timespan_part(start, pos, error);
+ start = pos + 1;
+ break;
+
+ case 'd':
+ result += 24 * 60 * 60 * parse_timespan_part(start, pos, error);
+ start = pos + 1;
+ break;
+
+ default:
+ if ((*pos < '0') || (*pos > '9')) {
+ *error = "Unexpected character";
+ return 0;
+ }
+ }
+
+ pos++;
+ }
+
+ if (pos != start) {
+ *error = "time without unit";
+ }
+
+ if (result / (24 * 60 * 60) > limit_in_days) {
+ *error = "time too large";
+ }
+
+ return result;
+}
+
+// Keep sap namespace free from implementation classes.
+namespace mallocStatImpl {
+
+// Allocates memory of the same size. It's pretty fast, but doesn't return
+// free memory to the OS.
+class Allocator {
+private:
+ // We need padding, since we have arrays of this class used in parallel.
+ char _pre_pad[DEFAULT_CACHE_LINE_SIZE];
+ size_t _allocation_size;
+ int _entries_per_chunk;
+ void** _chunks;
+ int _nr_of_chunks;
+ void** _free_list;
+ size_t _free_entries;
+ char _post_pad[DEFAULT_CACHE_LINE_SIZE];
+
+public:
+ Allocator(size_t allocation_size, int entries_per_chunk);
+ ~Allocator();
+
+ void* allocate();
+ void free(void* ptr);
+ size_t allocated();
+ size_t unused();
+};
+
+Allocator::Allocator(size_t allocation_size, int entries_per_chunk) :
+ _allocation_size(align_up(allocation_size, 8)), // We need no stricter alignment
+ _entries_per_chunk(entries_per_chunk),
+ _chunks(nullptr),
+ _nr_of_chunks(0),
+ _free_list(nullptr),
+ _free_entries(0) {
+}
+
+Allocator::~Allocator() {
+ for (int i = 0; i < _nr_of_chunks; ++i) {
+ real_malloc_funcs->free(_chunks[i]);
+ }
+}
+
+void* Allocator::allocate() {
+ if (_free_list != nullptr) {
+ void** result = _free_list;
+ _free_list = (void**) result[0];
+ assert(_free_entries > 0, "free entries count invalid.");
+ _free_entries -= 1;
+
+ return result;
+ }
+
+ // We need a new chunk.
+ char* new_chunk = (char*) real_malloc_funcs->malloc(_entries_per_chunk * _allocation_size);
+
+ if (new_chunk == nullptr) {
+ return nullptr;
+ }
+
+ void** new_chunks = (void**) real_malloc_funcs->realloc(_chunks, sizeof(void**) * (_nr_of_chunks + 1));
+
+ if (new_chunks == nullptr) {
+ return nullptr;
+ }
+
+ new_chunks[_nr_of_chunks] = new_chunk;
+ _nr_of_chunks += 1;
+ _chunks = new_chunks;
+
+ for (int i = 0; i < _entries_per_chunk; ++i) {
+ free(new_chunk + i * _allocation_size);
+ }
+
+ return allocate();
+}
+
+void Allocator::free(void* ptr) {
+ if (ptr != nullptr) {
+ void** as_array = (void**) ptr;
+ as_array[0] = (void*) _free_list;
+ _free_list = as_array;
+ _free_entries += 1;
+ }
+}
+
+size_t Allocator::allocated() {
+ return _allocation_size * _entries_per_chunk * _nr_of_chunks;
+}
+
+size_t Allocator::unused() {
+#if defined(ASSERT)
+ size_t real_free_entries = 0;
+ void** entry = _free_list;
+
+ while (entry != nullptr) {
+ real_free_entries += 1;
+ entry = (void**) entry[0];
+ }
+
+ assert(_free_entries == real_free_entries, "free entries inconsistent");
+#endif
+
+ return _allocation_size * _free_entries;
+}
+
+class AddressHashSet {
+private:
+ int _mask;
+ int _count;
+ address* _set;
+
+ int get_slot(address to_check);
+
+public:
+ AddressHashSet(bool enabled);
+ ~AddressHashSet();
+
+ bool contains(address to_check);
+ bool add(address to_add);
+ size_t allocated();
+ // The average chain length.
+ double load();
+};
+
+AddressHashSet::AddressHashSet(bool enabled) :
+ _mask(enabled ? 0 : 1),
+ _count(0),
+ _set(nullptr) {
+}
+
+AddressHashSet::~AddressHashSet() {
+ real_malloc_funcs->free(_set);
+}
+
+int AddressHashSet::get_slot(address to_check) {
+ assert(to_check != nullptr, "Invalid value");
+
+ if (_set == nullptr) {
+ // Initialize lazily.
+ if (_mask == 0) {
+ _mask = 8191;
+ _set = (address*) real_malloc_funcs->calloc(_mask + 1, sizeof(address));
+ }
+
+ // When we overflow, return treat each address as not be contained. This is
+ // the safe behaviour for our use case.
+ return -1;
+ }
+
+ int slot = (int) (((uintptr_t) to_check) & _mask);
+
+ while (_set[slot] != nullptr) {
+ if (_set[slot] == to_check) {
+ return slot;
+ }
+
+ slot = (slot + 1) & _mask;
+ }
+
+ return slot;
+}
+
+bool AddressHashSet::contains(address to_check) {
+ int slot = get_slot(to_check);
+
+ return (slot >= 0) && (_set[slot] != nullptr);
+}
+
+bool AddressHashSet::add(address to_add) {
+ int slot = get_slot(to_add);
+
+ if ((slot < 0) || (_set[slot] != nullptr)) {
+ // Already present.
+ return false;
+ }
+
+ // Check if we should resize.
+ if (_count * 2 > _mask) {
+ address* old_set = _set;
+ int old_mask = _mask;
+
+ _mask = _mask * 2 + 1;
+ _count = 0;
+
+ _set = (address*) real_malloc_funcs->calloc(_mask + 1, sizeof(address));
+
+ // If full, we fall back to always return false.
+ if (_set == nullptr) {
+ real_malloc_funcs->free(old_set);
+
+ return false;
+ }
+
+ for (int i = 0; i <= old_mask; ++i) {
+ if (old_set[i] != nullptr) {
+ add(old_set[i]);
+ }
+ }
+
+ real_malloc_funcs->free(old_set);
+ add(to_add);
+ } else {
+ _set[slot] = to_add;
+ _count += 1;
+ }
+
+ return true;
+}
+
+size_t AddressHashSet::allocated() {
+ if (_set == nullptr) {
+ return 0;
+ }
+
+ return (_mask + 1) * sizeof(address);
+}
+
+double AddressHashSet::load() {
+ if (_set == nullptr) {
+ return 0.0;
+ }
+
+ return 1.0 * _count / (_mask + 1);
+}
+
+class Locker : public StackObj {
+private:
+ pthread_mutex_t* _mutex;
+
+public:
+ Locker(pthread_mutex_t* mutex, bool disabled = false);
+ ~Locker();
+};
+
+Locker::Locker(pthread_mutex_t* lock, bool disabled) :
+ _mutex(disabled ? nullptr : lock) {
+ if ((_mutex != nullptr) && (pthread_mutex_lock(_mutex) != 0)) {
+ fatal("Could not lock mutex");
+ }
+}
+
+Locker::~Locker() {
+ if ((_mutex != nullptr) && (pthread_mutex_unlock(_mutex) != 0)) {
+ fatal("Could not unlock mutex");
+ }
+}
+
+// Entry for the hash map containing statistics about allocation stack traces.
+class StatEntry {
+private:
+ StatEntry* _next;
+ uint64_t _hash_and_nr_of_frames;
+ uint64_t _size;
+ uint64_t _count;
+ address _frames[];
+
+public:
+ StatEntry(uint64_t hash, size_t size, int nr_of_frames, address* frames) :
+ _next(nullptr),
+ _hash_and_nr_of_frames((hash * (MAX_FRAMES + 1)) + nr_of_frames),
+ _size(size),
+ _count(1) {
+ assert(nr_of_frames >= 0, "Must not be negative");
+ assert(nr_of_frames <= MAX_FRAMES, "too many frames");
+ memcpy(_frames, frames, sizeof(address) * nr_of_frames);
+ assert(hash == this->hash(), "Must be the same: " UINT64_FORMAT " " UINT64_FORMAT, hash, this->hash());
+ assert(nr_of_frames == this->nr_of_frames(), "Must be equal");
+
+ }
+
+ uint64_t hash() {
+ return _hash_and_nr_of_frames / (MAX_FRAMES + 1);
+ }
+
+ static int scaled_hash(uint64_t hash) {
+ return hash / NR_OF_STACK_MAPS;
+ }
+
+ static size_t size(int frames) {
+ return sizeof(StatEntry) + sizeof(address) * frames;
+ }
+
+ StatEntry* next() {
+ return _next;
+ }
+
+ void set_next(StatEntry* next) {
+ _next = next;
+ }
+
+ void add_allocation(size_t size) {
+ _size += size;
+ _count += 1;
+ }
+
+ void remove_allocation(size_t size) {
+ assert(_size >= size, "Size cannot get negative (" UINT64_FORMAT " removed from " \
+ UINT64_FORMAT ", count " UINT64_FORMAT ")", (uint64_t) size, _size, _count);
+ assert(_count >= 1, "Count cannot get negative");
+ _size -= size;
+ _count -= 1;
+ }
+
+ uint64_t size() {
+ return _size;
+ }
+
+ uint64_t count() {
+ return _count;
+ }
+
+ int nr_of_frames() {
+ return _hash_and_nr_of_frames & MAX_FRAMES;
+ }
+
+ address* frames() {
+ return _frames;
+ }
+};
+
+
+struct StatEntryCopy {
+ StatEntry* _entry;
+ uint64_t _size;
+ uint64_t _count;
+};
+
+// The entry for a single allocation. Note that we don't store the pointer itself
+// but use the hash code instead. Our hash function is resersible, so this is OK.
+class AllocEntry {
+private:
+ uint64_t _hash;
+ StatEntry* _entry;
+ AllocEntry* _next;
+ DEBUG_ONLY(void* _ptr); // Is not really needed, but helps debugging.
+
+public:
+
+ AllocEntry(uint64_t hash, StatEntry* entry, AllocEntry* next DEBUG_ONLY(COMMA void* ptr)) :
+ _hash(hash),
+ _entry(entry),
+ _next(next)
+ DEBUG_ONLY(COMMA _ptr(ptr)) {
+ }
+
+ uint64_t hash() {
+ return _hash;
+ }
+
+ static int scaled_hash(uint64_t hash) {
+ return hash / NR_OF_ALLOC_MAPS;
+ }
+
+ StatEntry* entry() {
+ return _entry;
+ }
+
+ AllocEntry* next() {
+ return _next;
+ }
+
+ void set_next(AllocEntry* next) {
+ _next = next;
+ }
+
+ AllocEntry** next_ptr() {
+ return &_next;
+ }
+
+#if defined(ASSERT)
+ void* ptr() {
+ return _ptr;
+ }
+#endif
+};
+
+
+
+static register_hooks_t* register_hooks;
+static get_real_malloc_funcs_t* get_real_malloc_funcs;
+
+#if defined(__APPLE__)
+#define LD_PRELOAD "DYLD_INSERT_LIBRARIES"
+#define LIB_MALLOC_HOOKS "libmallochooks.dylib"
+#else
+#define LD_PRELOAD "LD_PRELOAD"
+#define LIB_MALLOC_HOOKS "libmallochooks.so"
+#endif
+
+static void print_needed_preload_env(outputStream* st) {
+ st->print_cr("%s=%s/%s", LD_PRELOAD, Arguments::get_dll_dir(), LIB_MALLOC_HOOKS);
+ st->print_cr("Its current value is %s", getenv(LD_PRELOAD));
+}
+
+static void remove_malloc_hooks_from_env() {
+ char const* env = ::getenv(LD_PRELOAD);
+
+ if ((env == nullptr) || (env[0] == '\0')) {
+ return;
+ }
+
+ // Create a env with ':' prepended and appended. This makes the
+ // code easier.
+ stringStream guarded_env;
+ guarded_env.print(":%s:", env);
+
+ stringStream new_env;
+ size_t len = strlen(LIB_MALLOC_HOOKS);
+ char const* base = guarded_env.base();
+ char const* pos = base;
+
+ while ((pos = strstr(pos, LIB_MALLOC_HOOKS)) != nullptr) {
+ if (pos[len] != ':') {
+ pos += 1;
+
+ continue;
+ }
+
+ if (pos[-1] == ':') {
+ new_env.print("%.*s%s", (int) (pos - base) - 1, base, pos + len);
+ } else if (pos[-1] == '/') {
+ char const* c = pos - 1;
+
+ while (c[0] != ':') {
+ --c;
+ }
+
+ new_env.print("%.*s%s", (int) (c - base + 1), base, pos + len + 1);
+ } else {
+ pos += 1;
+
+ continue;
+ }
+
+ if (new_env.size() <= 2) {
+ ::unsetenv(LD_PRELOAD);
+ } else {
+ stringStream ss;
+ ss.print("%.*s", MAX(0, (int) (new_env.size() - 2)), new_env.base() + 1);
+ ::setenv(LD_PRELOAD, ss.base(), 1);
+ }
+
+ return;
+ }
+}
+
+typedef int backtrace_func_t(void** stacks, int max_depth);
+
+template struct HashMapData {
+ char _front_padding[DEFAULT_CACHE_LINE_SIZE];
+ Entry** _entries;
+ pthread_mutex_t _lock;
+ int _mask;
+ int _size;
+ int _limit;
+ Allocator* _alloc;
+ char _back_padding[DEFAULT_CACHE_LINE_SIZE];
+
+ HashMapData() :
+ _entries(nullptr),
+ _mask(0),
+ _size(0),
+ _limit(0),
+ _alloc(nullptr) {
+ }
+
+ void resize(int new_mask, double max_load) {
+ assert(is_power_of_2(new_mask + 1), "Must be a power of 2 minus 1");
+
+ Entry** new_entries = (Entry**) real_malloc_funcs->calloc(new_mask + 1, sizeof(Entry*));
+ Entry** old_entries = _entries;
+
+ // Fail silently if we don't get the memory.
+ if (new_entries != nullptr) {
+ for (int i = 0; i <= _mask; ++i) {
+ Entry* entry = old_entries[i];
+
+ while (entry != nullptr) {
+ Entry* next_entry = entry->next();
+ int slot = Entry::scaled_hash(entry->hash()) & new_mask;
+ entry->set_next(new_entries[slot]);
+ new_entries[slot] = entry;
+ entry = next_entry;
+ }
+ }
+
+ _entries = new_entries;
+ _mask = new_mask;
+ _limit = (int) ((_mask + 1) * max_load);
+ real_malloc_funcs->free(old_entries);
+ }
+ }
+
+ void cleanup() {
+ Locker locker(&_lock);
+
+ if (_alloc != nullptr) {
+ _alloc->~Allocator();
+ real_malloc_funcs->free(_alloc);
+ _alloc = nullptr;
+ }
+
+ if (_entries != nullptr) {
+ real_malloc_funcs->free(_entries);
+ _entries = nullptr;
+ }
+ }
+};
+
+typedef HashMapData StackMapData;
+typedef HashMapData AllocMapData;
+
+class MallocStatisticImpl : public AllStatic {
+private:
+
+ static backtrace_func_t* _backtrace;
+ static char const* _backtrace_name;
+ static bool _use_backtrace;
+ static volatile bool _initialized;
+ static bool _enabled;
+ static bool _shutdown;
+ static bool _track_free;
+ static bool _detailed_stats;
+ static bool _tried_to_load_backtrace;
+ static int _max_frames;
+ static registered_hooks_t _malloc_stat_hooks;
+ static pthread_mutex_t _malloc_stat_lock;
+ static bool _check_malloc_suspended;
+ static pthread_key_t _malloc_suspended;
+ static volatile int _enable_count;
+
+ static StackMapData _stack_maps_data[NR_OF_STACK_MAPS];
+ static AllocMapData _alloc_maps_data[NR_OF_ALLOC_MAPS];
+
+ static uint64_t _to_track_mask;
+ static uint64_t _to_track_limit;
+
+ static volatile uint64_t _stack_walk_time;
+ static volatile uint64_t _stack_walk_count;
+ static volatile uint64_t _tracked_ptrs;
+ static volatile uint64_t _not_tracked_ptrs;
+ static volatile uint64_t _failed_frees;
+
+ static void* _rainy_day_fund;
+ static registered_hooks_t _rainy_day_hooks;
+ static pthread_mutex_t _rainy_day_fund_lock;
+ static volatile bool _rainy_day_fund_used;
+
+ static void set_malloc_suspended(bool suspended);
+ static bool malloc_suspended();
+
+ // The hooks.
+ static void* malloc_hook(size_t size, void* caller_address);
+ static void* calloc_hook(size_t elems, size_t size, void* caller_address);
+ static void* realloc_hook(void* ptr, size_t size, void* caller_address);
+ static void free_hook(void* ptr, void* caller_address);
+ static int posix_memalign_hook(void** ptr, size_t align, size_t size, void* caller_address);
+ static void* memalign_hook(size_t align, size_t size, void* caller_address);
+ static void* aligned_alloc_hook(size_t align, size_t size, void* caller_address);
+ static void* valloc_hook(size_t size, void* caller_address);
+ static void* pvalloc_hook(size_t size, void* caller_address);
+
+ // The hooks used after we use the rainy day fund
+ static void* malloc_hook_rd(size_t size, void* caller_address);
+ static void* calloc_hook_rd(size_t elems, size_t size, void* caller_address);
+ static void* realloc_hook_rd(void* ptr, size_t size, void* caller_addresse);
+ static void free_hook_rd(void* ptr, void* caller_address);
+ static int posix_memalign_hook_rd(void** ptr, size_t align, size_t size, void* caller_address);
+ static void* memalign_hook_rd(size_t align, size_t size, void* caller_address);
+ static void* aligned_alloc_hook_rd(size_t align, size_t size, void* caller_address);
+ static void* valloc_hook_rd(size_t size, void* caller_address);
+ static void* pvalloc_hook_rd(size_t size, void* caller_address);
+ static void wait_for_rainy_day_fund();
+
+ static StatEntry* record_allocation_size(size_t to_add, int nr_of_frames, address* frames,
+ int* enable_count = nullptr);
+ static void record_allocation(void* ptr, uint64_t hash, int nr_of_frames, address* frames);
+ static StatEntry* record_free(void* ptr, uint64_t hash, size_t size);
+
+ static uint64_t ptr_hash_impl(void* ptr);
+ static uint64_t ptr_hash(void* ptr);
+ static bool should_track(uint64_t hash);
+ static int capture_stack(address* frames, address real_func, address caller);
+
+ static bool setup_hooks(registered_hooks_t* hooks, outputStream* st);
+ static void cleanup();
+
+ static bool dump_entry(outputStream* st, StatEntryCopy* entry, int index,
+ uint64_t total_size, uint64_t total_count, int total_entries,
+ char const* filter, AddressHashSet* filter_cache);
+
+public:
+ static void initialize();
+ static bool rainy_day_fund_used();
+ static bool enable(outputStream* st, TraceSpec const& spec);
+ static bool disable(outputStream* st);
+ static bool dump(outputStream* msg_stream, outputStream* dump_stream, DumpSpec const& spec);
+ static void shutdown();
+};
+
+registered_hooks_t MallocStatisticImpl::_malloc_stat_hooks = {
+ MallocStatisticImpl::malloc_hook,
+ MallocStatisticImpl::calloc_hook,
+ MallocStatisticImpl::realloc_hook,
+ MallocStatisticImpl::free_hook,
+ MallocStatisticImpl::posix_memalign_hook,
+ MallocStatisticImpl::memalign_hook,
+ MallocStatisticImpl::aligned_alloc_hook,
+ MallocStatisticImpl::valloc_hook,
+ MallocStatisticImpl::pvalloc_hook
+};
+
+registered_hooks_t MallocStatisticImpl::_rainy_day_hooks = {
+ MallocStatisticImpl::malloc_hook_rd,
+ MallocStatisticImpl::calloc_hook_rd,
+ MallocStatisticImpl::realloc_hook_rd,
+ MallocStatisticImpl::free_hook_rd,
+ MallocStatisticImpl::posix_memalign_hook_rd,
+ MallocStatisticImpl::memalign_hook_rd,
+ MallocStatisticImpl::aligned_alloc_hook_rd,
+ MallocStatisticImpl::valloc_hook_rd,
+ MallocStatisticImpl::pvalloc_hook_rd
+};
+
+backtrace_func_t* MallocStatisticImpl::_backtrace;
+char const* MallocStatisticImpl::_backtrace_name;
+bool MallocStatisticImpl::_use_backtrace;
+volatile bool MallocStatisticImpl::_initialized;
+bool MallocStatisticImpl::_enabled;
+bool MallocStatisticImpl::_shutdown;
+bool MallocStatisticImpl::_track_free;
+bool MallocStatisticImpl::_detailed_stats;
+bool MallocStatisticImpl::_tried_to_load_backtrace;
+int MallocStatisticImpl::_max_frames;
+pthread_mutex_t MallocStatisticImpl::_malloc_stat_lock;
+volatile int MallocStatisticImpl::_enable_count;
+bool MallocStatisticImpl::_check_malloc_suspended;
+pthread_key_t MallocStatisticImpl::_malloc_suspended;
+StackMapData MallocStatisticImpl::_stack_maps_data[NR_OF_STACK_MAPS];
+AllocMapData MallocStatisticImpl::_alloc_maps_data[NR_OF_ALLOC_MAPS];
+uint64_t MallocStatisticImpl::_to_track_mask;
+uint64_t MallocStatisticImpl::_to_track_limit;
+volatile uint64_t MallocStatisticImpl::_stack_walk_time;
+volatile uint64_t MallocStatisticImpl::_stack_walk_count;
+volatile uint64_t MallocStatisticImpl::_tracked_ptrs;
+volatile uint64_t MallocStatisticImpl::_not_tracked_ptrs;
+volatile uint64_t MallocStatisticImpl::_failed_frees;
+void* MallocStatisticImpl::_rainy_day_fund;
+pthread_mutex_t MallocStatisticImpl::_rainy_day_fund_lock;
+volatile bool MallocStatisticImpl::_rainy_day_fund_used;
+
+ALWAYSINLINE int MallocStatisticImpl::capture_stack(address* frames, address real_func, address caller) {
+ uint64_t ticks = _detailed_stats ? Ticks::now().nanoseconds() : 0;
+ int nr_of_frames = 0;
+
+ if (_max_frames <= 2) {
+ // Skip, since we will fill it in later anyway.
+ } else if (_use_backtrace) {
+ nr_of_frames = _backtrace((void**) frames, _max_frames + FRAMES_TO_SKIP);
+ } else {
+ // We have to unblock SIGSEGV signal handling, since os::is_first_C_frame()
+ // calls SafeFetch, which needs the proper handling of SIGSEGV.
+ sigset_t curr, old;
+ sigemptyset(&curr);
+ sigaddset(&curr, SIGSEGV);
+ pthread_sigmask(SIG_UNBLOCK, &curr, &old);
+ frame fr = os::current_frame();
+
+ while (fr.pc() && nr_of_frames < _max_frames + FRAMES_TO_SKIP) {
+ frames[nr_of_frames] = fr.pc();
+ nr_of_frames += 1;
+
+ if (nr_of_frames >= _max_frames + FRAMES_TO_SKIP) {
+ break;
+ }
+
+ if (fr.fp() == nullptr || fr.cb() != nullptr || fr.sender_pc() == nullptr || os::is_first_C_frame(&fr)) {
+ break;
+ }
+
+ fr = os::get_sender_for_C_frame(&fr);
+ }
+
+ pthread_sigmask(SIG_SETMASK, &old, nullptr);
+ }
+
+ // We know at least the function and the caller.
+ if (nr_of_frames < 2) {
+ frames[0] = real_func;
+ frames[1] = caller;
+ nr_of_frames = MAX2(2, _max_frames);
+ }
+
+ if (_detailed_stats) {
+ AtomicAccess::add(&_stack_walk_time, Ticks::now().nanoseconds() - ticks);
+ AtomicAccess::add(&_stack_walk_count, (uint64_t) 1);
+ }
+
+ return nr_of_frames;
+}
+
+static void after_child_fork() {
+ if (register_hooks != nullptr) {
+ register_hooks(nullptr);
+ }
+}
+
+
+bool MallocStatisticImpl::setup_hooks(registered_hooks_t* hooks, outputStream* st) {
+ if (register_hooks == nullptr) {
+ register_hooks = (register_hooks_t*) dlsym((void*) RTLD_DEFAULT, REGISTER_HOOKS_NAME);
+ get_real_malloc_funcs = (get_real_malloc_funcs_t*) dlsym((void*) RTLD_DEFAULT,
+ GET_REAL_MALLOC_FUNCS_NAME);
+
+ if ((register_hooks == nullptr) || (get_real_malloc_funcs == nullptr)) {
+ if (UseMallocHooks) {
+ st->print_raw_cr("Could not find preloaded libmallochooks while -XX:+UseMallocHooks is set. " \
+ "This usually happens if the VM is not loaded via the JDK launcher (e.g. " \
+ "java.exe). In this case you must preload the library by setting the " \
+ "following environment variable: ");
+ print_needed_preload_env(st);
+ } else {
+ st->print_cr("Could not find preloaded libmallochooks. Try using -XX:+UseMallocHooks " \
+ "VM option to automatically preload it using the JDK launcher. Or you can set " \
+ "the following environment variable: ");
+ print_needed_preload_env(st);
+ }
+
+ st->print_raw_cr("VM arguments:");
+ Arguments::print_summary_on(st);
+
+ return false;
+ }
+ }
+
+ real_malloc_funcs = get_real_malloc_funcs();
+ register_hooks(hooks);
+
+ return true;
+}
+
+// Note that this function must be resersible. We
+// rely on it having unique values for a pointer.
+// See https://github.com/skeeto/hash-prospector?tab=readme-ov-file#reversible-operation-selection
+// for a list of operations which are resersible.
+uint64_t MallocStatisticImpl::ptr_hash_impl(void* ptr) {
+ uint64_t hash = (uint64_t) ptr;
+ hash = (~hash) + (hash << 21);
+ hash = hash ^ (hash >> 24);
+ hash = (hash + (hash << 3)) + (hash << 8);
+ hash = hash ^ (hash >> 14);
+ hash = (hash + (hash << 2)) + (hash << 4);
+ hash = hash ^ (hash >> 28);
+ hash = hash + (hash << 31);
+
+ return hash;
+}
+
+uint64_t MallocStatisticImpl::ptr_hash(void* ptr) {
+ if (!_track_free && (_to_track_mask == 0)) {
+ return 0;
+ }
+
+ return ptr_hash_impl(ptr);
+}
+
+bool MallocStatisticImpl::should_track(uint64_t hash) {
+ if (_detailed_stats) {
+ if ((hash & _to_track_mask) < _to_track_limit) {
+ AtomicAccess::add(&_tracked_ptrs, (uint64_t) 1);
+ } else {
+ AtomicAccess::add(&_not_tracked_ptrs, (uint64_t) 1);
+ }
+ }
+
+ return (hash & _to_track_mask) < _to_track_limit;
+}
+
+void MallocStatisticImpl::set_malloc_suspended(bool suspended) {
+ _check_malloc_suspended = suspended;
+ pthread_setspecific(_malloc_suspended, suspended ? (void*) 1 : nullptr);
+}
+
+bool MallocStatisticImpl::malloc_suspended() {
+ return _check_malloc_suspended && (pthread_getspecific(_malloc_suspended) != nullptr);
+}
+
+void* MallocStatisticImpl::malloc_hook(size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->malloc(size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) malloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ record_allocation_size(size, nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::calloc_hook(size_t elems, size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->calloc(elems, size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) calloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ record_allocation_size(elems * size, nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::realloc_hook(void* ptr, size_t size, void* caller_address) {
+ size_t old_size = ptr != nullptr ? real_malloc_funcs->malloc_size(ptr) : 0;
+ uint64_t old_hash = ptr_hash(ptr);
+
+ // We have to speculate the realloc does not fail, since realloc itself frees
+ // the ptr potentially and another thread might get it from malloc and tries
+ // to add to the alloc hash map before we could remove it here.
+ StatEntry* freed_entry = nullptr;
+
+ if (_track_free && (ptr != nullptr) && should_track(old_hash)) {
+ freed_entry = record_free(ptr, old_hash, old_size);
+ }
+
+ void* result = real_malloc_funcs->realloc(ptr, size);
+
+ if ((result == nullptr) && (freed_entry != nullptr) && (size > 0)) {
+ // We failed, but we already removed the freed memory, so we have to re-add it.
+ record_allocation(ptr, old_hash, freed_entry->nr_of_frames(), freed_entry->frames());
+
+ return nullptr;
+ }
+
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) realloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else if (old_size < size) {
+ // Track the additional allocate bytes. This is somewhat wrong, since
+ // we don't know the requested size of the original allocation and
+ // old_size might be greater.
+ record_allocation_size(size - old_size, nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void MallocStatisticImpl::free_hook(void* ptr, void* caller_address) {
+ if ((ptr != nullptr) && _track_free) {
+ uint64_t hash = ptr_hash(ptr);
+
+ if (should_track(hash)) {
+ record_free(ptr, hash, real_malloc_funcs->malloc_size(ptr));
+ }
+ }
+
+ real_malloc_funcs->free(ptr);
+}
+
+int MallocStatisticImpl::posix_memalign_hook(void** ptr, size_t align, size_t size, void* caller_address) {
+ int result = real_malloc_funcs->posix_memalign(ptr, align, size);
+ uint64_t hash = ptr_hash(*ptr);
+
+ if ((result == 0) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) posix_memalign_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(*ptr, hash, nr_of_frames, frames);
+ } else {
+ // Here we track the really allocated size, since it might be very different
+ // from the requested one.
+ record_allocation_size(real_malloc_funcs->malloc_size(*ptr), nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::memalign_hook(size_t align, size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->memalign(align, size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) memalign_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ // Here we track the really allocated size, since it might be very different
+ // from the requested one.
+ record_allocation_size(real_malloc_funcs->malloc_size(result), nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::aligned_alloc_hook(size_t align, size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->aligned_alloc(align, size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) aligned_alloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ // Here we track the really allocated size, since it might be very different
+ // from the requested one.
+ record_allocation_size(real_malloc_funcs->malloc_size(result), nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::valloc_hook(size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->valloc(size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) valloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ // Here we track the really allocated size, since it might be very different
+ // from the requested one.
+ record_allocation_size(real_malloc_funcs->malloc_size(result), nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+void* MallocStatisticImpl::pvalloc_hook(size_t size, void* caller_address) {
+ void* result = real_malloc_funcs->pvalloc(size);
+ uint64_t hash = ptr_hash(result);
+
+ if ((result != nullptr) && should_track(hash) && !malloc_suspended()) {
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) pvalloc_hook, (address) caller_address);
+
+ if (_track_free) {
+ record_allocation(result, hash, nr_of_frames, frames);
+ } else {
+ // Here we track the really allocated size, since it might be very different
+ // from the requested one.
+ record_allocation_size(real_malloc_funcs->malloc_size(result), nr_of_frames, frames);
+ }
+ }
+
+ return result;
+}
+
+
+void* MallocStatisticImpl::malloc_hook_rd(size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->malloc(size);
+}
+
+void* MallocStatisticImpl::calloc_hook_rd(size_t elems, size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->calloc(elems, size);
+}
+
+void* MallocStatisticImpl::realloc_hook_rd(void* ptr, size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->realloc(ptr, size);
+}
+
+void MallocStatisticImpl::free_hook_rd(void* ptr, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ real_malloc_funcs->free(ptr);
+}
+
+int MallocStatisticImpl::posix_memalign_hook_rd(void** ptr, size_t align, size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->posix_memalign(ptr, align, size);
+}
+
+void* MallocStatisticImpl::memalign_hook_rd(size_t align, size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->memalign(align, size);
+}
+
+void* MallocStatisticImpl::aligned_alloc_hook_rd(size_t align, size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->aligned_alloc(align, size);
+}
+
+void* MallocStatisticImpl::valloc_hook_rd(size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->valloc(size);
+}
+
+void* MallocStatisticImpl::pvalloc_hook_rd(size_t size, void* caller_address) {
+ wait_for_rainy_day_fund();
+
+ return real_malloc_funcs->pvalloc(size);
+}
+
+void MallocStatisticImpl::wait_for_rainy_day_fund() {
+ Locker locker(&_rainy_day_fund_lock);
+}
+
+static bool is_same_stack(StatEntry* to_check, int nr_of_frames, address* frames) {
+ for (int i = 0; i < nr_of_frames; ++i) {
+ if (to_check->frames()[i] != frames[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static uint64_t hash_for_frames(int nr_of_frames, address* frames) {
+ uint64_t result = 0;
+
+ for (int i = 0; i < nr_of_frames; ++i) {
+ uint64_t frame_addr = (uint64_t) (intptr_t) frames[i];
+ result = result * 31 + ((frame_addr & 0xfffffff0) >> 4) LP64_ONLY(+ 127 * (frame_addr >> 36));
+ }
+
+ // Avoid more bits than we can store in the entry.
+ return result & (((uint64_t) UINT64_MAX) / (MAX_FRAMES + 1));
+}
+
+StatEntry* MallocStatisticImpl::record_allocation_size(size_t to_add, int nr_of_frames, address* frames,
+ int* enable_count) {
+ // Skip the top frame since it is always from the hooks.
+ nr_of_frames = MAX2(nr_of_frames - FRAMES_TO_SKIP, 0);
+ frames += FRAMES_TO_SKIP;
+
+ assert(nr_of_frames <= _max_frames, "Overflow");
+
+ uint64_t hash = hash_for_frames(nr_of_frames, frames);
+ int idx = hash & (NR_OF_STACK_MAPS - 1);
+ assert((idx >= 0) && (idx < NR_OF_STACK_MAPS), "invalid map index");
+
+ StackMapData& map = _stack_maps_data[idx];
+ Locker locker(&map._lock);
+
+ if (enable_count != nullptr) {
+ *enable_count = _enable_count;
+ }
+
+ if (!_enabled) {
+ return nullptr;
+ }
+
+ int slot = StatEntry::scaled_hash(hash) & map._mask;
+ assert((slot >= 0) || (slot <= map._mask), "Invalid slot");
+ StatEntry* to_check = map._entries[slot];
+
+ // Check if we already know this stack.
+ while (to_check != nullptr) {
+ if ((to_check->hash() == hash) && (to_check->nr_of_frames() == nr_of_frames)) {
+ if (is_same_stack(to_check, nr_of_frames, frames)) {
+ to_check->add_allocation(to_add);
+
+ return to_check;
+ }
+ }
+
+ to_check = to_check->next();
+ }
+
+ // Need a new entry. Fail silently if we don't get the memory.
+ void* mem = map._alloc->allocate();
+
+ if (mem != nullptr) {
+ StatEntry* entry = new (mem) StatEntry(hash, to_add, nr_of_frames, frames);
+ entry->set_next(map._entries[slot]);
+ map._entries[slot] = entry;
+ map._size += 1;
+
+ if (map._size > map._limit) {
+ _stack_maps_data[idx].resize(map._mask * 2 + 1, MAX_STACK_MAP_LOAD);
+ }
+
+ return entry;
+ }
+
+ return nullptr;
+}
+
+void MallocStatisticImpl::record_allocation(void* ptr, uint64_t hash, int nr_of_frames, address* frames) {
+ // Use the size that the malloc implementation used, since we don't store
+ // the size and have to account for it later in realloc/free.
+ size_t size = real_malloc_funcs->malloc_size(ptr);
+ int enable_count;
+
+ StatEntry* stat_entry = record_allocation_size(size, nr_of_frames, frames, &enable_count);
+
+ if (stat_entry == nullptr) {
+ return;
+ }
+
+ // hash could be 0 since ptr_hash checked for _track_free without
+ // lock protection. Recalculate it again.
+ if (hash == 0) {
+ hash = ptr_hash_impl(ptr);
+ }
+
+ int idx = (int) (hash & (NR_OF_ALLOC_MAPS - 1));
+ AllocMapData& map = _alloc_maps_data[idx];
+ Locker locker(&map._lock);
+
+ // _track_free could have changed concurrently.
+ if (!(_track_free && _enabled)) {
+ return;
+ }
+
+ // We might have enable the trace again after we created the stat
+ // entry, so if that happened, we bail out.
+ if (enable_count != _enable_count) {
+ return;
+ }
+
+ int slot = AllocEntry::scaled_hash(hash) & map._mask;
+
+ // Should not already be in the table, since this is the pointer to a newly allocated
+ // piece of memory, so we remove the check in the optimized version.
+#ifdef ASSERT
+ AllocEntry* entry = map._entries[slot];
+
+ while (entry != nullptr) {
+ if (entry->hash() == hash) {
+ char tmp[1024];
+ set_malloc_suspended(true);
+ shutdown();
+
+ address frames[MAX_FRAMES + FRAMES_TO_SKIP];
+ int nr_of_frames = capture_stack(frames, (address) nullptr, (address) nullptr);
+
+ fdStream ss(1);
+ ss.print_cr("Same hash " UINT64_FORMAT " for %p and %p", (uint64_t) hash, ptr, entry->ptr());
+ ss.print_raw_cr("Current stack:");
+
+ for (int i = 0; i < nr_of_frames; ++i) {
+ ss.print(" [" PTR_FORMAT "] ", p2i(frames[i]));
+ os::print_function_and_library_name(&ss, frames[i], tmp, sizeof(tmp), true, true, false);
+ ss.cr();
+ }
+
+ ss.print_raw_cr("Original stack:");
+ StatEntry* stat_entry = entry->entry();
+
+ for (int i = 0; i < stat_entry->nr_of_frames(); ++i) {
+ address frame = stat_entry->frames()[i];
+ ss.print(" [" PTR_FORMAT "] ", p2i(frame));
+
+ if (os::print_function_and_library_name(&ss, frame, tmp, sizeof(tmp), true, true, false)) {
+ ss.cr();
+ } else {
+ if ((frame >= CodeCache::low_bound()) && (frame < CodeCache::high_bound())) {
+ ss.print_raw_cr(" ");
+ } else {
+ ss.print_raw_cr(" ");
+ }
+ }
+ }
+ }
+
+ assert((entry->hash() != hash) || (ptr == entry->ptr()), "Same hash for different pointer");
+ assert(entry->hash() != hash, "Must not be already present");
+ entry = entry->next();
+ }
+#endif
+
+ void* mem = map._alloc->allocate();
+
+ if (mem != nullptr) {
+#if defined(ASSERT)
+ AllocEntry* entry = new (mem) AllocEntry(hash, stat_entry, map._entries[slot], ptr);
+#else
+ AllocEntry* entry = new (mem) AllocEntry(hash, stat_entry, map._entries[slot]);
+#endif
+ map._entries[slot] = entry;
+ map._size += 1;
+
+ if (map._size > map._limit) {
+ _alloc_maps_data[idx].resize(map._mask * 2 + 1, MAX_ALLOC_MAP_LOAD);
+ }
+ }
+}
+
+StatEntry* MallocStatisticImpl::record_free(void* ptr, uint64_t hash, size_t size) {
+ // hash could be 0 since ptr_hash checked for _track_free without
+ // lock protection. Recalculate it again.
+ if (hash == 0) {
+ hash = ptr_hash_impl(ptr);
+ }
+
+ int idx = (int) (hash & (NR_OF_ALLOC_MAPS - 1));
+ AllocMapData& map = _alloc_maps_data[idx];
+ Locker locker(&map._lock);
+
+ // _track_free could have changed concurrently.
+ if (!(_track_free && _enabled)) {
+ return nullptr;
+ }
+
+ assert(map._entries != nullptr, "Must be initialized");
+
+ int slot = (hash / NR_OF_ALLOC_MAPS) & map._mask;
+ int enable_count = _enable_count;
+ AllocEntry** entry = &map._entries[slot];
+
+ while (*entry != nullptr) {
+ if ((*entry)->hash() == hash) {
+ StatEntry* stat_entry = (*entry)->entry();
+ assert((*entry)->ptr() == ptr, "Same hash must be same pointer");
+ AllocEntry* next = (*entry)->next();
+ map._alloc->free(*entry);
+ map._size -= 1;
+ *entry = next;
+
+ // Should not be in the table anymore.
+#ifdef ASSERT
+ AllocEntry* to_check = map._entries[slot];
+
+ while (to_check != nullptr) {
+ assert(to_check->hash() != hash, "Must not be already present");
+ to_check = to_check->next();
+ }
+#endif
+
+ // We need to lock the stat table containing the entry to avoid
+ // races when changing the size and count fields.
+ int idx2 = (int) (stat_entry->hash() & (NR_OF_STACK_MAPS - 1));
+ Locker locker2(&_stack_maps_data[idx2]._lock);
+ stat_entry->remove_allocation(size);
+
+ return stat_entry;
+ }
+
+ entry = (*entry)->next_ptr();
+ }
+
+ // We missed an allocation. This is fine, since we might have enabled the
+ // trace after the allocation itself (or it might be a bug in the progam,
+ // but we can't be sure).
+ if (_detailed_stats) {
+ AtomicAccess::add(&_failed_frees, (uint64_t) 1);
+ }
+
+ return nullptr;
+}
+
+void MallocStatisticImpl::cleanup() {
+ _enable_count += 1;
+
+ // Cleanup alloc map first, to avoid having dangling pointers
+ // to stat entries.
+ for (int i = 0; i < NR_OF_ALLOC_MAPS; ++i) {
+ _alloc_maps_data[i].cleanup();
+ }
+
+ for (int i = 0; i < NR_OF_STACK_MAPS; ++i) {
+ _stack_maps_data[i].cleanup();
+ }
+
+ _enable_count += 1;
+
+ if (real_malloc_funcs != nullptr) {
+ real_malloc_funcs->free(_rainy_day_fund);
+ _rainy_day_fund = nullptr;
+ }
+}
+
+void MallocStatisticImpl::initialize() {
+ if (_initialized) {
+ return;
+ }
+
+ _initialized = true;
+
+ if (pthread_mutex_init(&_malloc_stat_lock, nullptr) != 0) {
+ fatal("Could not initialize malloc stat lock");
+ }
+
+ pthread_mutexattr_t attr;
+
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+
+ if (pthread_mutex_init(&_rainy_day_fund_lock, &attr) != 0) {
+ fatal("Could not initialize rainy day fund lock");
+ }
+
+ if (pthread_key_create(&_malloc_suspended, nullptr) != 0) {
+ fatal("Could not initialize malloc suspend key");
+ }
+
+ for (int i = 0; i < NR_OF_STACK_MAPS; ++i) {
+ if (pthread_mutex_init(&_stack_maps_data[i]._lock, nullptr) != 0) {
+ fatal("Could not initialize stack maps lock");
+ }
+ }
+
+ for (int i = 0; i < NR_OF_ALLOC_MAPS; ++i) {
+ if (pthread_mutex_init(&_alloc_maps_data[i]._lock, nullptr) != 0) {
+ fatal("Could not initialize alloc maps lock");
+ }
+ }
+}
+
+bool MallocStatisticImpl::rainy_day_fund_used() {
+ return _rainy_day_fund_used;
+}
+
+bool MallocStatisticImpl::enable(outputStream* st, TraceSpec const& spec) {
+ Locker lock(&_malloc_stat_lock);
+
+ if (_enabled) {
+ if (spec._force) {
+ _enabled = false;
+ setup_hooks(nullptr, st);
+ cleanup();
+
+ st->print_raw_cr("Disabled already running trace first.");
+ } else {
+ st->print_raw_cr("Malloc statistic is already enabled!");
+
+ return false;
+ }
+ }
+
+ if (_shutdown) {
+ st->print_raw_cr("Malloc statistic is already shut down!");
+
+ return false;
+ }
+
+ if (spec._stack_depth < 2 || spec._stack_depth > MAX_FRAMES) {
+ st->print_cr("The given stack depth %d is outside of the valid range [%d, %d]",
+ spec._stack_depth, 2, MAX_FRAMES);
+
+ return false;
+ }
+
+ // Get the backtrace function if needed.
+ if (spec._use_backtrace && !_tried_to_load_backtrace) {
+ _tried_to_load_backtrace = true;
+
+#if defined(__APPLE__)
+ // Try libunwind first on mac.
+ _backtrace = (backtrace_func_t*) dlsym(RTLD_DEFAULT, "unw_backtrace");
+ _backtrace_name = "backtrace (libunwind)";
+
+ if (_backtrace == nullptr) {
+ _backtrace = (backtrace_func_t*) dlsym(RTLD_DEFAULT, "backtrace");
+ _backtrace_name = "backtrace";
+ }
+#else
+ _backtrace = (backtrace_func_t*) dlsym(RTLD_DEFAULT, "backtrace");
+ _backtrace_name = "backtrace";
+
+ if (_backtrace == nullptr) {
+ // Try if we have libunwind installed.
+ char ebuf[512];
+ void* libunwind = os::dll_load(MallocTraceUnwindLibName, ebuf, sizeof ebuf);
+
+ if (libunwind != nullptr) {
+ _backtrace = (backtrace_func_t*) dlsym(libunwind, "unw_backtrace");
+ _backtrace_name = "backtrace (libunwind)";
+ }
+ }
+#endif
+
+ // Clear dlerror.
+ dlerror();
+
+ if (_backtrace != nullptr) {
+ // Trigger initialization needed.
+ void* tmp[1];
+ _backtrace(tmp, 1);
+ }
+ }
+
+ _track_free = spec._track_free;
+ _detailed_stats = spec._detailed_stats;
+
+ if (_track_free) {
+ st->print_raw_cr("Tracking live memory.");
+ } else {
+ st->print_raw_cr("Tracking all allocated memory.");
+ }
+
+ if (_detailed_stats) {
+ st->print_raw_cr("Collecting detailed statistics.");
+ }
+
+ int only_nth = MIN2(1000, MAX2(1, spec._only_nth));
+
+ if (only_nth > 1) {
+ uint64_t pow = ((uint64_t) 1) << 42;
+ _to_track_limit = pow / only_nth;
+ _to_track_mask = pow - 1;
+
+ st->print_cr("Tracking about every %d allocations (%d / %d).", only_nth, (int) _to_track_mask, (int) _to_track_limit);
+ } else {
+ _to_track_mask = 0;
+ _to_track_limit = 1;
+ }
+
+ _use_backtrace = spec._use_backtrace && (_backtrace != nullptr);
+
+ // Reset statistic counters.
+ _stack_walk_time = 0;
+ _stack_walk_count = 0;
+ _tracked_ptrs = 0;
+ _not_tracked_ptrs = 0;
+ _failed_frees = 0;
+
+ if (_use_backtrace && spec._use_backtrace) {
+ st->print_raw_cr("Using backtrace() to sample stacks.");
+ } else if (spec._use_backtrace) {
+ st->print_raw_cr("Using fallback mechanism to sample stacks, since backtrace() was not available.");
+ } else {
+ st->print_raw_cr("Using fallback mechanism to sample stacks.");
+ }
+
+ _max_frames = spec._stack_depth;
+
+ if (!setup_hooks(&_malloc_stat_hooks, st)) {
+ return false;
+ }
+
+ // Never set _funcs to nullptr, even if we fail. It's just safer that way.
+ size_t entry_size = StatEntry::size(_max_frames);
+
+ if (spec._rainy_day_fund > 0) {
+ _rainy_day_fund = real_malloc_funcs->malloc(spec._rainy_day_fund);
+
+ if (_rainy_day_fund == nullptr) {
+ st->print_cr("Could not allocate rainy day fund of %d bytes", spec._rainy_day_fund);
+ cleanup();
+
+ return false;
+ }
+ }
+
+ for (int i = 0; i < NR_OF_STACK_MAPS; ++i) {
+ void* mem = real_malloc_funcs->malloc(sizeof(Allocator));
+
+ if (mem == nullptr) {
+ st->print_raw_cr("Could not allocate the allocator!");
+ cleanup();
+
+ return false;
+ }
+
+ StackMapData& map = _stack_maps_data[i];
+ Locker locker(&map._lock);
+ map._alloc = new (mem) Allocator(entry_size, 256);
+ map._mask = STACK_MAP_INIT_SIZE - 1;
+ map._size = 0;
+ map._limit = (int) ((map._mask + 1) * MAX_STACK_MAP_LOAD);
+ map._entries = (StatEntry**) real_malloc_funcs->calloc(map._mask + 1, sizeof(StatEntry*));
+
+ if (map._entries == nullptr) {
+ st->print_raw_cr("Could not allocate the stack map!");
+ cleanup();
+
+ return false;
+ }
+ }
+
+ for (int i = 0; i < NR_OF_ALLOC_MAPS; ++i) {
+ void* mem = real_malloc_funcs->malloc(sizeof(Allocator));
+
+ if (mem == nullptr) {
+ st->print_raw_cr("Could not allocate the allocator!");
+ cleanup();
+
+ return false;
+ }
+
+ AllocMapData& map = _alloc_maps_data[i];
+ Locker locker(&map._lock);
+ map._alloc = new (mem) Allocator(sizeof(AllocEntry), 2048);
+ map._mask = ALLOC_MAP_INIT_SIZE - 1;
+ map._size = 0;
+ map._limit = (int) ((map._mask + 1) * MAX_ALLOC_MAP_LOAD);
+ map._entries = (AllocEntry**) real_malloc_funcs->calloc(map._mask + 1, sizeof(AllocEntry*));
+
+ if (map._entries == nullptr) {
+ st->print_raw_cr("Could not allocate the alloc map!");
+ cleanup();
+
+ return false;
+ }
+ }
+
+ _enabled = true;
+ return true;
+}
+
+bool MallocStatisticImpl::disable(outputStream* st) {
+ Locker lock(&_malloc_stat_lock);
+
+ if (!_enabled) {
+ if (st != nullptr) {
+ st->print_raw_cr("Malloc statistic is already disabled!");
+ }
+
+ return false;
+ }
+
+ _enabled = false;
+ setup_hooks(nullptr, st);
+ cleanup();
+
+ return true;
+}
+
+
+static char const* mem_prefix[] = {"k", "M", "G", "T", nullptr};
+
+static void print_percentage(outputStream* st, double f) {
+ if (f <= 0) {
+ st->print("0.00 %%");
+ } else if (f < 0.01) {
+ st->print("< 0.01 %%");
+ } else if (f < 10) {
+ st->print("%.2f %%", f);
+ } else {
+ st->print("%.1f %%", f);
+ }
+}
+
+static void print_mem(outputStream* st, uint64_t mem, uint64_t total = 0) {
+ uint64_t k = 1024;
+ double perc = 0.0;
+ if (total > 0) {
+ perc = 100.0 * mem / total;
+ }
+
+ if ((int64_t) mem < 0) {
+ mem = -((int64_t) mem);
+ st->print("*neg* ");
+ }
+
+ if (mem < 1000) {
+ if (total > 0) {
+ st->print("%'" PRId64 " (", (uint64_t) mem);
+ print_percentage(st, perc);
+ st->print_raw(")");
+ } else {
+ st->print("%'" PRId64, (uint64_t) mem);
+ }
+ } else {
+ int idx =0;
+ uint64_t curr = mem;
+ double f = 1.0 / k;
+
+ while (mem_prefix[idx] != nullptr) {
+ if (curr < 1000 * k) {
+ if (curr < 100 * k) {
+ if (total > 0) {
+ st->print("%'" PRId64 " (%.1f %s, ", mem, f * curr, mem_prefix[idx]);
+ print_percentage(st, perc);
+ st->print_raw(")");
+ } else {
+ st->print("%'" PRId64 " (%.1f %s)", mem, f * curr, mem_prefix[idx]);
+ }
+ } else {
+ if (total > 0) {
+ st->print("%'" PRId64 " (%d %s, ", mem, (int) (curr / k), mem_prefix[idx]);
+ print_percentage(st, perc);
+ st->print_raw(")");
+ } else {
+ st->print("%'" PRId64 " (%d %s)", mem, (int) (curr / k), mem_prefix[idx]);
+ }
+ }
+
+ return;
+ }
+
+ curr /= k;
+ idx += 1;
+ }
+
+ st->print("%'" PRId64 " (%'" PRId64 "%s)", mem, curr, mem_prefix[idx - 1]);
+ }
+}
+
+static void print_count(outputStream* st, uint64_t count, uint64_t total = 0) {
+ st->print("%'" PRId64, (int64_t) count);
+
+ if (total > 0) {
+ double perc = 100.0 * count / total;
+
+ st->print_raw(" (");
+ print_percentage(st, perc);
+ st->print_raw(")");
+ }
+}
+
+static void print_frame(outputStream* st, address frame) {
+ char tmp[256];
+
+ if (os::print_function_and_library_name(st, frame, tmp, sizeof(tmp), true, true, false)) {
+ st->cr();
+ } else {
+ // We don't try to print the code blob at the given address, since the pc at the
+ // time the stack trace was taken might not be valid anymore (e.g. because of recompilation).
+ // Most of the time this might not occur, but we don't want to print wrong stack traces.
+ // So we now only indicate that the code was in the code cache.
+ if ((frame >= CodeCache::low_bound()) && (frame < CodeCache::high_bound())) {
+ st->print_raw_cr(" ");
+ } else {
+ st->print_raw_cr(" ");
+ }
+ }
+}
+
+bool MallocStatisticImpl::dump_entry(outputStream* st, StatEntryCopy* entry, int index,
+ uint64_t total_size, uint64_t total_count, int total_entries,
+ char const* filter, AddressHashSet* filter_cache) {
+ // Use a temp buffer since the output stream might use unbuffered I/O.
+ char ss_tmp[4096];
+ stringStream ss(ss_tmp, sizeof(ss_tmp));
+
+ // Check if we should print this stack.
+ if (is_non_empty_string(filter)) {
+ bool found = false;
+
+ for (int i = 0; i < entry->_entry->nr_of_frames(); ++i) {
+ address frame = entry->_entry->frames()[i];
+
+ if (filter_cache->contains(frame)) {
+ continue;
+ }
+
+ print_frame(&ss, frame);
+
+ if (strstr(ss.base(), filter) != nullptr) {
+ found = true;
+ ss.reset();
+
+ break;
+ } else {
+ filter_cache->add(frame);
+ }
+
+ ss.reset();
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ // We use int64_t here to easy see if values got negative (instead of seeing
+ // an insanely large number).
+ ss.print("Stack %d of %d: ", index, total_entries);
+ print_mem(&ss, entry->_size, total_size);
+ ss.print_raw(" bytes, ");
+ print_count(&ss, entry->_count, total_count);
+ ss.print_cr(" allocations");
+
+ for (int i = 0; i < entry->_entry->nr_of_frames(); ++i) {
+ address frame = entry->_entry->frames()[i];
+ ss.print(" [" PTR_FORMAT "] ", p2i(frame));
+ print_frame(&ss, frame);
+
+ // Flush the temp buffer if we are near the end.
+ if (sizeof(ss_tmp) - ss.size() < 512) {
+ st->write(ss_tmp, ss.size());
+ ss.reset();
+ }
+ }
+
+ if (entry->_entry->nr_of_frames() == 0) {
+ ss.print_raw_cr(" ");
+ }
+
+ st->write(ss_tmp, ss.size());
+
+ return true;
+}
+
+static void print_allocation_stats(outputStream* st, HashMapData* data,
+ int nr_of_maps, char const* type) {
+ uint64_t allocated = 0;
+ uint64_t unused = 0;
+ uint64_t total_entries = 0;
+ uint64_t total_slots = 0;
+
+ for (int i = 0; i < nr_of_maps; ++i) {
+ Locker lock(&data[i]._lock);
+ allocated += (data[i]._mask + 1) * sizeof(void*);
+ total_entries += data[i]._size;
+ total_slots += data[i]._mask + 1;
+ allocated += data[i]._alloc->allocated();
+ unused += data[i]._alloc->unused();
+ }
+
+ st->cr();
+ st->print_cr("Statistic for %s:", type);
+ st->print_raw("Allocated memory: ");
+ print_mem(st, allocated);
+ st->cr();
+ st->print_raw("Unused memory : ");
+ print_mem(st, unused);
+ st->cr();
+ st->print_cr("Average load : %.2f", total_entries / (double) total_slots);
+ st->print_cr("Nr. of entries : %'" PRId64, total_entries);
+}
+
+static int sort_by_size(const void* p1, const void* p2) {
+ StatEntryCopy* e1 = (StatEntryCopy*) p1;
+ StatEntryCopy* e2 = (StatEntryCopy*) p2;
+
+ if (e1->_size > e2->_size) {
+ return -1;
+ }
+
+ if (e1->_size < e2->_size) {
+ return 1;
+ }
+
+ // For consistent sorting.
+ if (e1->_entry < e2->_entry) {
+ return -1;
+ }
+
+ return 1;
+}
+
+static int sort_by_count(const void* p1, const void* p2) {
+ StatEntryCopy* e1 = (StatEntryCopy*) p1;
+ StatEntryCopy* e2 = (StatEntryCopy*) p2;
+
+ if (e1->_count > e2->_count) {
+ return -1;
+ }
+
+ if (e1->_count < e2->_count) {
+ return 1;
+ }
+
+ // For consistent sorting.
+ if (e1->_entry < e2->_entry) {
+ return -1;
+ }
+
+ return 1;
+}
+
+bool MallocStatisticImpl::dump(outputStream* msg_stream, outputStream* dump_stream, DumpSpec const& spec) {
+ bool used_rainy_day_fund = false;
+
+ if (spec._on_error) {
+ if (_initialized) {
+ // Make sure all other threads don't allocate memory anymore
+ if (AtomicAccess::cmpxchg(&_rainy_day_fund_used, false, true) == true) {
+ // Only can be done once.
+ return false;
+ }
+
+ used_rainy_day_fund = true;
+ } else {
+ return false;
+ }
+ }
+
+ Locker locker(&_rainy_day_fund_lock, !used_rainy_day_fund);
+
+ if (used_rainy_day_fund) {
+ setup_hooks(&_rainy_day_hooks, nullptr);
+
+ // Free rainy day fund so we have some memory to use.
+ real_malloc_funcs->free(_rainy_day_fund);
+ _rainy_day_fund = nullptr;
+ msg_stream->print_raw_cr("Emergency dump of malloc trace statistic ...");
+ }
+
+ // We need to avoid having the trace disabled concurrently.
+ Locker lock(&_malloc_stat_lock, spec._on_error);
+
+ if (!_enabled) {
+ msg_stream->print_raw_cr("Malloc statistic not enabled!");
+
+ return false;
+ }
+
+ // Hide allocations done by this thread during dumping if requested.
+ // Note that we always track frees or we might end up trying to add
+ // an allocation with a pointer which is already in the alloc maps.
+ set_malloc_suspended(spec._hide_dump_allocs);
+
+ if (_backtrace != nullptr) {
+ dump_stream->print_cr("Stacks were collected via %s.", _backtrace_name);
+ } else {
+ dump_stream->print_cr("Stacks were collected via the fallback mechanism.");
+ }
+
+ if (_track_free) {
+ dump_stream->print_raw_cr("Contains the currently allocated memory since enabling.");
+ } else {
+ dump_stream->print_raw_cr("Contains every allocation done since enabling.");
+ }
+
+ bool uses_filter = is_non_empty_string(spec._filter);
+
+ if (uses_filter) {
+ dump_stream->print_cr("Only printing stacks in which frames contain '%s'.", spec._filter);
+ }
+
+ // We make a copy of each hash map, since we don't want to lock for the whole operation.
+ StatEntryCopy* entries[NR_OF_STACK_MAPS];
+ int nr_of_entries[NR_OF_STACK_MAPS];
+
+ bool failed_alloc = false;
+ uint64_t total_count = 0;
+ uint64_t total_size = 0;
+ int total_entries = 0;
+ int total_non_empty_entries = 0;
+ int max_entries = MAX2(1, spec._dump_percentage > 0 ? INT_MAX : spec._max_entries);
+ int max_printed_entries = max_entries;
+
+ if (uses_filter) {
+ max_entries = INT_MAX;
+ }
+
+ elapsedTimer totalTime;
+ elapsedTimer lockedTime;
+
+ totalTime.start();
+
+ for (int idx = 0; idx < NR_OF_STACK_MAPS; ++idx) {
+ int pos = 0;
+ int expected_size;
+
+ {
+ StackMapData& map = _stack_maps_data[idx];
+ Locker locker(&map._lock);
+ lockedTime.start();
+ expected_size = map._size;
+
+ entries[idx] = (StatEntryCopy*) real_malloc_funcs->malloc(sizeof(StatEntryCopy) * expected_size);
+
+ if (entries[idx] != nullptr) {
+ StatEntry** orig = map._entries;
+ StatEntryCopy* copies = entries[idx];
+ int nr_of_slots = map._mask + 1;
+
+ for (int slot = 0; slot < nr_of_slots; ++slot) {
+ StatEntry* entry = orig[slot];
+
+ while (entry != nullptr) {
+ assert(pos < expected_size, "To many entries");
+
+ if (entry->count() > 0) {
+ copies[pos]._entry = entry;
+ copies[pos]._size = entry->size();
+ copies[pos]._count = entry->count();
+
+ total_size += entry->size();
+ total_count += entry->count();
+
+ pos += 1;
+ }
+
+ entry = entry->next();
+ }
+ }
+
+ lockedTime.stop();
+ assert(pos <= expected_size, "Size must be correct");
+ } else {
+ nr_of_entries[idx] = 0;
+ failed_alloc = true;
+ lockedTime.stop();
+ continue;
+ }
+ }
+
+ // See if it makes sense to trim. We have to shave of enough and don't
+ // trim anyway after sorting.
+ if ((pos < expected_size - 16) && (pos < max_entries)) {
+ void* result = real_malloc_funcs->realloc(entries[idx], pos * sizeof(StatEntryCopy));
+
+ if (result != nullptr) {
+ entries[idx] = (StatEntryCopy*) result;
+ }
+ }
+
+ nr_of_entries[idx] = pos;
+ total_entries += expected_size;
+ total_non_empty_entries += pos;
+
+ // Now sort so we might be able to trim the array to only contain the
+ // maximum possible entries.
+ if (spec._sort_by_count) {
+ qsort(entries[idx], nr_of_entries[idx], sizeof(StatEntryCopy), sort_by_count);
+ } else {
+ qsort(entries[idx], nr_of_entries[idx], sizeof(StatEntryCopy), sort_by_size);
+ }
+
+ // Free up some memory if possible.
+ if (nr_of_entries[idx] > max_entries) {
+ void* result = real_malloc_funcs->realloc(entries[idx], max_entries * sizeof(StatEntryCopy));
+
+ if (result == nullptr) {
+ // No problem, since the original memory is still there. Should not happen
+ // in reality.
+ } else {
+ entries[idx] = (StatEntryCopy*) result;
+ }
+
+ nr_of_entries[idx] = max_entries;
+ }
+ }
+
+ uint64_t size_limit = total_size;
+ uint64_t count_limit = total_count;
+
+ if (spec._dump_percentage > 0) {
+ if (spec._sort_by_count) {
+ count_limit = (uint64_t) (0.01 * total_count * spec._dump_percentage);
+ } else {
+ size_limit = (uint64_t) (0.01 * total_size * spec._dump_percentage);
+ }
+ }
+
+ AddressHashSet filter_cache(!spec._on_error);
+
+ int curr_pos[NR_OF_STACK_MAPS];
+ memset(curr_pos, 0, NR_OF_STACK_MAPS * sizeof(int));
+
+ uint64_t printed_size = 0;
+ uint64_t printed_count = 0;
+ int printed_entries = 0;
+
+ for (int i = 0; i < max_entries; ++i) {
+ int max_pos = -1;
+ StatEntryCopy* max = nullptr;
+
+ // Find the largest entry not currently printed.
+ if (spec._sort_by_count) {
+ for (int j = 0; j < NR_OF_STACK_MAPS; ++j) {
+ if (curr_pos[j] < nr_of_entries[j]) {
+ if ((max == nullptr) || (max->_count < entries[j][curr_pos[j]]._count)) {
+ max = &entries[j][curr_pos[j]];
+ max_pos = j;
+ }
+ }
+ }
+ } else {
+ for (int j = 0; j < NR_OF_STACK_MAPS; ++j) {
+ if (curr_pos[j] < nr_of_entries[j]) {
+ if ((max == nullptr) || (max->_size < entries[j][curr_pos[j]]._size)) {
+ max = &entries[j][curr_pos[j]];
+ max_pos = j;
+ }
+ }
+ }
+ }
+
+ if (max == nullptr) {
+ // Done everything we can.
+ break;
+ }
+
+ curr_pos[max_pos] += 1;
+
+ if (dump_entry(dump_stream, max, i + 1, total_size, total_count,
+ total_non_empty_entries, spec._filter, &filter_cache)) {
+ printed_size += max->_size;
+ printed_count += max->_count;
+ printed_entries += 1;
+
+ if (printed_entries >= max_printed_entries) {
+ break;
+ }
+ }
+
+ if (printed_size > size_limit) {
+ break;
+ }
+
+ if (printed_count > count_limit) {
+ break;
+ }
+ }
+
+ for (int i = 0; i < NR_OF_STACK_MAPS; ++i) {
+ real_malloc_funcs->free(entries[i]);
+ }
+
+ dump_stream->cr();
+ dump_stream->print_cr("Printed %'d stacks", printed_entries);
+
+ if (_track_free) {
+ dump_stream->print_cr("Total unique stacks: %'d (%'d including stacks with no alive allocations)",
+ total_non_empty_entries, total_entries);
+ } else {
+ dump_stream->print_cr("Total unique stacks: %'d", total_non_empty_entries);
+ }
+
+ dump_stream->print_raw("Total allocated bytes: ");
+ print_mem(dump_stream, total_size);
+ dump_stream->cr();
+ dump_stream->print_raw("Total allocation count: ");
+ print_count(dump_stream, total_count);
+ dump_stream->cr();
+ dump_stream->print_raw("Total printed bytes: ");
+ print_mem(dump_stream, printed_size, total_size);
+ dump_stream->cr();
+ dump_stream->print_raw("Total printed count: ");
+ print_count(dump_stream, printed_count, total_count);
+ dump_stream->cr();
+
+ totalTime.stop();
+
+ if (failed_alloc) {
+ dump_stream->print_cr("Failed to alloc memory during dump, so it might be incomplete!");
+ }
+
+ if (spec._internal_stats && _detailed_stats) {
+ uint64_t per_stack = _stack_walk_time / MAX2(_stack_walk_count, (uint64_t) 1);
+ msg_stream->cr();
+ msg_stream->print_cr("Sampled %'" PRId64 " stacks, took %'" PRId64 " ns per stack on average.",
+ _stack_walk_count, per_stack);
+ msg_stream->print_cr("Sampling took %.2f seconds in total", _stack_walk_time * 1e-9);
+ msg_stream->print_cr("Tracked allocations : %'" PRId64, _tracked_ptrs);
+ msg_stream->print_cr("Untracked allocations: %'" PRId64, _not_tracked_ptrs);
+ msg_stream->print_cr("Untracked frees : %'" PRId64, _failed_frees);
+
+ if ((_to_track_mask > 0) && (_tracked_ptrs > 0)) {
+ double frac = 100.0 * _tracked_ptrs / (_tracked_ptrs + _not_tracked_ptrs);
+ double rate = 100.0 / frac;
+ int target = (int) (0.5 + (_to_track_mask + 1) / (double) _to_track_limit);
+ msg_stream->print_cr("%.2f %% of the allocations were tracked, about every %.2f allocations " \
+ "(target %d)", frac, rate, target);
+ }
+ }
+
+ if (spec._internal_stats) {
+ print_allocation_stats(msg_stream, (HashMapData*) _stack_maps_data,
+ NR_OF_STACK_MAPS, "stack maps");
+
+ if (_track_free) {
+ print_allocation_stats(msg_stream, (HashMapData*) _alloc_maps_data,
+ NR_OF_ALLOC_MAPS, "alloc maps");
+ }
+
+ if (uses_filter) {
+ msg_stream->cr();
+ msg_stream->print_raw_cr("Statistic for filter cache:");
+ msg_stream->print("Allocated memory: ");
+ print_mem(dump_stream, filter_cache.allocated(), 0);
+ msg_stream->cr();
+ msg_stream->print_cr("Load factor : %.3f", filter_cache.load());
+ }
+ }
+
+ msg_stream->cr();
+ msg_stream->print_cr("Dumping done in %.3f s (%.3f s of that locked)",
+ totalTime.milliseconds() * 0.001,
+ lockedTime.milliseconds() * 0.001);
+
+ set_malloc_suspended(false);
+
+ if (used_rainy_day_fund) {
+ setup_hooks(&_malloc_stat_hooks, nullptr);
+ }
+
+ return true;
+}
+
+void MallocStatisticImpl::shutdown() {
+ _shutdown = true;
+
+ if (_initialized) {
+ _enabled = false;
+
+ if (register_hooks != nullptr) {
+ register_hooks(nullptr);
+ }
+ }
+}
+
+static void dump_from_flags(bool on_error) {
+ DumpSpec spec;
+ char const* file = MallocTraceDumpOutput;
+ spec._on_error = on_error;
+ spec._filter = MallocTraceDumpFilter;
+ spec._sort_by_count = MallocTraceDumpSortByCount;
+ spec._max_entries = MallocTraceDumpMaxEntries;
+ spec._dump_percentage = MallocTraceDumpPercentage;
+ spec._hide_dump_allocs = MallocTraceDumpHideDumpAllocs;
+ spec._internal_stats = MallocTraceDumpInternalStats;
+
+ if (is_non_empty_string(file)) {
+ if (strcmp("stdout", file) == 0) {
+ fdStream fds(1);
+ mallocStatImpl::MallocStatisticImpl::dump(&fds, &fds, spec);
+ } else if (strcmp("stderr", file) == 0) {
+ fdStream fds(2);
+ mallocStatImpl::MallocStatisticImpl::dump(&fds, &fds, spec);
+ } else {
+ char const* pid_tag = strstr(file, "@pid");
+
+ if (pid_tag != nullptr) {
+ size_t len = strlen(file);
+ size_t first = pid_tag - file;
+ char buf[32768];
+ jio_snprintf(buf, sizeof(buf), "%.*s%zu%s",
+ first, file, os::current_process_id(), pid_tag + 4);
+ fileStream fs(buf, "a");
+ mallocStatImpl::MallocStatisticImpl::dump(&fs, &fs, spec);
+ } else {
+ fileStream fs(file, "a");
+ mallocStatImpl::MallocStatisticImpl::dump(&fs, &fs, spec);
+ }
+ }
+ } else {
+ stringStream ss;
+ mallocStatImpl::MallocStatisticImpl::dump(&ss, &ss, spec);
+ }
+}
+
+class MallocTraceDumpPeriodicTask : public PeriodicTask {
+private:
+ int _left;
+
+public:
+ MallocTraceDumpPeriodicTask(uint64_t delay) :
+ PeriodicTask(MIN2((uint64_t) 2000000000, 1000 * delay)),
+ _left(MallocTraceDumpCount - 1) {
+ }
+
+ virtual void task();
+};
+
+void MallocTraceDumpPeriodicTask::task() {
+ dump_from_flags(false);
+ --_left;
+
+ if (_left == 0) {
+ disenroll();
+ }
+}
+
+class MallocTraceDumpInitialTask : public PeriodicTask {
+
+public:
+ MallocTraceDumpInitialTask(uint64_t delay) :
+ PeriodicTask(MIN2((uint64_t) 2000000000, 1000 * delay)) {
+ }
+
+ virtual void task();
+};
+
+void MallocTraceDumpInitialTask::task() {
+ dump_from_flags(false);
+
+ if (MallocTraceDumpCount > 1) {
+ uint64_t delay = MAX2((uint64_t) 1, parse_timespan(MallocTraceDumpInterval));
+
+ mallocStatImpl::MallocTraceDumpPeriodicTask* task = new mallocStatImpl::MallocTraceDumpPeriodicTask(delay);
+ task->enroll();
+ }
+
+ disenroll();
+}
+
+void enable_from_flags() {
+ TraceSpec spec;
+ stringStream ss;
+
+ spec._stack_depth = (int) MallocTraceStackDepth;
+ spec._use_backtrace = MallocTraceUseBacktrace;
+ spec._only_nth = (int) MallocTraceOnlyNth;
+ spec._track_free = MallocTraceTrackFree;
+ spec._detailed_stats = MallocTraceDetailedStats;
+
+ if (MallocTraceDumpOnError) {
+ spec._rainy_day_fund = (int) MallocTraceRainyDayFund;
+ }
+
+ if (!MallocStatistic::enable(&ss, spec) && MallocTraceExitIfFail) {
+ fprintf(stderr, "Could not enable malloc trace via -XX:+MallocTraceAtStartup: %s", ss.base());
+ os::exit(1);
+ }
+}
+
+static void enable_delayed_dump() {
+ if (MallocTraceDumpCount > 0) {
+ uint64_t delay = MAX2((uint64_t) 1, parse_timespan(MallocTraceDumpDelay));
+ mallocStatImpl::MallocTraceDumpInitialTask* task = new mallocStatImpl::MallocTraceDumpInitialTask(delay);
+ task->enroll();
+ }
+}
+
+class MallocTraceEnablePeriodicTask : public PeriodicTask {
+
+public:
+ MallocTraceEnablePeriodicTask(uint64_t delay) :
+ PeriodicTask(1000 * delay) {
+ }
+
+ virtual void task();
+};
+
+void MallocTraceEnablePeriodicTask::task() {
+ enable_from_flags();
+ enable_delayed_dump();
+ disenroll();
+}
+
+} // namespace mallocStatImpl
+
+void MallocStatistic::initialize() {
+ // Remove the hooks from the preload env, so we don't
+ // preload mallochooks for spawned programs.
+ mallocStatImpl::remove_malloc_hooks_from_env();
+
+ // We have to make sure the child process of a fork doesn't run with
+ // enabled malloc hooks before forking.
+ pthread_atfork(nullptr, nullptr, mallocStatImpl::after_child_fork);
+
+ mallocStatImpl::MallocStatisticImpl::initialize();
+
+ if (MallocTraceAtStartup) {
+#define CHECK_TIMESPAN_ARG(argument) \
+ char const* error_##argument; \
+ parse_timespan(argument, &error_##argument); \
+ if (error_##argument != nullptr) { \
+ fprintf(stderr, "Could not parse argument '%s' of -XX:" #argument ": %s\n", argument, error_##argument); \
+ os::exit(1); \
+ }
+
+ // Check interval specs now, so we don't fail later.
+ CHECK_TIMESPAN_ARG(MallocTraceEnableDelay);
+ CHECK_TIMESPAN_ARG(MallocTraceDumpDelay);
+ CHECK_TIMESPAN_ARG(MallocTraceDumpInterval);
+
+ uint64_t delay = parse_timespan(MallocTraceEnableDelay);
+
+ if (delay > 0) {
+ mallocStatImpl::MallocTraceEnablePeriodicTask* task = new mallocStatImpl::MallocTraceEnablePeriodicTask(delay);
+ task->enroll();
+ } else {
+ mallocStatImpl::enable_from_flags();
+ mallocStatImpl::enable_delayed_dump();
+ }
+ }
+}
+
+bool MallocStatistic::enable(outputStream* st, TraceSpec const& spec) {
+ return mallocStatImpl::MallocStatisticImpl::enable(st, spec);
+}
+
+bool MallocStatistic::disable(outputStream* st) {
+ return mallocStatImpl::MallocStatisticImpl::disable(st);
+}
+
+bool MallocStatistic::dump(outputStream* st, DumpSpec const& spec) {
+ const char* dump_file = spec._dump_file;
+
+ if (is_non_empty_string(dump_file)) {
+ int fd;
+
+ if (strcmp("stderr", dump_file) == 0) {
+ fd = 2;
+ } else if (strcmp("stdout", dump_file) == 0) {
+ fd = 1;
+ } else {
+ fd = ::open(dump_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+
+ if (fd < 0) {
+ st->print_cr("Could not open '%s' for output.", dump_file);
+
+ return false;
+ }
+ }
+
+ fdStream dump_stream(fd);
+ bool result = mallocStatImpl::MallocStatisticImpl::dump(st, &dump_stream, spec);
+
+ if ((fd != 1) && (fd != 2)) {
+ ::close(fd);
+ }
+
+ return fd;
+ }
+
+ return mallocStatImpl::MallocStatisticImpl::dump(st, st, spec);
+}
+
+void MallocStatistic::emergencyDump() {
+ // Check enabled at all or already done.
+ if (!MallocTraceDumpOnError || mallocStatImpl::MallocStatisticImpl::rainy_day_fund_used()) {
+ return;
+ }
+
+ mallocStatImpl::dump_from_flags(true);
+}
+
+void MallocStatistic::shutdown() {
+ mallocStatImpl::MallocStatisticImpl::shutdown();
+}
+
+MallocTraceEnableDCmd::MallocTraceEnableDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _stack_depth("-stack-depth", "The maximum stack depth to track", "INT", false, "12"),
+ _use_backtrace("-use-backtrace", "If true we try to use the backtrace() method to sample " \
+ "the stack traces.", "BOOLEAN", false, "false"),
+ _only_nth("-only-nth", "If > 1 we only track about every n'th allocation. Note that we round " \
+ "the given number to the closest power of 2.", "INT", false, "1"),
+ _force("-force", "If the trace is already enabled, we disable it first.", "BOOLEAN", false, "false"),
+ _track_free("-track-free", "If true we also track frees, so we know the live memory consumption " \
+ "and not just the total allocated amount. This costs some performance and memory.",
+ "BOOLEAN", false, "false"),
+ _detailed_stats("-detailed-stats", "Collect more detailed statistics. This will costs some " \
+ "CPU time, but no memory.", "BOOLEAN", false, "false") {
+ _dcmdparser.add_dcmd_option(&_stack_depth);
+ _dcmdparser.add_dcmd_option(&_use_backtrace);
+ _dcmdparser.add_dcmd_option(&_only_nth);
+ _dcmdparser.add_dcmd_option(&_force);
+ _dcmdparser.add_dcmd_option(&_track_free);
+ _dcmdparser.add_dcmd_option(&_detailed_stats);
+}
+
+void MallocTraceEnableDCmd::execute(DCmdSource source, TRAPS) {
+ // Need to switch to native or the long operations block GCs.
+ ThreadToNativeFromVM ttn(THREAD);
+
+ TraceSpec spec;
+ spec._stack_depth = (int) _stack_depth.value();
+ spec._use_backtrace = _use_backtrace.value();
+ spec._only_nth = (int) _only_nth.value();
+ spec._force = _force.value();
+ spec._track_free = _track_free.value();
+ spec._detailed_stats = _detailed_stats.value();
+
+ if (MallocStatistic::enable(_output, spec)) {
+ _output->print_raw_cr("Malloc statistic enabled");
+ }
+}
+
+MallocTraceDisableDCmd::MallocTraceDisableDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap) {
+}
+
+void MallocTraceDisableDCmd::execute(DCmdSource source, TRAPS) {
+ // Need to switch to native or the long operations block GCs.
+ ThreadToNativeFromVM ttn(THREAD);
+
+ if (MallocStatistic::disable(_output)) {
+ _output->print_raw_cr("Malloc statistic disabled.");
+ }
+}
+
+MallocTraceDumpDCmd::MallocTraceDumpDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _dump_file("-dump-file", "If given the dump command writes the result to the given file. " \
+ "Note that the filename is interpreted by the target VM. You can use " \
+ "'stdout' or 'stderr' as filenames to dump via stdout or stderr of " \
+ "the target VM", "STRING", false),
+ _filter("-filter", "If given we only print a stack if it includes a function which contains the " \
+ "given string as a substring.", "STRING", false),
+ _max_entries("-max-entries", "The maximum number of entries to dump.", "INT", false, "10"),
+ _dump_percentage("-percentage", "If > 0 we dump the given percentage of allocated bytes " \
+ "(or allocated objects if sorted by count). In that case the -max-entries " \
+ "option is ignored", "INT", false, "0"),
+ _sort_by_count("-sort-by-count", "If given the stacks are sorted according to the number " \
+ "of allocations. Otherwise they are orted by the number of allocated bytes.",
+ "BOOLEAN", false),
+ _internal_stats("-internal-stats", "If given some internal statistics about the overhead of " \
+ "the trace is included in the output", "BOOLEAN", false) {
+ _dcmdparser.add_dcmd_option(&_dump_file);
+ _dcmdparser.add_dcmd_option(&_filter);
+ _dcmdparser.add_dcmd_option(&_max_entries);
+ _dcmdparser.add_dcmd_option(&_dump_percentage);
+ _dcmdparser.add_dcmd_option(&_sort_by_count);
+ _dcmdparser.add_dcmd_option(&_internal_stats);
+}
+
+void MallocTraceDumpDCmd::execute(DCmdSource source, TRAPS) {
+ // Need to switch to native or the long operations block GCs.
+ ThreadToNativeFromVM ttn(THREAD);
+
+ DumpSpec spec;
+ spec._dump_file = _dump_file.value();
+ spec._filter = _filter.value();
+ spec._max_entries = _max_entries.value();
+ spec._dump_percentage = _dump_percentage.value();
+ spec._on_error = false;
+ spec._sort_by_count = _sort_by_count.value();
+ spec._internal_stats = _internal_stats.value();
+
+ MallocStatistic::dump(_output, spec);
+}
+
+} // namespace sap
+
+#endif // defined(LINUX) || defined(__APPLE__)
diff --git a/src/hotspot/os/posix/malloctrace/mallocTracePosix.hpp b/src/hotspot/os/posix/malloctrace/mallocTracePosix.hpp
new file mode 100644
index 000000000000..8dc2c96cddf9
--- /dev/null
+++ b/src/hotspot/os/posix/malloctrace/mallocTracePosix.hpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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.
+ *
+ */
+
+#ifndef OS_POSIX_MALLOCTRACE_MALLOCTRACE_HPP
+#define OS_POSIX_MALLOCTRACE_MALLOCTRACE_HPP
+
+#include "services/diagnosticCommand.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+#if defined(LINUX) || defined(__APPLE__)
+
+class outputStream;
+
+namespace sap {
+
+// The spec we use for configuring the trace
+struct TraceSpec {
+ int _stack_depth;
+ bool _use_backtrace;
+ int _only_nth;
+ bool _force;
+ bool _track_free;
+ bool _detailed_stats;
+ int _rainy_day_fund;
+
+ TraceSpec() :
+ _stack_depth(10),
+ _use_backtrace(true),
+ _only_nth(0),
+ _force(false),
+ _track_free(false),
+ _detailed_stats(false),
+ _rainy_day_fund(0) {
+ }
+};
+
+// The spec we use for configuring the dump.
+struct DumpSpec {
+ const char* _dump_file;
+ const char* _filter;
+ int _max_entries;
+ bool _hide_dump_allocs;
+ bool _on_error;
+ bool _sort_by_count;
+ int _dump_percentage;
+ bool _internal_stats;
+
+ DumpSpec() :
+ _dump_file(nullptr),
+ _filter(nullptr),
+ _max_entries(0),
+ _hide_dump_allocs(true),
+ _on_error(false),
+ _sort_by_count(false),
+ _dump_percentage(100),
+ _internal_stats(false) {
+ }
+};
+
+// Traces where allocations take place. Sums up the allocations by stack and total
+// size. It is cheaper than a full trace, since it doesn't have to record frees
+// and doesn't have to store data for each individual allocation.
+class MallocStatistic : public AllStatic {
+
+public:
+
+ // Called early to initialize the class.
+ static void initialize();
+
+ // Enables the tracing. Returns true if enabled.
+ static bool enable(outputStream* st, TraceSpec const& spec);
+
+ // Disables the tracing. Returns true if disabled.
+ static bool disable(outputStream* st);
+
+ // Dumps the statistic.
+ static bool dump(outputStream* st, DumpSpec const& spec);
+
+ // Does the emergency dump.
+ static void emergencyDump();
+
+ // Shuts down the statistic on error.
+ static void shutdown();
+};
+
+class MallocTraceEnableDCmd : public DCmdWithParser {
+ DCmdArgument _stack_depth;
+ DCmdArgument _use_backtrace;
+ DCmdArgument _only_nth;
+ DCmdArgument _force;
+ DCmdArgument _track_free;
+ DCmdArgument _detailed_stats;
+
+public:
+ static int num_arguments() {
+ return 6;
+ }
+
+ MallocTraceEnableDCmd(outputStream* output, bool heap);
+
+ static const char* name() {
+ return "MallocTrace.enable";
+ }
+
+ static const char* description() {
+ return "Enables tracing memory allocations";
+ }
+
+ static const char* impact() {
+ return "High";
+ }
+
+ virtual void execute(DCmdSource source, TRAPS);
+};
+
+class MallocTraceDisableDCmd : public DCmdWithParser {
+
+public:
+ static int num_arguments() {
+ return 0;
+ }
+
+ MallocTraceDisableDCmd(outputStream* output, bool heap);
+
+ static const char* name() {
+ return "MallocTrace.disable";
+ }
+
+ static const char* description() {
+ return "Disables tracing memory allocations";
+ }
+
+ static const char* impact() {
+ return "Low";
+ }
+
+ virtual void execute(DCmdSource source, TRAPS);
+};
+
+class MallocTraceDumpDCmd : public DCmdWithParser {
+private:
+
+ DCmdArgument _dump_file;
+ DCmdArgument _filter;
+ DCmdArgument _max_entries;
+ DCmdArgument _dump_percentage;
+ DCmdArgument _sort_by_count;
+ DCmdArgument _internal_stats;
+
+public:
+ static int num_arguments() {
+ return 6;
+ }
+
+ MallocTraceDumpDCmd(outputStream* output, bool heap);
+
+ static const char* name() {
+ return "MallocTrace.dump";
+ }
+
+ static const char* description() {
+ return "Dumps the currently running malloc trace";
+ }
+
+ static const char* impact() {
+ return "Low";
+ }
+
+ virtual void execute(DCmdSource source, TRAPS);
+};
+
+}
+
+#endif // defined(LINUX) || defined(__APPLE__)
+
+#endif // OS_POSIX_MALLOCTRACE_MALLOCTRACE_HPP
diff --git a/src/hotspot/os/windows/vitals_windows.cpp b/src/hotspot/os/windows/vitals_windows.cpp
new file mode 100644
index 000000000000..bb1e9812f161
--- /dev/null
+++ b/src/hotspot/os/windows/vitals_windows.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "runtime/os.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitals_internals.hpp"
+
+#include
+
+namespace sapmachine_vitals {
+
+static Column* g_col_system_memoryload = nullptr;
+static Column* g_col_system_avail_phys = nullptr;
+static Column* g_col_process_working_set_size = nullptr;
+static Column* g_col_process_commit_charge = nullptr;
+
+bool platform_columns_initialize() {
+ g_col_system_memoryload =
+ define_column("system", nullptr, "mload", "Approximate percentage of physical memory that is in use.", true, MAX);
+
+ // MEMORYSTATUSEX ullAvailPhys
+ g_col_system_avail_phys =
+ define_column("system", nullptr, "avail-phys", "Amount of physical memory currently available.", true, MIN);
+ // PROCESS_MEMORY_COUNTERS_EX WorkingSetSize
+ g_col_process_working_set_size =
+ define_column("system", nullptr, "wset", "Working set size", true);
+
+ // PROCESS_MEMORY_COUNTERS_EX PrivateUsage
+ g_col_process_commit_charge =
+ define_column("system", nullptr, "comch", "Commit charge", true);
+
+ return true;
+}
+
+static void set_value_in_sample(Column* col, Sample* sample, value_t val) {
+ if (col != nullptr) {
+ int index = col->index();
+ sample->set_value(index, val);
+ }
+}
+
+void sample_platform_values(Sample* sample) {
+ MEMORYSTATUSEX mse;
+ mse.dwLength = sizeof(mse);
+ if (::GlobalMemoryStatusEx(&mse)) {
+ set_value_in_sample(g_col_system_memoryload, sample, mse.dwMemoryLoad);
+ set_value_in_sample(g_col_system_avail_phys, sample, mse.ullAvailPhys);
+ }
+
+ PROCESS_MEMORY_COUNTERS cnt;
+ cnt.cb = sizeof(cnt);
+ if (::GetProcessMemoryInfo(::GetCurrentProcess(), &cnt, sizeof(cnt))) {
+ set_value_in_sample(g_col_process_working_set_size, sample, cnt.WorkingSetSize);
+ set_value_in_sample(g_col_process_commit_charge, sample, cnt.PagefileUsage);
+ }
+}
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp
index d1ea9c09d4cc..2c035bdf9443 100644
--- a/src/hotspot/share/classfile/classLoaderData.cpp
+++ b/src/hotspot/share/classfile/classLoaderData.cpp
@@ -179,6 +179,11 @@ ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool has_class_mirror_ho
NOT_PRODUCT(_dependency_count = 0); // number of class loader dependencies
JFR_ONLY(INIT_ID(this);)
+
+ // SapMachine 2023-07-04: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::inc_cld_count(has_class_mirror_holder);
+ }
}
ClassLoaderData::ChunkedHandleList::~ChunkedHandleList() {
diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.cpp b/src/hotspot/share/classfile/classLoaderDataGraph.cpp
index b6f7915db71d..8d5fa03be1af 100644
--- a/src/hotspot/share/classfile/classLoaderDataGraph.cpp
+++ b/src/hotspot/share/classfile/classLoaderDataGraph.cpp
@@ -424,6 +424,11 @@ bool ClassLoaderDataGraph::do_unloading() {
ClassUnloadingContext::context()->register_unloading_class_loader_data(data);
+ // SapMachine 2023-07-04: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::dec_cld_count(data->has_class_mirror_holder());
+ }
+
// Move dead CLD to unloading list.
if (prev != nullptr) {
prev->unlink_next();
diff --git a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp
index 767db20a8f01..9e95f93f33f8 100644
--- a/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp
+++ b/src/hotspot/share/classfile/classLoaderDataGraph.inline.hpp
@@ -32,6 +32,10 @@
#include "runtime/atomicAccess.hpp"
#include "runtime/orderAccess.hpp"
+// SapMachine 2019-09-01: Vitals
+#include "runtime/globals.hpp"
+#include "vitals/vitals.hpp"
+
inline ClassLoaderData *ClassLoaderDataGraph::find_or_create(Handle loader) {
guarantee(loader() != nullptr && oopDesc::is_oop(loader()), "Loader must be oop");
// Gets the class loader data out of the java/lang/ClassLoader object, if non-null
@@ -53,20 +57,36 @@ size_t ClassLoaderDataGraph::num_array_classes() {
void ClassLoaderDataGraph::inc_instance_classes(size_t count) {
AtomicAccess::add(&_num_instance_classes, count, memory_order_relaxed);
+ // SapMachine 2019-02-20: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::inc_classes_loaded(count);
+ }
}
void ClassLoaderDataGraph::dec_instance_classes(size_t count) {
size_t old_count = AtomicAccess::fetch_then_add(&_num_instance_classes, -count, memory_order_relaxed);
assert(old_count >= count, "Sanity");
+ // SapMachine 2019-02-20: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::inc_classes_unloaded(count);
+ }
}
void ClassLoaderDataGraph::inc_array_classes(size_t count) {
AtomicAccess::add(&_num_array_classes, count, memory_order_relaxed);
+ // SapMachine 2019-02-20: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::inc_classes_loaded(count);
+ }
}
void ClassLoaderDataGraph::dec_array_classes(size_t count) {
size_t old_count = AtomicAccess::fetch_then_add(&_num_array_classes, -count, memory_order_relaxed);
assert(old_count >= count, "Sanity");
+ // SapMachine 2019-02-20: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::counters::inc_classes_unloaded(count);
+ }
}
bool ClassLoaderDataGraph::should_clean_metaspaces_and_reset() {
diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp
index 848ff78678a2..57bf5d5a3d80 100644
--- a/src/hotspot/share/logging/logTag.hpp
+++ b/src/hotspot/share/logging/logTag.hpp
@@ -219,6 +219,7 @@ class outputStream;
LOG_TAG(valuebasedclasses) \
LOG_TAG(verification) \
LOG_TAG(verify) \
+ LOG_TAG(vitals) /* SapMachine 2022-06-01: Vitals */ \
LOG_TAG(vmatree) \
LOG_TAG(vmmutex) \
LOG_TAG(vmoperation) \
diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
index cc6e04a9ef02..dde6f834585c 100644
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -2786,6 +2786,11 @@ JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JavaThread::name_for(JNIHandles::resolve_non_null(jthread)));
// No one should hold a reference to the 'native_thread'.
native_thread->smr_delete();
+
+ // SapMachine 2021-05-21: All ...OnOutOfMemoryError switches should work for
+ // thread creation failures too.
+ report_java_out_of_memory(os::native_thread_creation_failed_msg());
+
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp
index b7f5b427edaa..a32bffaca4b4 100644
--- a/src/hotspot/share/prims/unsafe.cpp
+++ b/src/hotspot/share/prims/unsafe.cpp
@@ -474,6 +474,13 @@ UNSAFE_LEAF (void, Unsafe_WriteBackPostSync0(JNIEnv *env, jobject unsafe)) {
doWriteBackSync0(false);
} UNSAFE_END
+// SapMachine 2025-02-05: Report error for DirectMemoryOom to exit the VM
+UNSAFE_ENTRY (void, Unsafe_ReportJavaOutOfMemory0(JNIEnv *env, jobject unsafe, jstring message)) {
+ ResourceMark rm;
+ char *utf_message = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(message));
+ report_java_out_of_memory(utf_message);
+} UNSAFE_END
+
////// Random queries
// Finds the object field offset of a field with the matching name, or an error code
@@ -916,6 +923,8 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = {
{CC "shouldBeInitialized0", CC "(" CLS ")Z", FN_PTR(Unsafe_ShouldBeInitialized0)},
{CC "fullFence", CC "()V", FN_PTR(Unsafe_FullFence)},
+ // SapMachine 2025-02-05: Report error for DirectMemoryOom to exit the VM
+ {CC "reportJavaOutOfMemory0", CC "(" LANG "String;)V", FN_PTR(Unsafe_ReportJavaOutOfMemory0)},
};
#undef CC
diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
index a607484d02a8..d96d863cb29f 100644
--- a/src/hotspot/share/runtime/arguments.cpp
+++ b/src/hotspot/share/runtime/arguments.cpp
@@ -2074,6 +2074,29 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin
}
}
#endif // !INCLUDE_JVMTI
+ // SapMachine 2025-08-11: Handle JMC agent if requested.
+ } else if (match_option(option, "-jmcagent:", &tail)) {
+#if !defined(WITH_SAP_JMC_AGENT)
+ jio_fprintf(defaultStream::error_stream(),
+ "SAP JMC agent is not included in this VM\n");
+ return JNI_ERR;
+#else
+#if !INCLUDE_JVMTI
+#error "Must have JVMTI enabled with SAP JMC agent"
+#endif
+ char const* agent_jar = "agent.jar";
+ if (tail != nullptr) {
+ size_t length = strlen(tail) + strlen(_java_home->value()) + strlen(agent_jar) + 7;
+ char* options = NEW_C_HEAP_ARRAY(char, length, mtArguments);
+ jio_snprintf(options, length, "%s/lib/%s=%s", _java_home->value(), agent_jar, tail);
+ JvmtiAgentList::add("instrument", options, false);
+
+ // java agents need module java.instrument
+ if (!create_numbered_module_property("jdk.module.addmods", "java.instrument", _addmods_count++)) {
+ return JNI_ENOMEM;
+ }
+ }
+#endif // !WITH_SAP_JMC_AGENT
// --enable_preview
} else if (match_option(option, "--enable-preview")) {
set_enable_preview();
@@ -2673,6 +2696,11 @@ jint Arguments::finalize_vm_init_args() {
UNSUPPORTED_OPTION(ShowRegistersOnAssert);
#endif // CAN_SHOW_REGISTERS_ON_ASSERT
+ // SapMachine 2021-05-21: Let ExitVMOnOutOfMemory be an alias for CrashOnOutOfMemoryError
+ if (ExitVMOnOutOfMemoryError && !CrashOnOutOfMemoryError) {
+ FLAG_SET_ERGO(CrashOnOutOfMemoryError, true);
+ }
+
return JNI_OK;
}
diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp
index f90a644eaa42..47ef10167df9 100644
--- a/src/hotspot/share/runtime/globals.hpp
+++ b/src/hotspot/share/runtime/globals.hpp
@@ -448,7 +448,8 @@ const int ObjectAlignmentInBytes = 8;
product(bool, LogEvents, true, DIAGNOSTIC, \
"Enable the various ring buffer event logs") \
\
- product(int, LogEventsBufferEntries, 20, DIAGNOSTIC, \
+ /* SapMachine 2019-05-28: More events */ \
+ product(int, LogEventsBufferEntries, 75, DIAGNOSTIC, \
"Number of ring buffer event logs") \
range(1, NOT_LP64(1*K) LP64_ONLY(1*M)) \
\
@@ -515,6 +516,147 @@ const int ObjectAlignmentInBytes = 8;
develop(bool, Verbose, false, \
"Print additional debugging information from other modes") \
\
+ /* SapMachine 2019-02-20: Vitals */ \
+ product(bool, EnableVitals, true, \
+ "Enable sampling of vitals: memory, cpu utilization and various " \
+ "VM core statistics; display via jcmd \"VM.vitals\".") \
+ \
+ product(uintx, VitalsSampleInterval, 10, \
+ "Vitals sample rate interval in seconds (default 10)") \
+ range(1, 24 * 3600) \
+ \
+ product(uintx, VitalsShortTermTableHours, 1, \
+ "The size of the short term vitals table in hours") \
+ range(1, 365 * 24) \
+ \
+ product(uintx, VitalsLongTermSampleIntervalMinutes, 60, \
+ "Vitals sample rate interval in minutes for the long term table " \
+ "(default 60)") \
+ range(1, 365 * 24 * 60) \
+ \
+ product(uintx, VitalsLongTermTableDays, 14, \
+ "The size of the long term vitals table in days") \
+ range(1, 10 * 365) \
+ \
+ product(bool, VitalsLockFreeSampling, false, DIAGNOSTIC, \
+ "When sampling vitals, omit any actions which require locking.") \
+ \
+ product(bool, DumpVitalsAtExit, false, \
+ "Dump vitals at VM exit into two files, by default called " \
+ "sapmachine_vitals_.txt and sapmachine_vitals_.csv. " \
+ "Use -XX:VitalsFile option to change the file names.") \
+ \
+ product(bool, PrintVitalsAtExit, false, \
+ "Prints vitals at VM exit to tty.") \
+ \
+ product(bool, StoreVitalsExtremas, true, \
+ "If enabled we store the samples for extremum values of " \
+ "selected types.") \
+ \
+ product(ccstr, VitalsFile, nullptr, \
+ "When DumpVitalsAtExit is set, the file name prefix for the " \
+ "output files (default is sapmachine_vitals_).") \
+ \
+ /* SapMachine 2023-09-18: malloc trace */ \
+ product(bool, UseMallocHooks, false, \
+ "Preloads the malloc hooks library needed for the malloc trace. " \
+ "This flag only works when using a JDK launcher. Otherwise the " \
+ "library has to be preloaded by hand.") \
+ \
+ product(bool, MallocTraceAtStartup, false, \
+ "If set the malloc trace is enabled at startup.") \
+ \
+ product(bool, MallocTraceExitIfFail, true, \
+ "If set and enabling the malloc trace at startup fails, we " \
+ "print an error message and exit. Otherwise the error is " \
+ "silently ignored.") \
+ \
+ product(bool, MallocTraceTrackFree, true, \
+ "If set the malloc trace also tracks deallocation of memory " \
+ "if enabled at startup.") \
+ \
+ product(ccstr, MallocTraceEnableDelay, "0s", \
+ "If > 0 seconds and MallocTraceAtStartup is enabled, we delay " \
+ "the startup of tracking by the given amount of time. Can use " \
+ "s, m, h or d to specify the delay.") \
+ \
+ product(uintx, MallocTraceStackDepth, 12, \
+ "The maximum depth of stack traces for the malloc trace if " \
+ "enabled at startup.") \
+ \
+ product(uintx, MallocTraceOnlyNth, 1, \
+ "if > 1 we only track about every n'th allocation for the " \
+ "malloc trace if enabled at startup.") \
+ range(1, 1000) \
+ \
+ product(bool, MallocTraceUseBacktrace, PPC_ONLY(false) NOT_PPC(true), \
+ "If set we use the backtrace() call to sample the stacks of " \
+ "the malloc trace if enabled at startup. Note that while this " \
+ "creates better stack traces, it is also slower and not " \
+ "supported on every system. If not supported, this option is " \
+ "silently ignored.") \
+ \
+ product(ccstr, MallocTraceUnwindLibName, "libunwind.so.8", \
+ "The path of the libunwind to load if it should be used to " \
+ "create the stack traces and the backtrace() function cannot " \
+ "be found. If libunwind is not on the library path, an " \
+ "absolute path should be used.") \
+ \
+ product(bool, MallocTraceDetailedStats, false, \
+ "If enabled we collect more detailed statistics for the malloc " \
+ "trace if enabled at startup. This costs some performance.") \
+ \
+ product(bool, MallocTraceDumpOnError, false, \
+ "If enabled and the malloc trace is enabled too we do an " \
+ "emergency dump on native out-of-memory errors.") \
+ \
+ product(uintx, MallocTraceRainyDayFund, 1* M, \
+ "The size of the rainy day fund to use when doing an emergency " \
+ "dump.") \
+ \
+ product(ccstr, MallocTraceDumpFilter, "", \
+ "If set, we only print stacks which contains functions which " \
+ "match the given string.") \
+ \
+ product(bool, MallocTraceDumpInternalStats, false, \
+ "If enabled we include internal statistics in the dump. ") \
+ \
+ product(uintx, MallocTraceDumpCount, 0, \
+ "The number of dumps to perform.") \
+ \
+ product(ccstr, MallocTraceDumpDelay, "1h", \
+ "The delay after the trace was enabled at which to start the " \
+ "regular dumps. The format supports seconds, minutes, hours " \
+ "and days, e.g. '1s 2m 3h 4d' or '20s'.") \
+ \
+ product(ccstr, MallocTraceDumpInterval, "1h", \
+ "The interval for the dump for the dumps. The format supports " \
+ "seconds, minutes, hours and days, e.g. '1s 2m 3h 4d' or '20s'.") \
+ \
+ product(bool, MallocTraceDumpSortByCount, false, \
+ "If given we sort the output by allocation count instead of " \
+ "allocation size.") \
+ \
+ product(uintx, MallocTraceDumpMaxEntries, 10, \
+ "If > 0 it limits the number of entries printed. If no sort " \
+ "is specified via -XX:MallocTraceDumpSort, we sort by " \
+ "size.") \
+ \
+ product(uintx, MallocTraceDumpPercentage, 0, \
+ "If > 0 we dump the given percentage of allocated bytes " \
+ "(or allocated objects if sorted by count). In that case the " \
+ "-XX:MallocTraceDumpMaxEntries option is ignored.") \
+ \
+ product(bool, MallocTraceDumpHideDumpAllocs, true, \
+ "If enabled we don't track the allocation done for the dump.") \
+ \
+ product(ccstr, MallocTraceDumpOutput, "stderr", \
+ "If set the dump is appended to the given file. 'stderr' and " \
+ "'stdout' can be used for dumping to stderr or stdout. " \
+ "Otherwise the dump is written to the given file name ( " \
+ "the first occurrance of '@pid' is replaced by the pid of the " \
+ "process).") \
+ \
develop(bool, PrintMiscellaneous, false, \
"Print uncategorized debugging information (requires +Verbose)") \
\
@@ -568,11 +710,13 @@ const int ObjectAlignmentInBytes = 8;
"from JVM " \
"(also see HeapDumpPath, HeapDumpGzipLevel)") \
\
+ /* SapMachine 2024-05-10: HeapDumpPath for jcmd */ \
product(ccstr, HeapDumpPath, nullptr, MANAGEABLE, \
"When HeapDumpOnOutOfMemoryError, HeapDumpBeforeFullGC " \
- "or HeapDumpAfterFullGC is on, the path (filename or " \
- "directory) of the dump file (defaults to java_pid.hprof " \
- "in the working directory)") \
+ "or HeapDumpAfterFullGC is on, or a heap dump is triggered by " \
+ "jcmd GC.heap_dump without specifying a path, the path " \
+ "(filename or directory) of the dump file. " \
+ "(defaults to java_pid.hprof in the working directory)") \
\
product(int, HeapDumpGzipLevel, 0, MANAGEABLE, \
"When HeapDumpOnOutOfMemoryError, HeapDumpBeforeFullGC " \
@@ -597,7 +741,8 @@ const int ObjectAlignmentInBytes = 8;
"Repeat compilation without installing code (number of times)") \
range(0, max_jint) \
\
- product(bool, PrintExtendedThreadInfo, false, \
+ /* SapMachine 2018-08-29: Enable this per default */ \
+ product(bool, PrintExtendedThreadInfo, true, \
"Print more information in thread dump") \
\
product(intx, ScavengeRootsInCode, 2, DIAGNOSTIC, \
@@ -841,9 +986,18 @@ const int ObjectAlignmentInBytes = 8;
"JVM exits on the first occurrence of an out-of-memory error " \
"thrown from JVM") \
\
+ /* SapMachine 2021-05-21: Changed comment (we do not core on OOM) */ \
product(bool, CrashOnOutOfMemoryError, false, \
- "JVM aborts, producing an error log and core/mini dump, on the " \
- "first occurrence of an out-of-memory error thrown from JVM") \
+ "JVM aborts on the first occurrence of an out-of-memory error " \
+ "thrown from JVM. A thread stack is dumped to stdout and an " \
+ "error log produced. No core file will be produced unless " \
+ "-XX:+CreateCoredumpOnCrash is explicitly specified on the " \
+ "command line.") \
+ \
+ /* SapMachine 2021-05-21: Support ExitVMOnOutOfMemory to keep */ \
+ /* command line parity with SAPJVM */ \
+ product(bool, ExitVMOnOutOfMemoryError, false, \
+ "Alias for CrashOnOutOfMemoryError") \
\
product(intx, UserThreadWaitAttemptsAtExit, 30, \
"The number of times to wait for user threads to stop executing " \
@@ -1019,8 +1173,9 @@ const int ObjectAlignmentInBytes = 8;
"If an error occurs, save the error data to this file " \
"[default: ./hs_err_pid%p.log] (%p replaced with pid)") \
\
+ /* SapMachine 2018-12-18: Enable this per default. */ \
product(bool, ExtensiveErrorReports, \
- PRODUCT_ONLY(false) NOT_PRODUCT(true), \
+ PRODUCT_ONLY(true) NOT_PRODUCT(true), \
"Error reports are more extensive.") \
\
product(bool, DisplayVMOutputToStderr, false, \
diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp
index 776996ceb807..71248a087a97 100644
--- a/src/hotspot/share/runtime/java.cpp
+++ b/src/hotspot/share/runtime/java.cpp
@@ -102,6 +102,10 @@
#include "jfr/jfr.hpp"
#endif
+// SapMachine 2019-09-01: Vitals
+#include "runtime/globals.hpp"
+#include "vitals/vitals.hpp"
+
GrowableArray* collected_profiled_methods;
static int compare_methods(Method** a, Method** b) {
@@ -360,6 +364,14 @@ void print_statistics() {
CompilationMemoryStatistic::print_final_report(tty);
}
+ // SapMachine 2019-09-01: Vitals
+ if (DumpVitalsAtExit) {
+ sapmachine_vitals::dump_reports();
+ }
+ if (PrintVitalsAtExit) {
+ sapmachine_vitals::print_report(tty);
+ }
+
ThreadsSMRSupport::log_statistics();
if (log_is_enabled(Info, perf, class, link)) {
@@ -514,6 +526,11 @@ void before_exit(JavaThread* thread, bool halt) {
}
}
+ // SapMachine 2021-09-01: Shutdown vitals thread
+ if (EnableVitals) {
+ sapmachine_vitals::cleanup();
+ }
+
#undef BEFORE_EXIT_NOT_RUN
#undef BEFORE_EXIT_RUNNING
#undef BEFORE_EXIT_DONE
diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp
index 30bea6b81672..117cc699c009 100644
--- a/src/hotspot/share/runtime/threads.cpp
+++ b/src/hotspot/share/runtime/threads.cpp
@@ -115,6 +115,17 @@
#include "jfr/jfr.hpp"
#endif
+// SapMachine 2019-02-20: Vitals
+#include "vitals/vitals.hpp"
+#ifdef LINUX
+#include "vitals_linux_himemreport.hpp"
+#endif
+
+// SapMachine 2023-08-15: malloc trace
+#if defined(LINUX) || defined(__APPLE__)
+#include "malloctrace/mallocTracePosix.hpp"
+#endif
+
// Initialization after module runtime initialization
void universe_post_module_init(); // must happen after call_initPhase2
@@ -622,6 +633,15 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
ObjectMonitor::Initialize2();
+ // SapMachine 2023-09-20: malloc trace2
+#if defined(LINUX) || defined(__APPLE__)
+ sap::MallocStatistic::initialize();
+#else
+ if (MallocTraceAtStartup || UseMallocHooks) {
+ warning("Malloc trace is not supported on this platform");
+ }
+#endif
+
JFR_ONLY(Jfr::on_create_vm_1();)
// Should be done after the heap is fully created
@@ -830,6 +850,16 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
JFR_ONLY(Jfr::on_create_vm_3();)
+ // SapMachine 2019-02-20: Vitals
+ if (EnableVitals) {
+ sapmachine_vitals::initialize();
+ }
+#ifdef LINUX
+ if (HiMemReport) {
+ sapmachine_vitals::initialize_himem_report_facility();
+ }
+#endif // LINUX
+
#if INCLUDE_MANAGEMENT
bool start_agent = true;
#if INCLUDE_CDS
@@ -1078,6 +1108,9 @@ void Threads::add(JavaThread* p, bool force_daemon) {
// Make new thread known to active EscapeBarrier
EscapeBarrier::thread_added(p);
+
+ // SapMachine 2019-02-20: Vitals
+ sapmachine_vitals::counters::inc_threads_created(1);
}
void Threads::remove(JavaThread* p, bool is_daemon) {
@@ -1449,6 +1482,15 @@ void Threads::print_on_error(outputStream* st, Thread* current, char* buf,
print_on_error(WatcherThread::watcher_thread(), st, current, buf, buflen, &found_current);
print_on_error(AsyncLogWriter::instance(), st, current, buf, buflen, &found_current);
+ // SapMachine 2019-11-07: Vitals
+ print_on_error(const_cast(sapmachine_vitals::samplerthread()),
+ st, current, buf, buflen, &found_current);
+#ifdef LINUX
+ // SapMachine 2022-05-07: HiMemReport
+ print_on_error(const_cast(sapmachine_vitals::himem_reporter_thread()),
+ st, current, buf, buflen, &found_current);
+#endif
+
if (Universe::heap() != nullptr) {
PrintOnErrorClosure print_closure(st, current, buf, buflen, &found_current);
Universe::heap()->gc_threads_do(&print_closure);
diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp
index f2fa114133e7..a9792078b508 100644
--- a/src/hotspot/share/services/diagnosticCommand.cpp
+++ b/src/hotspot/share/services/diagnosticCommand.cpp
@@ -76,6 +76,13 @@
#include
#endif
+// SapMachine 2023-08-15: malloc trace2
+#if defined(LINUX) || defined(__APPLE__)
+#include "malloctrace/mallocTracePosix.hpp"
+#endif
+
+// SapMachine 2019-02-20: Vitals
+#include "vitals/vitalsDCmd.hpp"
static void loadAgentModule(TRAPS) {
ResourceMark rm(THREAD);
@@ -109,6 +116,8 @@ void DCmd::register_dcmds() {
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+ // SapMachine 2019-02-20: Vitals
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
#if INCLUDE_SERVICES
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(DCmd_Source_Internal | DCmd_Source_AttachAPI));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
@@ -121,6 +130,10 @@ void DCmd::register_dcmds() {
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
#if INCLUDE_JVMTI // Both JVMTI and SERVICES have to be enabled to have this dcmd
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+ // SapMachine 2025-08-11: Add support for SAP JMC agent if requested.
+#if defined(WITH_SAP_JMC_AGENT)
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+#endif // WITH_SAP_JMC_AGENT
#endif // INCLUDE_JVMTI
#endif // INCLUDE_SERVICES
#if INCLUDE_JVMTI
@@ -146,6 +159,12 @@ void DCmd::register_dcmds() {
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
#endif // LINUX or WINDOWS or MacOS
+#if defined(LINUX) || defined(__APPLE__)
+ // SapMachine 2023-08-15: malloc trace2
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
+#endif // LINUX
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export));
@@ -332,6 +351,36 @@ void JVMTIAgentLoadDCmd::execute(DCmdSource source, TRAPS) {
}
}
+// SapMachine 2025-08-11: Add support for SAP JMC agent if requested.
+#if defined(WITH_SAP_JMC_AGENT)
+JVMTIJmcAgentLoadDCmd::JVMTIJmcAgentLoadDCmd(outputStream* output, bool heap) :
+ DCmdWithParser(output, heap),
+ _option("agent option", "Option string to pass the JMC agent.", "STRING", false) {
+ _dcmdparser.add_dcmd_argument(&_option);
+}
+
+void JVMTIJmcAgentLoadDCmd::execute(DCmdSource source, TRAPS) {
+ char const* java_home = Arguments::get_java_home();
+ char const* agent_jar = "agent.jar";
+ char const* option = _option.value();
+ size_t len = strlen(java_home) + strlen(agent_jar) + (option == nullptr ? 0 : 1 + strlen(option)) + 7;
+ char* agent_line = (char*)os::malloc(len, mtInternal);
+
+ if (agent_line == nullptr) {
+ output()->print_cr("JVMTI JMC agent attach failed: "
+ "Could not allocate " SIZE_FORMAT_X_0 " bytes for argument.",
+ len);
+ return;
+ }
+
+ jio_snprintf(agent_line, len, "%s/lib/%s%s%s", java_home, agent_jar, option == nullptr ? "" : "=",
+ option == nullptr ? "" : option);
+ JvmtiAgentList::load_agent("instrument", false, agent_line, output());
+
+ os::free(agent_line);
+}
+#endif // WITH_SAP_JMC_AGENT
+
#endif // INCLUDE_JVMTI
#endif // INCLUDE_SERVICES
@@ -467,7 +516,8 @@ void FinalizerInfoDCmd::execute(DCmdSource source, TRAPS) {
#if INCLUDE_SERVICES // Heap dumping/inspection supported
HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
- _filename("filename","Name of the dump file", "FILE",true),
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ _filename("filename", "Name of the dump file", "FILE", false, "if no filename was specified, but -XX:HeapDumpPath= is set, path is taken"),
_all("-all", "Dump all objects, including unreachable objects",
"BOOLEAN", false, "false"),
_gzip("-gz", "If specified, the heap dump is written in gzipped format "
@@ -488,6 +538,8 @@ HeapDumpDCmd::HeapDumpDCmd(outputStream* output, bool heap) :
void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
jlong level = -1; // -1 means no compression.
jlong parallel = HeapDumper::default_num_of_dump_threads();
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ bool use_heapdump_path = false;
if (_gzip.is_set()) {
level = _gzip.value();
@@ -510,11 +562,27 @@ void HeapDumpDCmd::execute(DCmdSource source, TRAPS) {
}
}
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ if (!_filename.is_set()) {
+ if (HeapDumpPath != nullptr) {
+ // use HeapDumpPath (file or directory is possible)
+ use_heapdump_path = true;
+ } else {
+ output()->print_cr("Filename or -XX:HeapDumpPath must be set!");
+ return;
+ }
+ }
+
// Request a full GC before heap dump if _all is false
// This helps reduces the amount of unreachable objects in the dump
// and makes it easier to browse.
- HeapDumper dumper(!_all.value() /* request GC if _all is false*/);
- dumper.dump(_filename.value(), output(), (int) level, _overwrite.value(), (uint)parallel);
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ if (use_heapdump_path) {
+ HeapDumper::dump_heap(!_all.value(), output(), (int)level, _overwrite.value(), (uint)parallel);
+ } else {
+ HeapDumper dumper(!_all.value() /* request GC if _all is false*/);
+ dumper.dump(_filename.value(), output(), (int)level, _overwrite.value(), (uint)parallel);
+ }
}
ClassHistogramDCmd::ClassHistogramDCmd(outputStream* output, bool heap) :
diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp
index 97ceb19d0ad2..76c45f4d438d 100644
--- a/src/hotspot/share/services/diagnosticCommand.hpp
+++ b/src/hotspot/share/services/diagnosticCommand.hpp
@@ -167,6 +167,26 @@ class JVMTIAgentLoadDCmd : public DCmdWithParser {
static const char* impact() { return "Low"; }
virtual void execute(DCmdSource source, TRAPS);
};
+
+// SapMachine 2025-08-11: Add support for SAP JMC agent if requested.
+#if defined(WITH_SAP_JMC_AGENT)
+class JVMTIJmcAgentLoadDCmd : public DCmdWithParser {
+protected:
+ DCmdArgument _option;
+public:
+ JVMTIJmcAgentLoadDCmd(outputStream* output, bool heap);
+ static int num_arguments() { return 1; }
+ static const char* name() { return "JVMTI.jmc_agent_load"; }
+ static const char* description() {
+ return "Load the SAP JMC agent.";
+ }
+ static const char* impact() {
+ return "Medium: Depends on the command or trace";
+ }
+ virtual void execute(DCmdSource source, TRAPS);
+};
+#endif // WITH_SAP_JMC_AGENT
+
#endif // INCLUDE_JVMTI
#endif // INCLUDE_SERVICES
diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp
index c850a3a711fc..ca25e7387adf 100644
--- a/src/hotspot/share/services/heapDumper.cpp
+++ b/src/hotspot/share/services/heapDumper.cpp
@@ -2733,7 +2733,8 @@ void HeapDumper::set_error(char const* error) {
// Called by out-of-memory error reporting by a single Java thread
// outside of a JVM safepoint
void HeapDumper::dump_heap_from_oome() {
- HeapDumper::dump_heap(true);
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ HeapDumper::dump_heap(false, true);
}
// Called by error reporting by a single Java thread outside of a JVM safepoint,
@@ -2742,15 +2743,24 @@ void HeapDumper::dump_heap_from_oome() {
// general use, however, this method will need modification to prevent
// inteference when updating the static variables base_path and dump_file_seq below.
void HeapDumper::dump_heap() {
- HeapDumper::dump_heap(false);
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ HeapDumper::dump_heap(false, false);
}
-void HeapDumper::dump_heap(bool oome) {
+// SapMachine 2024-05-10: HeapDumpPath for jcmd
+void HeapDumper::dump_heap(bool gc_before_heap_dump, outputStream* out, int compression, bool overwrite, uint parallel_thread_num) {
+ HeapDumper::dump_heap(gc_before_heap_dump, false, out, compression, overwrite, parallel_thread_num);
+}
+
+// SapMachine 2024-05-10: HeapDumpPath for jcmd
+void HeapDumper::dump_heap(bool gc_before_heap_dump, bool oome, outputStream* out, int compression, bool overwrite, uint parallel_thread_num) {
static char base_path[JVM_MAXPATHLEN] = {'\0'};
static uint dump_file_seq = 0;
char my_path[JVM_MAXPATHLEN];
const int max_digit_chars = 20;
- const char* dump_file_name = HeapDumpGzipLevel > 0 ? "java_pid%p.hprof.gz" : "java_pid%p.hprof";
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ const int ziplevel = compression < 0 ? HeapDumpGzipLevel : compression;
+ const char* dump_file_name = ziplevel > 0 ? "java_pid%p.hprof.gz" : "java_pid%p.hprof";
// The dump file defaults to java_pid.hprof in the current working
// directory. HeapDumpPath= can be used to specify an alternative
@@ -2791,7 +2801,9 @@ void HeapDumper::dump_heap(bool oome) {
}
dump_file_seq++; // increment seq number for next time we dump
- HeapDumper dumper(false /* no GC before heap dump */,
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ HeapDumper dumper(gc_before_heap_dump /* GC before heap dump */,
oome /* pass along out-of-memory-error flag */);
- dumper.dump(my_path, tty, HeapDumpGzipLevel);
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ dumper.dump(my_path, out, ziplevel, overwrite, parallel_thread_num);
}
diff --git a/src/hotspot/share/services/heapDumper.hpp b/src/hotspot/share/services/heapDumper.hpp
index a69cf72aaf0d..fc14e8584072 100644
--- a/src/hotspot/share/services/heapDumper.hpp
+++ b/src/hotspot/share/services/heapDumper.hpp
@@ -49,7 +49,8 @@ class HeapDumper : public StackObj {
// internal timer.
elapsedTimer* timer() { return &_t; }
- static void dump_heap(bool oome);
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ static void dump_heap(bool gc_before_heap_dump, bool oome, outputStream* out = tty, int compression = -1, bool overwrite = false, uint parallel_thread_num = default_num_of_dump_threads());
public:
HeapDumper(bool gc_before_heap_dump) :
@@ -70,6 +71,9 @@ class HeapDumper : public StackObj {
static void dump_heap_from_oome() NOT_SERVICES_RETURN;
+ // SapMachine 2024-05-10: HeapDumpPath for jcmd
+ static void dump_heap(bool gc_before_heap_dump, outputStream* out, int compression, bool overwrite, uint parallel_thread_num) NOT_SERVICES_RETURN;
+
// Parallel thread number for heap dump, initialize based on active processor count.
static uint default_num_of_dump_threads() {
return MAX2(1, (uint)os::initial_active_processor_count() * 3 / 8);
diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp
index 23e8281f000e..2f869bad1672 100644
--- a/src/hotspot/share/utilities/debug.cpp
+++ b/src/hotspot/share/utilities/debug.cpp
@@ -43,6 +43,9 @@
#include "runtime/atomic.hpp"
#include "runtime/flags/flagSetting.hpp"
#include "runtime/frame.inline.hpp"
+// SapMachine 2021-05-21
+#include "runtime/globals.hpp"
+#include "runtime/globals_extension.hpp"
#include "runtime/handles.inline.hpp"
#include "runtime/java.hpp"
#include "runtime/javaThread.hpp"
@@ -67,6 +70,11 @@
#include "jfr/jfr.hpp"
#endif
+// SapMachine 2023-08-15: malloc trace
+#if defined(LINUX) || defined(__APPLE__)
+#include "malloctrace/mallocTracePosix.hpp"
+#endif
+
#include
#include
@@ -233,6 +241,13 @@ void report_fatal(VMErrorType error_type, const char* file, int line, const char
void report_vm_out_of_memory(const char* file, int line, size_t size,
VMErrorType vm_err_type, const char* detail_fmt, ...) {
+ // SapMachine 2023-11-03: Check if we should to an emergency dump for the malloc trace.
+#if defined(LINUX) || defined(__APPLE__)
+ if ((vm_err_type == OOM_MALLOC_ERROR) || (vm_err_type == OOM_MMAP_ERROR)) {
+ sap::MallocStatistic::emergencyDump();
+ }
+#endif
+
va_list detail_args;
va_start(detail_args, detail_fmt);
@@ -274,6 +289,22 @@ void report_java_out_of_memory(const char* message) {
// commands multiple times we just do it once when the first thread that reports
// the error.
if (out_of_memory_reported.compare_set(false, true)) {
+
+ // SapMachine 2021-05-21: If any one of the xxxOnOutOfMemoryError is specified,
+ // print stack to stdout. Do this before any subsequent handling - this is the
+ // most important information.
+ if ((HeapDumpOnOutOfMemoryError) ||
+ (OnOutOfMemoryError && OnOutOfMemoryError[0]) ||
+ CrashOnOutOfMemoryError || ExitOnOutOfMemoryError) {
+ VMError::print_stack(tty);
+ }
+
+ // SapMachine 2021-05-21: If we crash due to CrashOnOutOfMemoryError, deactivate
+ // cores unless they had been explicitly enabled.
+ if (CrashOnOutOfMemoryError && FLAG_IS_DEFAULT(CreateCoredumpOnCrash)) {
+ FLAG_SET_ERGO(CreateCoredumpOnCrash, false);
+ }
+
// create heap dump before OnOutOfMemoryError commands are executed
if (HeapDumpOnOutOfMemoryError) {
tty->print_cr("java.lang.OutOfMemoryError: %s", message);
diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp
index 045fcc23d631..9d32f76e3f19 100644
--- a/src/hotspot/share/utilities/vmError.cpp
+++ b/src/hotspot/share/utilities/vmError.cpp
@@ -70,9 +70,22 @@
#include "utilities/nativeStackPrinter.hpp"
#include "utilities/ostream.hpp"
#include "utilities/vmError.hpp"
+// SapMachine 2019-02-20: Vitals
+#include "vitals/vitals.hpp"
#if INCLUDE_JFR
#include "jfr/jfr.hpp"
#endif
+#if INCLUDE_JVMCI
+#include "jvmci/jvmci.hpp"
+#endif
+#ifdef LINUX
+// SapMachine 2019-02-20: Vitals
+#include "vitals_linux_himemreport.hpp"
+#endif
+// SapMachine 2023-08-15: malloc trace
+#if defined(LINUX) || defined(__APPLE__)
+#include "malloctrace/mallocTracePosix.hpp"
+#endif
#ifndef PRODUCT
#include
@@ -1267,12 +1280,15 @@ void VMError::report(outputStream* st, bool _verbose) {
Arguments::print_on(st);
st->cr();
+ // SapMachine 2021-09-07:
+ // - print all values, not only non-default
+ // - comments are unnecessary bloat
STEP_IF("printing flags", _verbose)
JVMFlag::printFlags(
st,
- true, // with comments
+ false, // with comments
false, // no ranges
- true); // skip defaults
+ false); // skip defaults
st->cr();
STEP_IF("printing warning if internal testing API used", WhiteBox::used())
@@ -1312,6 +1328,23 @@ void VMError::report(outputStream* st, bool _verbose) {
NativeHeapTrimmer::print_state(st);
st->cr();
+ // SapMachine 2019-02-20: Vitals
+ STEP("Vitals")
+ if (_verbose) {
+ sapmachine_vitals::print_info_t info;
+ sapmachine_vitals::default_settings(&info);
+ info.sample_now = true;
+ st->print_cr("Vitals:");
+ sapmachine_vitals::print_report(st, &info);
+ }
+
+#ifdef LINUX
+ STEP("Vitals HiMemReport")
+ st->cr();
+ sapmachine_vitals::print_himemreport_state(st);
+ st->cr();
+#endif // LINUX
+
STEP_IF("printing system", _verbose)
st->print_cr("--------------- S Y S T E M ---------------");
st->cr();
@@ -1537,6 +1570,21 @@ void VMError::print_vm_info(outputStream* st) {
st->cr();
+ // SapMachine 2019-02-20: Vitals
+ // STEP("Vitals")
+ sapmachine_vitals::print_info_t info;
+ sapmachine_vitals::default_settings(&info);
+ info.sample_now = true;
+ st->print_cr("Vitals:");
+ sapmachine_vitals::print_report(st, &info);
+
+#ifdef LINUX
+ // STEP("Vitals HiMemReport")
+ st->cr();
+ sapmachine_vitals::print_himemreport_state(st);
+ st->cr();
+#endif // LINUX
+
// STEP("printing system")
st->print_cr("--------------- S Y S T E M ---------------");
st->cr();
@@ -1667,6 +1715,11 @@ void VMError::report_and_die(int id, const char* message, const char* detail_fmt
Thread* thread, address pc, const void* siginfo, const void* context, const char* filename,
int lineno, size_t size)
{
+#if defined(LINUX) || defined(__APPLE__)
+ // SapMachine 2023-09-18: Make sure we don't track allocations anymore.
+ sap::MallocStatistic::shutdown();
+#endif
+
// A single scratch buffer to be used from here on.
// Do not rely on it being preserved across function calls.
static char buffer[O_BUFLEN];
@@ -2216,3 +2269,13 @@ VMErrorCallbackMark::~VMErrorCallbackMark() {
assert(_thread->_vm_error_callbacks != nullptr, "Popped too far");
_thread->_vm_error_callbacks = _thread->_vm_error_callbacks->_next;
}
+
+// SapMachine 2021-05-21: A wrapper for VMError::print_stack_trace(..), public, for printing stacks
+// to tty on CrashOnOutOfMemoryError
+void VMError::print_stack(outputStream* st) {
+ Thread* t = Thread::current_or_null_safe();
+ char buf[1024];
+ if (t != nullptr && t->is_Java_thread()) {
+ VMError::print_stack_trace(st, (JavaThread*) t, buf, sizeof(buf), false);
+ }
+}
diff --git a/src/hotspot/share/utilities/vmError.hpp b/src/hotspot/share/utilities/vmError.hpp
index b46ba2087884..1050f774f9c4 100644
--- a/src/hotspot/share/utilities/vmError.hpp
+++ b/src/hotspot/share/utilities/vmError.hpp
@@ -228,6 +228,9 @@ class VMError : public AllStatic {
static void set_safepoint_timed_out_thread(Thread* thread);
static Thread* get_handshake_timed_out_thread();
static Thread* get_safepoint_timed_out_thread();
+
+ // SapMachine 2021-05-21: A wrapper for VMError::print_stack_trace(..), public, for printing stacks to tty on CrashOnOutOfMemoryError
+ static void print_stack(outputStream* st);
};
class VMErrorCallback {
diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp
new file mode 100644
index 000000000000..b326b5338eca
--- /dev/null
+++ b/src/hotspot/share/vitals/vitals.cpp
@@ -0,0 +1,1452 @@
+/*
+ * Copyright (c) 2019, 2026 SAP SE. 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.
+ *
+ */
+
+#include "classfile/classLoaderDataGraph.inline.hpp"
+#include "code/codeCache.hpp"
+#include "gc/shared/collectedHeap.hpp"
+#include "jvm_io.h"
+#include "logging/log.hpp"
+#include "memory/allocation.hpp"
+#include "memory/metaspace.hpp"
+#include "memory/metaspaceUtils.hpp"
+#include "memory/universe.hpp"
+#include "nmt/mallocTracker.hpp"
+#include "nmt/memBaseline.hpp"
+#include "nmt/memTracker.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "runtime/nonJavaThread.hpp"
+#include "runtime/os.hpp"
+#include "runtime/thread.hpp"
+#include "runtime/threads.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/macros.hpp"
+#include "utilities/ostream.hpp"
+#include "vitals/vitals.hpp"
+#include "vitals/vitals_internals.hpp"
+#include "vitals/vitalsLocker.hpp"
+
+#include
+#include
+
+// Newer JDKS: NMT is always on and this macro does not exist
+// Older JDKs: NMT can be off at compile time; in that case INCLUDE_NMT
+// will be defined=0 via CFLAGS; or on, in that case it will be defined=1 in macros.hpp.
+#ifndef INCLUDE_NMT
+#define INCLUDE_NMT 1
+#endif
+
+namespace sapmachine_vitals {
+
+static Lock g_vitals_lock("VitalsLock");
+
+namespace counters {
+
+static volatile size_t g_number_of_clds = 0;
+static volatile size_t g_number_of_anon_clds = 0;
+static volatile size_t g_classes_loaded = 0;
+static volatile size_t g_classes_unloaded = 0;
+static volatile size_t g_threads_created = 0;
+
+void inc_cld_count(bool is_anon_cld) {
+ AtomicAccess::inc(&g_number_of_clds);
+ if (is_anon_cld) {
+ AtomicAccess::inc(&g_number_of_anon_clds);
+ }
+}
+
+void dec_cld_count(bool is_anon_cld) {
+ AtomicAccess::dec(&g_number_of_clds);
+ if (is_anon_cld) {
+ AtomicAccess::dec(&g_number_of_anon_clds);
+ }
+}
+
+void inc_classes_loaded(size_t count) {
+ AtomicAccess::add(&g_classes_loaded, count);
+}
+
+void inc_classes_unloaded(size_t count) {
+ AtomicAccess::add(&g_classes_unloaded, count);
+}
+
+void inc_threads_created(size_t count) {
+ AtomicAccess::add(&g_threads_created, count);
+}
+
+} // namespace counters
+
+// helper function for the missing outputStream::put(int c, int repeat)
+static void ostream_put_n(outputStream* st, int c, int repeat) {
+ for (int i = 0; i < repeat; i ++) {
+ st->put(c);
+ }
+}
+
+/////// class Sample /////
+
+int Sample::num_values() { return ColumnList::the_list()->num_columns(); }
+
+size_t Sample::size_in_bytes() {
+ assert(num_values() > 0, "not yet initialized");
+ return sizeof(Sample) + sizeof(value_t) * (num_values() - 1); // -1 since Sample::values is size 1 to shut up compilers about zero length arrays
+}
+
+// Note: this is not to be used for regular samples, which live in a preallocated table.
+Sample* Sample::allocate() {
+ Sample* s = (Sample*) NEW_C_HEAP_ARRAY(char, size_in_bytes(), mtInternal);
+ s->reset();
+ return s;
+}
+
+void Sample::reset() {
+ for (int i = 0; i < num_values(); i ++) {
+ set_value(i, INVALID_VALUE);
+ }
+ DEBUG_ONLY(_num = -1;)
+ _timestamp = 0;
+}
+
+void Sample::set_value(int index, value_t v) {
+ assert(index >= 0 && index < num_values(), "invalid index");
+ _values[index] = v;
+}
+
+void Sample::set_timestamp(time_t t) {
+ _timestamp = t;
+}
+
+#ifdef ASSERT
+void Sample::set_num(int n) {
+ _num = n;
+}
+#endif
+
+value_t Sample::value(int index) const {
+ assert(index >= 0 && index < num_values(), "invalid index");
+ return _values[index];
+}
+
+static void print_text_with_dashes(outputStream* st, const char* text, int width) {
+ assert(width > 0, "Sanity");
+ // Print the name centered within the width like this
+ // ----- system ------
+ int extra_space = width - (int)strlen(text);
+ if (extra_space > 0) {
+ int left_space = extra_space / 2;
+ int right_space = extra_space - left_space;
+ ostream_put_n(st, '-', left_space);
+ st->print_raw(text);
+ ostream_put_n(st, '-', right_space);
+ } else {
+ ostream_put_n(st, '-', width);
+ }
+}
+
+// Helper function for printing:
+// Print to ostream, but only if ostream is given. In any case return number of
+// characters printed (or which would have been printed).
+static
+ATTRIBUTE_PRINTF(2, 3)
+int printf_helper(outputStream* st, const char *fmt, ...) {
+ // We only print numbers, so a small buffer is fine.
+ char buf[128];
+ va_list args;
+ int len = 0;
+ va_start(args, fmt);
+ len = jio_vsnprintf(buf, sizeof(buf), fmt, args);
+ va_end(args);
+ // jio_vsnprintf guarantees -1 on truncation, and always zero termination if buffersize > 0.
+ assert(len >= 0, "Error, possible truncation. Increase bufsize?");
+ if (len < 0) { // Handle in release too: just print a clear marker
+ jio_snprintf(buf, sizeof(buf), "!ERR!");
+ len = (int)::strlen(buf);
+ }
+ if (st != nullptr) {
+ st->print_raw(buf);
+ }
+ return len;
+}
+
+// length of time stamp
+#define TIMESTAMP_LEN 19
+// number of spaces after time stamp
+#define TIMESTAMP_DIVIDER_LEN 3
+static void print_timestamp(outputStream* st, time_t t) {
+ struct tm _tm;
+ if (os::localtime_pd(&t, &_tm) == &_tm) {
+ char buf[32];
+ ::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &_tm);
+ st->print("%*s", TIMESTAMP_LEN, buf);
+ }
+}
+
+
+////// ColumnWidths : a helper class for pre-calculating column widths to make a table align nicely.
+// Keeps an array of ints, dynamically sized (since each platform has a different number of columns),
+// and offers methods of auto-sizeing them to fit given samples (via dry-printing).
+class ColumnWidths {
+ int _widths[64]; // Don't allocate dynamically, since we might not have enough memory when we use it.
+
+public:
+
+ ColumnWidths() {
+ // Assert including the non-active columns, so we spot possible problems earlier.
+ assert(sizeof(_widths) / sizeof(_widths[0]) >= (size_t) Legend::the_legend()->nr_of_columns(), "array too small");
+
+ // Allocate array; initialize with the minimum required column widths (which is the
+ // size required to print the column header fully)
+ const Column* c = ColumnList::the_list()->first();
+ while (c != nullptr) {
+ _widths[c->index()] = (int)::strlen(c->name());
+ c = c->next();
+ }
+ }
+
+ // given a sample (and an optional preceding sample for delta values),
+ // update widths to accommodate sample values (uses dry-printing)
+ void update_from_sample(const Sample* sample, const Sample* last_sample, const print_info_t* pi, int add_width = 0) {
+ const Column* c = ColumnList::the_list()->first();
+ while (c != nullptr) {
+ const int idx = c->index();
+ const value_t v = sample->value(idx);
+ value_t v2 = INVALID_VALUE;
+ int age = -1;
+ if (last_sample != nullptr) {
+ v2 = last_sample->value(idx);
+ age = sample->timestamp() - last_sample->timestamp();
+ }
+ int needed = c->calc_print_size(v, v2, age, pi) + add_width;
+ if (_widths[idx] < needed) {
+ _widths[idx] = needed;
+ }
+ c = c->next();
+ }
+ }
+
+ int at(int index) const {
+ return _widths[index];
+ }
+};
+
+
+////// Legend ///////////////////////////////////////////////
+
+Legend* Legend::_the_legend = nullptr;
+
+bool Legend::initialize() {
+ _the_legend = new Legend();
+ return _the_legend != nullptr;
+}
+
+stringStream _legend;
+stringStream _footnote;
+static Legend* _the_legend;
+
+Legend::Legend() : _last_added_cat(nullptr), _nr_of_columns(0) {}
+
+void Legend::add_column_info(const char* const category, const char* const header,
+ const char* const name, const char* const description) {
+ // Print category label if this column opens a new category
+ if ((_last_added_cat == nullptr) || (::strcmp(_last_added_cat, category) != 0)) {
+ print_text_with_dashes(&_legend, category, 30);
+ _legend.cr();
+ }
+ _last_added_cat = category;
+ _nr_of_columns++;
+ // print column name and description
+ const int min_width_column_label = 16;
+ char buf[32];
+ if (header != nullptr) {
+ jio_snprintf(buf, sizeof(buf), "%s-%s", header, name);
+ } else {
+ jio_snprintf(buf, sizeof(buf), "%s", name);
+ }
+ _legend.print_cr("%*s: %s", min_width_column_label, buf, description);
+}
+
+void Legend::add_footnote(const char* text) {
+ _footnote.print_cr("%s", text);
+}
+
+void Legend::print_on(outputStream* st) const {
+ st->print_raw(_legend.base());
+ st->cr();
+ st->print_raw(_footnote.base());
+ st->print_cr("(Vitals version %X, pid: %d)", vitals_version, os::current_process_id());
+}
+
+////// ColumnList: a singleton class holding all information about all columns
+
+ColumnList* ColumnList::_the_list = nullptr;
+
+bool ColumnList::initialize() {
+ _the_list = new ColumnList();
+ return _the_list != nullptr;
+}
+
+void ColumnList::add_column(Column* c) {
+ assert(c->index() == -1, "Do not add twice.");
+ Column* c_last = _last;
+ if (_last != nullptr) {
+ _last->_next = c;
+ _last = c;
+ } else {
+ _first = _last = c;
+ }
+ // fix indices (describe position of column within table/category/header
+ c->_idx = c->_idx_cat = c->_idx_hdr = 0;
+ if (c_last != nullptr) {
+ c->_idx = c_last->_idx + 1;
+ if (::strcmp(c->category(), c_last->category()) == 0) { // same category as last column?
+ c->_idx_cat = c_last->_idx_cat + 1;
+ }
+ if (c->header() != nullptr && c_last->header() != nullptr &&
+ ::strcmp(c_last->header(), c->header()) == 0) { // have header and same as last column?
+ c->_idx_hdr = c_last->_idx_hdr + 1;
+ }
+ }
+ _num_columns ++;
+}
+
+////////////////////
+
+static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {
+
+ assert(pi->csv == false, "Not in csv mode");
+ ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+
+ const Column* c = ColumnList::the_list()->first();
+ assert(c != nullptr, "no columns?");
+ const char* last_category_text = nullptr;
+ int width = 0;
+
+ while(c != nullptr) {
+ if (c->index_within_category_section() == 0) {
+ if (width > 0) {
+ // Print category label centered over the last n columns, surrounded by dashes.
+ print_text_with_dashes(st, last_category_text, width - 1);
+ st->put(' ');
+ }
+ width = 0;
+ }
+ width += widths->at(c->index());
+ width += 1; // divider between columns
+ last_category_text = c->category();
+ c = c->next();
+ }
+ print_text_with_dashes(st, last_category_text, width - 1);
+ st->cr();
+}
+
+static void print_header_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {
+
+ assert(pi->csv == false, "Not in csv mode");
+ ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+
+ const Column* c = ColumnList::the_list()->first();
+ assert(c != nullptr, "no columns?");
+ const char* last_header_text = nullptr;
+ int width = 0;
+
+ while(c != nullptr) {
+ if (c->index_within_header_section() == 0) { // First in header section
+ if (width > 0) {
+ if (last_header_text != nullptr) {
+ // Print header label centered over the last n columns, surrounded by dashes.
+ print_text_with_dashes(st, last_header_text, width - 1);
+ st->put(' '); // divider
+ } else {
+ // the last n columns had no header. Just fill with blanks.
+ ostream_put_n(st, ' ', width);
+ }
+ }
+ width = 0;
+ }
+ width += widths->at(c->index());
+ width += 1; // divider between columns
+ last_header_text = c->header();
+ c = c->next();
+ }
+ if (width > 0 && last_header_text != nullptr) {
+ print_text_with_dashes(st, last_header_text, width - 1);
+ }
+ st->cr();
+}
+
+static void print_column_names(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {
+
+ // Leave space for timestamp column
+ if (pi->csv == false) {
+ ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
+ } else {
+ st->print_raw("time,");
+ }
+
+ const Column* c = ColumnList::the_list()->first();
+ const Column* previous = nullptr;
+ while (c != nullptr) {
+ if (pi->csv == false) {
+ st->print("%-*s ", widths->at(c->index()), c->name());
+ } else { // csv mode
+ // csv: use comma as delimiter, don't pad, and precede name with category/header
+ // (limited to 4 chars).
+ if (c->category() != nullptr) {
+ st->print("%.4s-", c->category());
+ }
+ if (c->header() != nullptr) {
+ st->print("%.4s-", c->header());
+ }
+ st->print("%s,", c->name());
+ }
+ previous = c;
+ c = c->next();
+ }
+ st->cr();
+}
+
+// Print a human readable size.
+// byte_size: size, in bytes, to be printed.
+// scale: K,M,G or 0 (dynamic)
+// width: printing width.
+static int print_memory_size(outputStream* st, size_t byte_size, size_t scale) {
+
+ // If we forced a unit via scale=.. argument, we suppress display of the unit
+ // since we already know which unit is used. That saves horizontal space and
+ // makes automatic processing of the data easier.
+ bool dynamic_mode = false;
+
+ if (scale == 0) {
+ dynamic_mode = true;
+ // Dynamic mode. Choose scale for this value.
+ if (byte_size == 0) {
+ scale = K;
+ } else {
+ if (byte_size >= G) {
+ scale = G;
+ } else if (byte_size >= M) {
+ scale = M;
+ } else {
+ scale = K;
+ }
+ }
+ }
+
+ const char* display_unit = "";
+ if (dynamic_mode) {
+ switch(scale) {
+ case K: display_unit = "k"; break;
+ case M: display_unit = "m"; break;
+ case G: display_unit = "g"; break;
+ default:
+ ShouldNotReachHere();
+ }
+ }
+
+ // How we display stuff:
+ // scale=1 (manually set) - print exact byte values without unit
+ // scale=0 (default, dynamic mode) - print values < 1024KB as "..k", <1024MB as "..m", "..g" above that
+ // - to distinguish between 0 and "almost 0" print very small values as "<1K"
+ // - print "k", "m" values with precision 0, "g" values with precision 1.
+ // scale=k,m or g (manually set) - print value divided by scale and without unit. No smart printing.
+ // Used mostly for automated processing, lets keep parsing simple.
+
+ int l = 0;
+ if (scale == 1) {
+ // scale = 1 - print exact bytes
+ l = printf_helper(st, "%zu", byte_size);
+ } else {
+ const float display_value = (float) byte_size / scale;
+ if (dynamic_mode) {
+ // dynamic scale
+ const int precision = scale >= G ? 1 : 0;
+ if (byte_size > 0 && byte_size < 1 * K) {
+ // very small but not zero.
+ assert(scale == K, "Sanity");
+ l = printf_helper(st, "<1%s", display_unit);
+ } else {
+ l = printf_helper(st, "%.*f%s", precision, display_value, display_unit);
+ }
+ } else {
+ // fixed scale K, M or G
+ const int precision = 0;
+ l = printf_helper(st, "%.*f%s", precision, display_value, display_unit);
+ }
+ }
+
+ return l;
+
+}
+
+///////// class Column and childs ///////////
+
+Column::Column(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : _category(category),
+ _header(header), // may be nullptr
+ _name(name),
+ _description(description),
+ _extremum(extremum),
+ _next(nullptr), _idx(-1),
+ _idx_cat(-1), _idx_hdr(-1)
+{}
+
+void Column::print_value(outputStream* st, value_t value, value_t last_value,
+ int last_value_age, int min_width, const print_info_t* pi, char const* marker) const {
+
+ // We print all values right aligned.
+ int needed = calc_print_size(value, last_value, last_value_age, pi);
+ if (pi->csv == false && min_width > needed) {
+ // In ascii (non csv) mode, pad to minimum width
+ ostream_put_n(st, ' ', min_width - needed);
+ }
+ // csv values shall be enclosed in quotes.
+ if (pi->csv) {
+ st->put('"');
+ }
+ do_print(st, value, last_value, last_value_age, pi);
+ st->print_raw(marker);
+ if (pi->csv) {
+ st->put('"');
+ }
+}
+
+// Returns the number of characters this value needs to be printed.
+int Column::calc_print_size(value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const {
+ return do_print(nullptr, value, last_value, last_value_age, pi);
+}
+
+int Column::do_print(outputStream* st, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const {
+ if (value == INVALID_VALUE) {
+ if (pi->raw) {
+ if (st != nullptr) {
+ return printf_helper(st, "%s", "?");
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ if (pi->raw) {
+ return printf_helper(st, UINT64_FORMAT, value);
+ } else {
+ return do_print0(st, value, last_value, last_value_age, pi);
+ }
+ }
+}
+
+int PlainValueColumn::do_print0(outputStream* st, value_t value,
+ value_t last_value, int last_value_age, const print_info_t* pi) const
+{
+ return printf_helper(st, UINT64_FORMAT, value);
+}
+
+int DeltaValueColumn::do_print0(outputStream* st, value_t value,
+ value_t last_value, int last_value_age, const print_info_t* pi) const {
+ if (last_value > value) {
+ // we assume the underlying value to be monotonically raising, and that
+ // any negative delta would be just a fluke (e.g. counter overflows)
+ // we do not want to show
+ return 0;
+ }
+ if (last_value != INVALID_VALUE) {
+ return printf_helper(st, INT64_FORMAT, (int64_t)(value - last_value));
+ }
+ return 0;
+}
+
+int MemorySizeColumn::do_print0(outputStream* st, value_t value,
+ value_t last_value, int last_value_age, const print_info_t* pi) const {
+ return print_memory_size(st, value, pi->scale);
+}
+
+int DeltaMemorySizeColumn::do_print0(outputStream* st, value_t value,
+ value_t last_value, int last_value_age, const print_info_t* pi) const {
+ if (last_value != INVALID_VALUE) {
+ return print_memory_size(st, value - last_value, pi->scale);
+ }
+ return 0;
+}
+
+////////////// sample printing ///////////////////////////
+
+// Print one sample.
+static void print_one_sample(outputStream* st, const Sample* sample,
+ const Sample* last_sample, const ColumnWidths* widths, const print_info_t* pi, int marked_index = -1, char const* mark = nullptr) {
+
+ // Print timestamp and divider
+ if (pi->csv) {
+ st->print("\"");
+ }
+ print_timestamp(st, sample->timestamp());
+ if (pi->csv) {
+ st->print("\"");
+ }
+
+ if (pi->csv == false) {
+ ostream_put_n(st, ' ', TIMESTAMP_DIVIDER_LEN);
+ } else {
+ st->put(',');
+ }
+
+ const Column* c = ColumnList::the_list()->first();
+ while (c != nullptr) {
+ const int idx = c->index();
+ const value_t v = sample->value(idx);
+ value_t v2 = INVALID_VALUE;
+ int age = -1;
+ if (last_sample != nullptr) {
+ v2 = last_sample->value(idx);
+ age = sample->timestamp() - last_sample->timestamp();
+ }
+ const int min_width = widths->at(idx) - (marked_index >= 0 ? 1 : 0);
+ c->print_value(st, v, v2, age, min_width, pi,
+ marked_index == idx ? mark : (marked_index >= 0 && !pi->csv ? " " : ""));
+ st->put(pi->csv ? ',' : ' ');
+ c = c->next();
+ }
+ st->cr();
+}
+
+////////////// Class SampleTable /////////////////////////
+
+// A fixed sized fifo buffer of n samples
+class SampleTable : public CHeapObj {
+
+ const int _num_entries;
+ int _head; // Index of the last sample written; -1 if none have been written yet
+ bool _did_wrap;
+ Sample* _samples;
+
+#ifdef ASSERT
+ void verify() const {
+ assert(_samples != nullptr, "sanity");
+ assert(_head >= 0 && _head < _num_entries, "sanity");
+ }
+#endif
+
+ size_t sample_offset_in_bytes(int idx) const {
+ assert(idx >= 0 && idx <= _num_entries, "invalid index: %d", idx);
+ return Sample::size_in_bytes() * idx;
+ }
+
+public:
+
+ SampleTable(int num_entries)
+ : _num_entries(num_entries),
+ _head(-1),
+ _did_wrap(false),
+ _samples(nullptr)
+ {
+ _samples = (Sample*) NEW_C_HEAP_ARRAY(char, Sample::size_in_bytes() * _num_entries, mtInternal);
+#ifdef ASSERT
+ for (int i = 0; i < _num_entries; i ++) {
+ sample_at(i)->reset();
+ }
+#endif
+ }
+
+ bool is_empty() const { return _head == -1; }
+
+ const Sample* sample_at(int index) const { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }
+ Sample* sample_at(int index) { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }
+
+ void add_sample(const Sample* sample) {
+ // Advance head
+ _head ++;
+ if (_head == _num_entries) {
+ _did_wrap = true;
+ _head = 0;
+ }
+ // Copy sample
+ ::memcpy(sample_at(_head), sample, Sample::size_in_bytes());
+ DEBUG_ONLY(verify());
+ }
+
+ // Given a valid sample index, return the previous index or -1 if this is the oldest sample.
+ int get_previous_index(int idx) const {
+ assert(idx >= 0 && idx <= _num_entries, "index oob: %d", idx);
+ assert(_did_wrap == true || idx <= _head, "index invalid: %d", idx);
+ int prev = idx - 1;
+ if (prev == -1 && _did_wrap) {
+ prev = _num_entries - 1;
+ }
+ if (prev == _head) {
+ prev = -1;
+ }
+ return prev;
+ }
+
+ class Closure {
+ public:
+ virtual void do_sample(const Sample* sample, const Sample* previous_sample) = 0;
+ };
+
+ void call_closure_for_sample_at(Closure* closure, int idx) const {
+ const Sample* sample = sample_at(idx);
+ int idx2 = get_previous_index(idx);
+ const Sample* previous_sample = idx2 == -1 ? nullptr : sample_at(idx2);
+ closure->do_sample(sample, previous_sample);
+ }
+
+ void walk_table_locked(Closure* closure, bool youngest_to_oldest = true) const {
+
+ if (_head == -1) {
+ return;
+ }
+
+ DEBUG_ONLY(verify();)
+
+ if (youngest_to_oldest) { // youngest to oldest
+ for (int pos = _head; pos >= 0; pos--) {
+ call_closure_for_sample_at(closure, pos);
+ }
+ if (_did_wrap) {
+ for (int pos = _num_entries - 1; pos > _head; pos--) {
+ call_closure_for_sample_at(closure, pos);
+ }
+ }
+ } else { // oldest to youngest
+ if (_did_wrap) {
+ for (int pos = _head + 1; pos < _num_entries; pos ++) {
+ call_closure_for_sample_at(closure, pos);
+ }
+ }
+ for (int pos = 0; pos <= _head; pos ++) {
+ call_closure_for_sample_at(closure, pos);
+ }
+ }
+ }
+
+};
+
+class MeasureColumnWidthsClosure : public SampleTable::Closure {
+ const print_info_t* const _pi;
+ ColumnWidths* const _widths;
+
+public:
+ MeasureColumnWidthsClosure(const print_info_t* pi, ColumnWidths* widths) :
+ _pi(pi), _widths(widths) {}
+
+ void do_sample(const Sample* sample, const Sample* previous_sample) {
+ _widths->update_from_sample(sample, previous_sample, _pi);
+ }
+};
+
+class PrintSamplesClosure : public SampleTable::Closure {
+ outputStream* const _st;
+ const print_info_t* const _pi;
+ const ColumnWidths* const _widths;
+
+public:
+
+ PrintSamplesClosure(outputStream* st, const print_info_t* pi, const ColumnWidths* widths) :
+ _st(st), _pi(pi), _widths(widths) {}
+
+ void do_sample(const Sample* sample, const Sample* previous_sample) {
+ print_one_sample(_st, sample, previous_sample, _widths, _pi);
+ }
+};
+
+// sampleTables is a combination of two tables: a short term table and a long term table.
+// It takes care to feed new samples into these tables at the appropriate intervals.
+class SampleTables: public CHeapObj {
+
+ static int short_term_tablesize() { return (VitalsShortTermTableHours * 3600 / VitalsSampleInterval) + 1; }
+ static int long_term_tablesize() { return (VitalsLongTermTableDays * 24 * 60 / VitalsLongTermSampleIntervalMinutes) + 1; }
+
+ SampleTable _short_term_table;
+ SampleTable _long_term_table;
+ SampleTable _extremum_samples;
+ SampleTable _last_extremum_samples;
+
+ int _count;
+ int _large_table_count;
+
+ static void print_table(const SampleTable* table, outputStream* st,
+ const ColumnWidths* widths, const print_info_t* pi) {
+ if (table->is_empty()) {
+ st->print_cr("(no samples)");
+ return;
+ }
+ PrintSamplesClosure prclos(st, pi, widths);
+ table->walk_table_locked(&prclos, !pi->reverse_ordering);
+ }
+
+ static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {
+ if (pi->csv == false) {
+ print_category_line(st, widths, pi);
+ print_header_line(st, widths, pi);
+ }
+ print_column_names(st, widths, pi);
+ }
+
+ // Helper, print a time span given in seconds-
+ static void print_time_span(outputStream* st, int secs) {
+ const int mins = secs / 60;
+ const int hrs = secs / (60 * 60);
+ const int days = secs / (60 * 60 * 24);
+ if (days > 1) {
+ st->print_cr("Last %d days:", days);
+ } else if (hrs > 1) {
+ st->print_cr("Last %d hours:", hrs);
+ } else if (mins > 1) {
+ st->print_cr("Last %d minutes:", mins);
+ } else {
+ st->print_cr("Last %d seconds:", secs);
+ }
+ }
+
+public:
+
+ SampleTables()
+ : _short_term_table(short_term_tablesize()),
+ _long_term_table(long_term_tablesize()),
+ _extremum_samples(Sample::num_values()),
+ _last_extremum_samples(Sample::num_values()),
+ _count(0),
+ _large_table_count(MAX2(1, (int) (VitalsLongTermSampleIntervalMinutes * 60 / VitalsSampleInterval)))
+ {}
+
+ void add_sample(const Sample* sample) {
+ AutoLock autolock(&g_vitals_lock);
+ // Nothing we do in here blocks: the sample values are already taken,
+ // we only modify existing data structures (no memory is allocated either).
+ _short_term_table.add_sample(sample);
+ // Increment before feeding longterm table, in order for it to show up in reports
+ // only after an initial long term table interval has passed
+ _count++;
+ // Feed long term table
+ if ((_count % _large_table_count) == 0) {
+ _long_term_table.add_sample(sample);
+ }
+
+ // Update exetremum samples if needed.
+ if (StoreVitalsExtremas) {
+ static Sample* last_sample = nullptr;
+
+ if (last_sample == nullptr) {
+ // Nothing to do yet. We need at least two samples, since some types need the
+ // previous sample to print. Just allocate the space for the last sample as a marker
+ // for seeing the first sample.
+ last_sample = (Sample*) NEW_C_HEAP_ARRAY(char, Sample::size_in_bytes(), mtInternal);
+ } else if (_extremum_samples.is_empty()) {
+ // We already have a last sample and this is the second sample we see.
+ // We can initialize the two tables now to store the last sample and this sample
+ // for all extremas.
+ for (int i = 0; i < Sample::num_values(); ++i) {
+ _last_extremum_samples.add_sample(last_sample);
+ _extremum_samples.add_sample(sample);
+ }
+ } else {
+ // Iterate columns and update if needed.
+ for (Column const* column = ColumnList::the_list()->first(); column != nullptr; column = column->next()) {
+ if (column->extremum() != NONE) {
+ int idx = column->index();
+ Sample* extremum_sample = _extremum_samples.sample_at(idx);
+
+ bool should_log = (column->extremum() == MAX) && (sample->value(idx) > extremum_sample->value(idx));
+ should_log |= (column->extremum() == MIN) && (sample->value(idx) < extremum_sample->value(idx));
+ should_log &= sample->value(idx) != INVALID_VALUE;
+ should_log |= extremum_sample->value(idx) == INVALID_VALUE;
+
+ if (should_log) {
+ Sample* last_extremum_sample = _last_extremum_samples.sample_at(idx);
+ ::memcpy(last_extremum_sample, last_sample, Sample::size_in_bytes());
+ ::memcpy(extremum_sample, sample, Sample::size_in_bytes());
+ }
+ }
+ }
+ }
+
+ // Remember the last sample.
+ ::memcpy(last_sample, sample, Sample::size_in_bytes());
+ }
+ }
+
+ void print_all(outputStream* st, const print_info_t* pi, const Sample* sample_now) {
+
+ { // lock start
+ AutoLock autolock(&g_vitals_lock);
+
+ if (sample_now != nullptr) {
+ ColumnWidths widths;
+ MeasureColumnWidthsClosure mcwclos(pi, &widths);
+ widths.update_from_sample(sample_now, nullptr, pi);
+ st->print_cr("Now:");
+ print_headers(st, &widths, pi);
+ print_one_sample(st, sample_now, nullptr, &widths, pi);
+ st->cr();
+ }
+
+ if (!_short_term_table.is_empty()) {
+ ColumnWidths widths;
+ MeasureColumnWidthsClosure mcwclos(pi, &widths);
+ _short_term_table.walk_table_locked(&mcwclos);
+
+ if (pi->csv == false) {
+ print_time_span(st, VitalsShortTermTableHours * 3600);
+ }
+ print_headers(st, &widths, pi);
+ print_table(&_short_term_table, st, &widths, pi);
+ st->cr();
+ }
+
+ if (!_long_term_table.is_empty()) {
+ ColumnWidths widths;
+ MeasureColumnWidthsClosure mcwclos(pi, &widths);
+ _long_term_table.walk_table_locked(&mcwclos);
+ print_time_span(st, VitalsLongTermTableDays * 24 * 3600);
+ print_headers(st, &widths, pi);
+ print_table(&_long_term_table, st, &widths, pi);
+ st->cr();
+ }
+
+ if (StoreVitalsExtremas && !_extremum_samples.is_empty() && !_last_extremum_samples.is_empty()) {
+ st->print_cr("Samples at extremes (+ marks a maximum, - marks a minimum)");
+
+ ColumnWidths widths;
+ MeasureColumnWidthsClosure mcwclos(pi, &widths);
+
+ for (Column const* column = ColumnList::the_list()->first(); column != nullptr; column = column->next()) {
+ if (column->extremum() != NONE) {
+ Sample* extremum_sample = _extremum_samples.sample_at(column->index());
+ Sample* last_extremum_sample = _last_extremum_samples.sample_at(column->index());
+ widths.update_from_sample(extremum_sample, last_extremum_sample, pi, 1);
+ }
+ }
+
+ print_headers(st, &widths, pi); // Need more space for the mark to display.
+
+ for (Column const* column = ColumnList::the_list()->first(); column != nullptr; column = column->next()) {
+ if (column->extremum() != NONE) {
+ Sample* extremum_sample = _extremum_samples.sample_at(column->index());
+ Sample* last_extremum_sample = _last_extremum_samples.sample_at(column->index());
+ print_one_sample(st, extremum_sample, last_extremum_sample, &widths, pi, column->index(),
+ column->extremum() == MIN ? "-" : "+");
+ }
+ }
+ }
+
+ st->cr();
+
+ } // lock end
+ }
+};
+
+static SampleTables* g_all_tables = nullptr;
+
+/////////////// SAMPLING //////////////////////
+
+// Samples all values, but leaves timestamp unchanged
+static void sample_values(Sample* sample, bool avoid_locking) {
+ time_t t;
+ ::time(&t);
+ sample->set_timestamp(t);
+ DEBUG_ONLY(sample->set_num(-1);)
+ sample_jvm_values(sample, avoid_locking);
+ sample_platform_values(sample);
+}
+
+class SamplerThread: public NamedThread {
+
+ Sample* _sample;
+ bool _stop;
+ int _samples_taken;
+ int _jump_cooldown;
+
+ static int get_sample_interval_ms() {
+ return (int)VitalsSampleInterval * 1000;
+ }
+
+ void take_sample() {
+ _sample->reset();
+ DEBUG_ONLY(_sample->set_num(_samples_taken);)
+ _samples_taken ++;
+ sample_values(_sample, VitalsLockFreeSampling);
+ g_all_tables->add_sample(_sample);
+ }
+
+public:
+
+ SamplerThread()
+ : NamedThread(),
+ _sample(nullptr),
+ _stop(false),
+ _samples_taken(0),
+ _jump_cooldown(0)
+ {
+ _sample = Sample::allocate();
+ this->set_name("vitals sampler thread");
+ }
+
+ virtual void run() {
+ record_stack_base_and_size();
+ for (;;) {
+ take_sample();
+ os::naked_sleep(get_sample_interval_ms());
+ if (_stop) {
+ break;
+ }
+ }
+ }
+
+ void stop() {
+ _stop = true;
+ }
+
+};
+
+static SamplerThread* g_sampler_thread = nullptr;
+
+static bool initialize_sampler_thread() {
+ g_sampler_thread = new SamplerThread();
+ if (g_sampler_thread != nullptr) {
+ if (os::create_thread(g_sampler_thread, os::os_thread)) {
+ os::start_thread(g_sampler_thread);
+ }
+ return true;
+ }
+ return false;
+}
+
+
+///////////////////////////////////////
+/////// JVM-specific columns //////////
+
+static Column* g_col_heap_committed = nullptr;
+static Column* g_col_heap_used = nullptr;
+
+static Column* g_col_metaspace_committed = nullptr;
+static Column* g_col_metaspace_used = nullptr;
+
+static Column* g_col_classspace_committed = nullptr;
+static Column* g_col_classspace_used = nullptr;
+
+static Column* g_col_metaspace_cap_until_gc = nullptr;
+
+static Column* g_col_codecache_committed = nullptr;
+
+static bool g_show_nmt_columns = false;
+static Column* g_col_nmt_malloc = nullptr;
+static Column* g_col_nmt_mmap = nullptr;
+static Column* g_col_nmt_gc_overhead = nullptr;
+static Column* g_col_nmt_other = nullptr;
+static Column* g_col_nmt_overhead = nullptr;
+
+static Column* g_col_number_of_java_threads = nullptr;
+static Column* g_col_number_of_java_threads_non_demon = nullptr;
+
+static bool g_show_size_thread_stacks_col = false;
+static Column* g_col_size_thread_stacks = nullptr;
+
+static Column* g_col_number_of_java_threads_created = nullptr;
+
+static Column* g_col_number_of_clds = nullptr;
+static Column* g_col_number_of_anon_clds = nullptr;
+
+static Column* g_col_number_of_classes = nullptr;
+static Column* g_col_number_of_class_loads = nullptr;
+static Column* g_col_number_of_class_unloads = nullptr;
+
+static bool is_nmt_enabled() {
+#if INCLUDE_NMT
+ // Note: JDK version dependency: Before JDK18, NMT had the ability to shut down operations
+ // at any point in time, and therefore we also had NMT_minimal tracking level. Therefore,
+ // to stay version independent, we never must compare against NMT_off or NMT_minimal directly,
+ // only always against NMT_summary/detail. And to be very safe, don't assume a numerical
+ // value either, since older JDKs had NMT_unknown as a very high numerical value.
+ const NMT_TrackingLevel lvl = MemTracker::tracking_level();
+ return (lvl == NMT_summary || lvl == NMT_detail);
+#else
+ return false;
+#endif
+}
+
+static bool add_jvm_columns() {
+ // Order matters!
+ const char* const jvm_cat = "jvm";
+
+ Legend::the_legend()->add_footnote(" [delta]: values refer to the previous measurement.");
+ Legend::the_legend()->add_footnote(" [nmt]: only shown if NMT is available and activated");
+ Legend::the_legend()->add_footnote(" [cs]: only shown on 64-bit if class space is active");
+ Legend::the_legend()->add_footnote(" [linux]: only on Linux");
+
+ g_col_heap_committed =
+ define_column(jvm_cat, "heap", "comm", "Java Heap Size, committed", true);
+ g_col_heap_used =
+ define_column(jvm_cat, "heap", "used", "Java Heap Size, used", true);
+
+ g_col_metaspace_committed =
+ define_column(jvm_cat, "meta", "comm", "Meta Space Size (class+nonclass), committed", true);
+ g_col_metaspace_used =
+ define_column(jvm_cat, "meta", "used", "Meta Space Size (class+nonclass), used", true);
+
+ // Class space columns only shown if class space is active
+ g_col_classspace_committed =
+ define_column(jvm_cat, "meta", "csc", "Class Space Size, committed [cs]", INCLUDE_CLASS_SPACE == 1);
+ g_col_classspace_used =
+ define_column(jvm_cat, "meta", "csu", "Class Space Size, used [cs]", INCLUDE_CLASS_SPACE == 1);
+
+ g_col_metaspace_cap_until_gc =
+ define_column(jvm_cat, "meta", "gctr", "GC threshold", true);
+
+ g_col_codecache_committed =
+ define_column(jvm_cat, nullptr, "code", "Code cache, committed", true);
+
+ // NMT columns only shown if NMT is at least at summary level
+ g_show_nmt_columns = is_nmt_enabled();
+ g_col_nmt_malloc =
+ define_column(jvm_cat, "nmt", "mlc", "Memory malloced by hotspot [nmt]", g_show_nmt_columns);
+ g_col_nmt_mmap =
+ define_column(jvm_cat, "nmt", "map", "Memory mapped by hotspot [nmt]", g_show_nmt_columns);
+ g_col_nmt_gc_overhead =
+ define_column(jvm_cat, "nmt", "gc", "NMT \"gc\" (GC-overhead, malloc and mmap) [nmt]", g_show_nmt_columns);
+ g_col_nmt_other =
+ define_column(jvm_cat, "nmt", "oth", "NMT \"other\" (typically DBB or Unsafe.allocateMemory) [nmt]", g_show_nmt_columns);
+ g_col_nmt_overhead =
+ define_column(jvm_cat, "nmt", "ovh", "NMT overhead [nmt]", g_show_nmt_columns);
+
+ g_col_number_of_java_threads =
+ define_column(jvm_cat, "jthr", "num", "Number of java threads", true);
+ g_col_number_of_java_threads_non_demon =
+ define_column(jvm_cat, "jthr", "nd", "Number of non-demon java threads", true);
+ g_col_number_of_java_threads_created =
+ define_column(jvm_cat, "jthr", "cr", "Threads created [delta]", true);
+
+ // Displaying thread stack size for now only implemented on Linux, and requires NMT
+ g_show_size_thread_stacks_col = LINUX_ONLY(g_show_nmt_columns) NOT_LINUX(false);
+ g_col_size_thread_stacks =
+ define_column(jvm_cat, "jthr", "st", "Total reserved size of java thread stacks [nmt] [linux]",
+ g_show_size_thread_stacks_col);
+
+ g_col_number_of_clds =
+ define_column(jvm_cat, "cldg", "num", "Classloader Data", true);
+ g_col_number_of_anon_clds =
+ define_column(jvm_cat, "cldg", "anon", "Anonymous CLD", true);
+
+ g_col_number_of_classes =
+ define_column(jvm_cat, "cls", "num", "Classes (instance + array)", true);
+
+ g_col_number_of_class_loads =
+ define_column(jvm_cat, "cls", "ld", "Class loaded [delta]", true);
+
+ g_col_number_of_class_unloads =
+ define_column(jvm_cat, "cls", "uld", "Classes unloaded [delta]", true);
+
+ return true;
+}
+
+
+////////// class ValueSampler and childs /////////////////
+
+template
+static void set_value_in_sample(const Column* col, Sample* sample, T t) {
+ if (col != nullptr) {
+ int idx = col->index();
+ assert(ColumnList::the_list()->is_valid_column_index(idx), "Invalid column index");
+ sample->set_value(idx, (value_t)t);
+ }
+}
+
+struct nmt_values_t {
+ // How much memory, in total, was committed via mmap
+ value_t mapped_total;
+ // How much memory, in total, was malloced
+ value_t malloced_total;
+ // How many allocations from malloc, in total
+ value_t malloced_num;
+ // thread stack size; depending on NMT version this would
+ // be reserved (I believe up to and including jdk 8) or committed (9+)
+ value_t thread_stacks_committed;
+ // NMT "GC" category (both malloced and mapped)
+ value_t gc_overhead;
+ // NMT "other" category (both malloced and mapped). Usually dominated by DBB allocated with allocateDirect(),
+ // and Unsafe.allocateMemory.
+ value_t other_memory;
+ // NMT overhead (both malloced and mapped)
+ value_t overhead;
+};
+
+static bool get_nmt_values(nmt_values_t* out) {
+#if INCLUDE_NMT
+ if (is_nmt_enabled()) {
+ MutexLocker locker(MemTracker::query_lock());
+ MemBaseline baseline;
+ baseline.baseline(true);
+ MallocMemorySnapshot* mlc_snapshot = baseline.malloc_memory_snapshot();
+ VirtualMemorySnapshot vm_snapshot;
+ {
+ MemTracker::NmtVirtualMemoryLocker nvml;
+ VirtualMemorySummary::snapshot(&vm_snapshot);
+ }
+ out->malloced_total = mlc_snapshot->total();
+ out->mapped_total = vm_snapshot.total_committed();
+ out->thread_stacks_committed =
+ vm_snapshot.by_tag(mtThreadStack)->committed();
+ out->thread_stacks_committed =
+ vm_snapshot.by_tag(MemTag::mtThreadStack)->committed() +
+ mlc_snapshot->by_tag(MemTag::mtThreadStack)->malloc_size();
+ out->gc_overhead =
+ vm_snapshot.by_tag(MemTag::mtGC)->committed() +
+ mlc_snapshot->by_tag(MemTag::mtGC)->malloc_size();
+ out->other_memory =
+ vm_snapshot.by_tag(MemTag::mtOther)->committed() +
+ mlc_snapshot->by_tag(MemTag::mtOther)->malloc_size();
+ out->overhead =
+ vm_snapshot.by_tag(MemTag::mtNMT)->committed() +
+ mlc_snapshot->by_tag(MemTag::mtNMT)->malloc_size() +
+ mlc_snapshot->malloc_overhead();
+ out->malloced_num =
+ mlc_snapshot->total_count();
+ return true;
+ }
+#endif // INCLUDE_NMT
+ return false;
+}
+
+void sample_jvm_values(Sample* sample, bool avoid_locking) {
+
+ // Note: if avoid_locking=true, skip values which need JVM-side locking.
+
+ nmt_values_t nmt_vals;
+ bool have_nmt_values = false;
+ if (!avoid_locking) {
+ have_nmt_values = get_nmt_values(&nmt_vals);
+ }
+
+ // Heap
+ if (!avoid_locking) {
+ size_t heap_cap = 0;
+ size_t heap_used = 0;
+ const CollectedHeap* const heap = Universe::heap();
+ if (heap != nullptr) {
+ MutexLocker hl(Heap_lock);
+ heap_cap = Universe::heap()->capacity();
+ heap_used = Universe::heap()->used();
+ }
+ set_value_in_sample(g_col_heap_committed, sample, heap_cap);
+ set_value_in_sample(g_col_heap_used, sample, heap_used);
+ }
+
+ // Metaspace
+ set_value_in_sample(g_col_metaspace_committed, sample, MetaspaceUtils::committed_bytes());
+ set_value_in_sample(g_col_metaspace_used, sample, MetaspaceUtils::used_bytes());
+
+ if (INCLUDE_CLASS_SPACE == 1) {
+ set_value_in_sample(g_col_classspace_committed, sample, MetaspaceUtils::committed_bytes(Metaspace::ClassType));
+ set_value_in_sample(g_col_classspace_used, sample, MetaspaceUtils::used_bytes(Metaspace::ClassType));
+ }
+
+ set_value_in_sample(g_col_metaspace_cap_until_gc, sample, MetaspaceGC::capacity_until_GC());
+
+ // Code cache
+ value_t codecache_committed = INVALID_VALUE;
+ if (!avoid_locking) {
+ MutexLocker lck(CodeCache_lock, Mutex::_no_safepoint_check_flag);
+ codecache_committed = CodeCache::capacity();
+ }
+ set_value_in_sample(g_col_codecache_committed, sample, codecache_committed);
+
+ // NMT integration
+ if (have_nmt_values) {
+ set_value_in_sample(g_col_nmt_malloc, sample, nmt_vals.malloced_total);
+ set_value_in_sample(g_col_nmt_mmap, sample, nmt_vals.mapped_total);
+ set_value_in_sample(g_col_nmt_gc_overhead, sample, nmt_vals.gc_overhead);
+ set_value_in_sample(g_col_nmt_other, sample, nmt_vals.other_memory);
+ set_value_in_sample(g_col_nmt_overhead, sample, nmt_vals.overhead);
+ }
+
+ // Java threads
+ set_value_in_sample(g_col_number_of_java_threads, sample, Threads::number_of_threads());
+ set_value_in_sample(g_col_number_of_java_threads_non_demon, sample, Threads::number_of_non_daemon_threads());
+ set_value_in_sample(g_col_number_of_java_threads_created, sample, counters::g_threads_created);
+
+ // Java thread stack size
+ if (have_nmt_values) {
+ set_value_in_sample(g_col_size_thread_stacks, sample, nmt_vals.thread_stacks_committed);
+ }
+
+ // CLDG
+ set_value_in_sample(g_col_number_of_clds, sample, counters::g_number_of_clds);
+ set_value_in_sample(g_col_number_of_anon_clds, sample, counters::g_number_of_anon_clds);
+
+ // Classes
+ set_value_in_sample(g_col_number_of_classes, sample,
+ ClassLoaderDataGraph::num_instance_classes() + ClassLoaderDataGraph::num_array_classes());
+ set_value_in_sample(g_col_number_of_class_loads, sample, counters::g_classes_loaded);
+ set_value_in_sample(g_col_number_of_class_unloads, sample, counters::g_classes_unloaded);
+}
+
+bool initialize() {
+
+ static bool initialized = false;
+ assert(initialized == false, "Vitals already initialized");
+ initialized = true;
+
+ log_info(vitals)("Vitals v%x", vitals_version);
+
+ bool success = ColumnList::initialize();
+ success = success && Legend::initialize();
+
+ // Order matters. First platform columns, then jvm columns.
+ success = success && platform_columns_initialize();
+ success = success && add_jvm_columns();
+
+ // -- Now the number of columns is known (and fixed). --
+
+ g_all_tables = new SampleTables();
+ success = success && (g_all_tables != nullptr);
+
+ success = success && initialize_sampler_thread();
+
+ if (success) {
+ log_info(vitals)("Vitals initialized.");
+ log_debug(vitals)("Vitals sample interval: %zu seconds.", VitalsSampleInterval);
+ } else {
+ log_warning(vitals)("Failed to initialize Vitals.");
+ }
+
+ return success;
+
+}
+
+void cleanup() {
+ if (g_sampler_thread != nullptr) {
+ g_sampler_thread->stop();
+ }
+}
+
+void default_settings(print_info_t* out) {
+ out->raw = false;
+ out->csv = false;
+ out->no_legend = false;
+ out->reverse_ordering = false;
+ out->scale = 0;
+ out->sample_now = false;
+}
+
+void print_report(outputStream* st, const print_info_t* pinfo) {
+
+ if (ColumnList::the_list() == nullptr) {
+ st->print_cr(" (unavailable)");
+ return;
+ }
+
+ print_info_t info;
+ if (pinfo != nullptr) {
+ info = *pinfo;
+ } else {
+ default_settings(&info);
+ }
+
+ if (info.csv == false) {
+ st->cr();
+ }
+
+ // Print legend at the top (omit if suppressed on command line, or in csv mode).
+ if (info.no_legend == false && info.csv == false) {
+ Legend::the_legend()->print_on(st);
+ if (info.scale != 0) {
+ const char* display_unit = nullptr;
+ switch (info.scale) {
+ case 1: display_unit = "bytes"; break;
+ case K: display_unit = "KB"; break;
+ case M: display_unit = "MB"; break;
+ case G: display_unit = "GB"; break;
+ default: ShouldNotReachHere();
+ }
+ st->print_cr("[mem] values are in %s.", display_unit);
+ }
+ st->cr();
+ }
+
+ // If we are to sample the current values at print time, do that and print them too.
+ // Note: we omit the "Now" sample for csv output.
+ Sample* sample_now = nullptr;
+ if (info.sample_now && !info.csv) {
+ sample_now = Sample::allocate();
+ sample_values(sample_now, true /* never lock for now sample - be safe */ );
+ }
+
+ g_all_tables->print_all(st, &info, sample_now);
+
+ os::free(sample_now);
+}
+
+// Dump both textual and csv style reports to two files, "sapmachine_vitals_.txt" and "sapmachine_vitals_.csv".
+// If these files exist, they are overwritten.
+void dump_reports() {
+
+ static const char* file_prefix = "sapmachine_vitals_";
+ char vitals_file_name[1024];
+
+ if (VitalsFile != nullptr) {
+ os::snprintf_checked(vitals_file_name, sizeof(vitals_file_name), "%s.txt", VitalsFile);
+ } else {
+ os::snprintf_checked(vitals_file_name, sizeof(vitals_file_name), "%s%d.txt", file_prefix, os::current_process_id());
+ }
+
+ // Note: we print two reports, both in reverse order (oldest to youngest). One in text form, one as csv.
+
+ ::printf("Dumping Vitals to %s\n", vitals_file_name);
+ {
+ fileStream fs(vitals_file_name);
+ static const sapmachine_vitals::print_info_t settings = {
+ false, // raw
+ false, // csv
+ false, // no_legend
+ true, // reverse_ordering
+ 0, // scale
+ true // sample_now
+ };
+ print_report(&fs, &settings);
+ }
+
+ if (VitalsFile != nullptr) {
+ os::snprintf_checked(vitals_file_name, sizeof(vitals_file_name), "%s.csv", VitalsFile);
+ } else {
+ os::snprintf_checked(vitals_file_name, sizeof(vitals_file_name), "%s%d.csv", file_prefix, os::current_process_id());
+ }
+ ::printf("Dumping Vitals csv to %s\n", vitals_file_name);
+ {
+ fileStream fs(vitals_file_name);
+ static const sapmachine_vitals::print_info_t settings = {
+ false, // raw
+ true, // csv
+ false, // no_legend
+ true, // reverse_ordering
+ 1 * K, // scale
+ false // sample_now
+ };
+ print_report(&fs, &settings);
+ }
+}
+
+// For printing in thread lists only.
+const Thread* samplerthread() { return g_sampler_thread; }
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/share/vitals/vitals.hpp b/src/hotspot/share/vitals/vitals.hpp
new file mode 100644
index 000000000000..b09c45967c4a
--- /dev/null
+++ b/src/hotspot/share/vitals/vitals.hpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. All rights reserved.
+ * Copyright (c) 2019, 2022, 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.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_VITALS_VITALS_HPP
+#define HOTSPOT_SHARE_VITALS_VITALS_HPP
+
+#include "utilities/globalDefinitions.hpp"
+
+// Configure for different JDK versions
+#define JDK_MAINLINE
+//#define JDK17u
+//#define JDK11u
+
+class outputStream;
+class Thread;
+
+namespace sapmachine_vitals {
+
+ bool initialize();
+ void cleanup();
+
+ struct print_info_t {
+ bool raw;
+ bool csv;
+ // Omit printing a legend.
+ bool no_legend;
+ // Reverse printing order (default: youngest-to-oldest; reversed: oldest-to-youngest)
+ bool reverse_ordering;
+
+ size_t scale;
+
+ // if true, sample and print the current values too. If false,
+ // just print the sample tables.
+ bool sample_now;
+
+ };
+
+ void default_settings(print_info_t* out);
+
+ // Print report to stream. Leave print_info nullptr for default settings.
+ void print_report(outputStream* st, const print_info_t* print_info = nullptr);
+
+ // Dump both textual and csv style reports to two files, "vitals_.txt" and "vitals_.csv".
+ // If these files exist, they are overwritten.
+ void dump_reports();
+
+ // For printing in thread lists only.
+ const Thread* samplerthread();
+
+ namespace counters {
+ void inc_cld_count(bool is_anon_cld);
+ void dec_cld_count(bool is_anon_cld);
+ void inc_classes_loaded(size_t count);
+ void inc_classes_unloaded(size_t count);
+ void inc_threads_created(size_t count);
+ };
+
+};
+
+#endif /* HOTSPOT_SHARE_VITALS_VITALS_HPP */
diff --git a/src/hotspot/share/vitals/vitalsDCmd.cpp b/src/hotspot/share/vitals/vitalsDCmd.cpp
new file mode 100644
index 000000000000..8d127ec53cde
--- /dev/null
+++ b/src/hotspot/share/vitals/vitalsDCmd.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "memory/resourceArea.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+#include "vitals/vitals.hpp"
+#include "vitals/vitalsDCmd.hpp"
+
+namespace sapmachine_vitals {
+
+VitalsDCmd::VitalsDCmd(outputStream* output, bool heap)
+ : DCmdWithParser(output, heap),
+ _scale("scale", "Memory usage in which to scale. Valid values are: k, m, g (fixed scale) "
+ "or \"dynamic\" for a dynamically chosen scale.",
+ "STRING", false, "dynamic"),
+ _csv("csv", "csv format.", "BOOLEAN", false, "false"),
+ _no_legend("no-legend", "Omit legend.", "BOOLEAN", false, "false"),
+ _reverse("reverse", "Reverse printing order.", "BOOLEAN", false, "false"),
+ _raw("raw", "Print raw values.", "BOOLEAN", false, "false"),
+ _sample_now("now", "Sample now values", "BOOLEAN", false, "false")
+{
+ _dcmdparser.add_dcmd_option(&_scale);
+ _dcmdparser.add_dcmd_option(&_csv);
+ _dcmdparser.add_dcmd_option(&_no_legend);
+ _dcmdparser.add_dcmd_option(&_reverse);
+ _dcmdparser.add_dcmd_option(&_raw);
+ _dcmdparser.add_dcmd_option(&_sample_now);
+}
+
+static bool scale_from_name(const char* scale, size_t* out) {
+ if (strcasecmp(scale, "dynamic") == 0) {
+ *out = 0;
+ } else if (strcasecmp(scale, "1") == 0 || strcasecmp(scale, "b") == 0) {
+ *out = 1;
+ } else if (strcasecmp(scale, "kb") == 0 || strcasecmp(scale, "k") == 0) {
+ *out = K;
+ } else if (strcasecmp(scale, "mb") == 0 || strcasecmp(scale, "m") == 0) {
+ *out = M;
+ } else if (strcasecmp(scale, "gb") == 0 || strcasecmp(scale, "g") == 0) {
+ *out = G;
+ } else {
+ return false; // Invalid value
+ }
+ return true;
+}
+
+void VitalsDCmd::execute(DCmdSource source, TRAPS) {
+ sapmachine_vitals::print_info_t info;
+ sapmachine_vitals::default_settings(&info);
+ if (!scale_from_name(_scale.value(), &(info.scale))) {
+ output()->print_cr("Invalid scale: \"%s\".", _scale.value());
+ return;
+ }
+ info.csv = _csv.value();
+ info.no_legend = _no_legend.value();
+ info.reverse_ordering = _reverse.value();
+ info.raw = _raw.value();
+ info.sample_now = _sample_now.value();
+
+ output()->print_cr("Vitals:");
+ if (info.sample_now && info.csv) {
+ output()->print_cr("(\"now\" ignored in csv mode)");
+ }
+ sapmachine_vitals::print_report(output(), &info);
+}
+
+} // namespace sapmachine_vitals
diff --git a/src/hotspot/share/vitals/vitalsDCmd.hpp b/src/hotspot/share/vitals/vitalsDCmd.hpp
new file mode 100644
index 000000000000..460dcc151a7c
--- /dev/null
+++ b/src/hotspot/share/vitals/vitalsDCmd.hpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 2023 SAP SE. All rights reserved.
+ * Copyright (c) 2019, 2022, 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.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP
+#define HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP
+
+#include "services/diagnosticCommand.hpp"
+
+namespace sapmachine_vitals {
+
+class VitalsDCmd : public DCmdWithParser {
+protected:
+ DCmdArgument _scale;
+ DCmdArgument _csv;
+ DCmdArgument _no_legend;
+ DCmdArgument _reverse;
+ DCmdArgument _raw;
+ DCmdArgument _sample_now;
+public:
+ static int num_arguments() { return 6; }
+ VitalsDCmd(outputStream* output, bool heap);
+ static const char* name() {
+ return "VM.vitals";
+ }
+ static const char* description() {
+ return "Print Vitals.";
+ }
+ static const char* impact() {
+ return "Low.";
+ }
+ virtual void execute(DCmdSource source, TRAPS);
+};
+
+} // namespace sapmachine_vitals
+
+#endif // HOTSPOT_SHARE_VITALS_VITALSDCMD_HPP
diff --git a/src/hotspot/share/vitals/vitalsLocker.cpp b/src/hotspot/share/vitals/vitalsLocker.cpp
new file mode 100644
index 000000000000..e9db3a56fb16
--- /dev/null
+++ b/src/hotspot/share/vitals/vitalsLocker.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. 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.
+ *
+ */
+
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "vitals/vitalsLocker.hpp"
+
+#ifndef _WIN32
+#include
+#endif
+
+namespace sapmachine_vitals {
+
+#ifdef _WIN32
+
+Lock::Lock(const char* name) : _name(name) {
+ ::InitializeCriticalSection(&_lock);
+}
+
+void Lock::lock() {
+ ::EnterCriticalSection(&_lock);
+}
+
+void Lock::unlock() {
+ ::LeaveCriticalSection(&_lock);
+}
+
+#else
+
+PRAGMA_DIAG_PUSH
+PRAGMA_ZERO_AS_NULL_POINTER_CONSTANT_IGNORED
+Lock::Lock(const char* name) : _name(name), _lock(PTHREAD_MUTEX_INITIALIZER) {}
+PRAGMA_DIAG_POP
+
+void Lock::lock() {
+ int rc = ::pthread_mutex_lock(&_lock);
+ assert(rc == 0, "%s: failed to grab lock (%d).", _name, errno);
+}
+
+void Lock::unlock() {
+ int rc = ::pthread_mutex_unlock(&_lock);
+ assert(rc == 0, "%s: failed to release lock (%d).", _name, errno);
+}
+
+#endif
+
+}; // namespace sapmachine_vitals
diff --git a/src/hotspot/share/vitals/vitalsLocker.hpp b/src/hotspot/share/vitals/vitalsLocker.hpp
new file mode 100644
index 000000000000..7f01fdb31c04
--- /dev/null
+++ b/src/hotspot/share/vitals/vitalsLocker.hpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021, 2022 SAP SE. 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.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_VITALS_VITALSLOCKER_HPP
+#define HOTSPOT_SHARE_VITALS_VITALSLOCKER_HPP
+
+// SapMachine 2021-10-14: I need a simple critical section. I don't
+// need hotspot mutex error checking here, and I want to be independent of
+// upstream changes to hotspot mutexes.
+
+#ifdef _WIN32
+ #include
+#else
+ #include
+#endif
+
+namespace sapmachine_vitals {
+
+class Lock {
+ const char* const _name;
+#ifdef _WIN32
+ CRITICAL_SECTION _lock;
+#else
+ pthread_mutex_t _lock;
+#endif
+
+public:
+ Lock(const char* name);
+ void lock();
+ void unlock();
+};
+
+class AutoLock {
+ Lock* const _lock;
+public:
+ AutoLock(Lock* lock)
+ : _lock(lock)
+ {
+ _lock->lock();
+ }
+ ~AutoLock() {
+ _lock->unlock();
+ }
+};
+
+};
+
+#endif /* HOTSPOT_SHARE_VITALS_VITALS_HPP */
diff --git a/src/hotspot/share/vitals/vitals_internals.hpp b/src/hotspot/share/vitals/vitals_internals.hpp
new file mode 100644
index 000000000000..4314318575d3
--- /dev/null
+++ b/src/hotspot/share/vitals/vitals_internals.hpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2019, 2025 SAP SE. All rights reserved.
+ * Copyright (c) 2019, 2022, 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.
+ *
+ */
+
+#ifndef HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP
+#define HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP
+
+#include "memory/allocation.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+#include "vitals/vitals.hpp"
+
+namespace sapmachine_vitals {
+
+ static const int vitals_version = 0x220600;
+
+ typedef uint64_t value_t;
+#define INVALID_VALUE ((value_t)UINT64_MAX)
+
+ class Sample {
+ DEBUG_ONLY(int _num;)
+ time_t _timestamp;
+ value_t _values[1]; // var sized
+ public:
+ static int num_values();
+ static size_t size_in_bytes();
+ static Sample* allocate();
+
+ void reset();
+ void set_value(int index, value_t v);
+ void set_timestamp(time_t t);
+ DEBUG_ONLY(void set_num(int n);)
+
+ value_t value(int index) const;
+ time_t timestamp() const { return _timestamp; }
+ DEBUG_ONLY(int num() const { return _num; })
+ };
+
+ class ColumnList;
+
+ enum Extremum {
+ NONE,
+ MAX,
+ MIN
+ };
+
+ class Column: public CHeapObj {
+ friend class ColumnList;
+
+ const char* const _category;
+ const char* const _header; // optional. May be nullptr.
+ const char* const _name;
+ const char* const _description;
+ const Extremum _extremum;
+
+ // The following members are fixed by ColumnList when the Column is added to it.
+ Column* _next; // next column in table
+ int _idx; // position in table
+ int _idx_cat; // position in category
+ int _idx_hdr; // position under its header (if any, 0 otherwise)
+
+ int do_print(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+
+ protected:
+
+ Column(const char* category, const char* header, const char* name, const char* description, Extremum extremum);
+
+ // Child classes implement this.
+ // output stream can be nullptr; in that case, method shall return number of characters it would have printed.
+ virtual int do_print0(outputStream* os, value_t value,
+ value_t last_value, int last_value_age, const print_info_t* pi) const = 0;
+
+ public:
+
+ const char* category() const { return _category; }
+ const char* header() const { return _header; }
+ const char* name() const { return _name; }
+ const char* description() const { return _description; }
+ Extremum extremum() const { return _extremum; }
+
+ void print_value(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, int min_width, const print_info_t* pi, char const* marker) const;
+
+ // Returns the number of characters this value needs to be printed.
+ int calc_print_size(value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+
+ // Returns the index (the position in the table) of this column.
+ int index() const { return _idx; }
+ int index_within_category_section() const { return _idx_cat; }
+ int index_within_header_section() const { return _idx_hdr; }
+
+ const Column* next () const { return _next; }
+
+ virtual bool is_memory_size() const { return false; }
+
+ static Extremum extremum_default() { return NONE; }
+ };
+
+ // Some standard column types
+
+ class PlainValueColumn: public Column {
+ int do_print0(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+ public:
+ PlainValueColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {}
+ };
+
+ class DeltaValueColumn: public Column {
+ int do_print0(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+ public:
+ // only_positive: only positive deltas are shown, negative deltas are supressed
+ DeltaValueColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {}
+ };
+
+ class MemorySizeColumn: public Column {
+ int do_print0(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+ public:
+ MemorySizeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {}
+ bool is_memory_size() const { return true; }
+ static Extremum extremum_default() { return MAX; }
+ };
+
+ class DeltaMemorySizeColumn: public Column {
+ int do_print0(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+ public:
+ DeltaMemorySizeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {}
+ };
+
+ class TimeStampColumn: public Column {
+ int do_print0(outputStream* os, value_t value, value_t last_value,
+ int last_value_age, const print_info_t* pi) const;
+ public:
+ TimeStampColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
+ : Column(category, header, name, description, extremum)
+ {}
+ };
+
+ ////// Legend: handles the legend
+
+ class Legend: public CHeapObj {
+ stringStream _legend;
+ stringStream _footnote;
+ static Legend* _the_legend;
+ // needed during building the legend
+ const char* _last_added_cat;
+ int _nr_of_columns;
+ public:
+ Legend();
+ void add_column_info(const char* const category, const char* const header,
+ const char* const name, const char* const description);
+ void add_footnote(const char* text);
+ void print_on(outputStream* st) const;
+ int nr_of_columns() { return _nr_of_columns; }
+ static Legend* the_legend () { return _the_legend; }
+ static bool initialize();
+ };
+
+ ////// ColumnList: a singleton class holding all information about all columns
+
+ class ColumnList: public CHeapObj {
+
+ Column* _first, *_last;
+ int _num_columns;
+
+ static ColumnList* _the_list;
+
+ public:
+
+ ColumnList()
+ : _first(nullptr), _last(nullptr), _num_columns(0)
+ {}
+
+ const Column* first() const { return _first; }
+ int num_columns() const { return _num_columns; }
+
+ void add_column(Column* column);
+
+ static ColumnList* the_list () { return _the_list; }
+
+ static bool initialize();
+
+#ifdef ASSERT
+ bool is_valid_column_index(int idx) {
+ return idx >= 0 && idx < _num_columns;
+ }
+#endif
+
+ };
+
+ // Convenient method to define and register a possibly deactivated column
+ // (a deactivated column is not shown in the table, but still shown in the legend, to
+ // given the user a hint about it)
+ template
+ Column* define_column (
+ const char* const category, const char* const header,
+ const char* const name, const char* const description,
+ bool is_active, Extremum extremum = ColumnType::extremum_default())
+ {
+ Column* c = nullptr;
+ if (is_active) {
+ c = new ColumnType(category, header, name, description, extremum);
+ ColumnList::the_list()->add_column(c);
+ }
+ Legend::the_legend()->add_column_info(category, header, name, description);
+ return c;
+ }
+
+ // Ask platform to add platform specific columns
+ bool platform_columns_initialize();
+
+ void sample_platform_values(Sample* sample);
+ void sample_jvm_values(Sample* sample, bool avoid_locking);
+
+}; // namespace sapmachine_vitals
+
+#endif /* HOTSPOT_SHARE_VITALS_VITALS_INTERNALS_HPP */
diff --git a/src/java.base/macosx/native/libjli/java_md_macosx.m b/src/java.base/macosx/native/libjli/java_md_macosx.m
index 2b205a65ba16..1c6b8263e434 100644
--- a/src/java.base/macosx/native/libjli/java_md_macosx.m
+++ b/src/java.base/macosx/native/libjli/java_md_macosx.m
@@ -344,6 +344,32 @@ static void MacOSXStartup(int argc, char *argv[]) {
}
JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%sjvm.cfg",
jdkroot, FILESEP, FILESEP);
+
+ /* SapMachine 2023-09-18: New malloc trace */
+ if (ShouldPreloadLibMallocHooks(*pargc, *pargv)) {
+ char const* env_name = "DYLD_INSERT_LIBRARIES";
+ char const* libpath = "/lib/libmallochooks.dylib";
+ char const* old_env = getenv(env_name);
+ char* env_entry;
+
+ if ((old_env == NULL) || (old_env[0] == '0')) {
+ size_t size = JLI_StrLen(jdkroot) + JLI_StrLen(libpath) + JLI_StrLen(env_name) + 2;
+ env_entry = JLI_MemAlloc(size);
+
+ snprintf(env_entry, size, "%s=%s%s", env_name, jdkroot, libpath);
+ } else {
+ size_t size = JLI_StrLen(jdkroot) + JLI_StrLen(libpath) + JLI_StrLen(env_name) +
+ 3 + JLI_StrLen(old_env);
+ env_entry = JLI_MemAlloc(size);
+
+ snprintf(env_entry, size, "%s=%s%s:%s", env_name, jdkroot, libpath, old_env);
+ }
+
+ if (putenv(env_entry) == 0) {
+ execv(execname, argv);
+ }
+ }
+
/* Find the specified JVM type */
if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
JLI_ReportErrorMessage(CFG_ERROR7);
diff --git a/src/java.base/share/classes/java/lang/Process.java b/src/java.base/share/classes/java/lang/Process.java
index 8aef43fe6c9e..6f0cf4a29654 100644
--- a/src/java.base/share/classes/java/lang/Process.java
+++ b/src/java.base/share/classes/java/lang/Process.java
@@ -25,6 +25,9 @@
package java.lang;
+// SapMachine 2024-07-01: process group extension
+import jdk.internal.access.JavaLangProcessAccess;
+import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.Blocker;
import jdk.internal.util.StaticProperty;
@@ -175,6 +178,18 @@ public abstract class Process implements Closeable {
private Charset errorCharset;
private boolean closed; // true if close() has been called
+ // SapMachine 2024-07-01: process group extension
+ static {
+ SharedSecrets.setJavaLangProcessAccess(
+ new JavaLangProcessAccess() {
+ @Override
+ public void destroyProcessGroup(Process leader, boolean force) throws IOException {
+ ((ProcessImpl)leader).terminateProcessGroup(force);
+ }
+ }
+ );
+ }
+
/**
* Default constructor for Process.
*/
diff --git a/src/java.base/share/classes/java/lang/ProcessBuilder.java b/src/java.base/share/classes/java/lang/ProcessBuilder.java
index bc92ea4ee82c..c72cefd8d954 100644
--- a/src/java.base/share/classes/java/lang/ProcessBuilder.java
+++ b/src/java.base/share/classes/java/lang/ProcessBuilder.java
@@ -35,6 +35,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+// SapMachine 2024-06-12: process group extension
+import jdk.internal.access.JavaLangProcessBuilderAccess;
+import jdk.internal.access.SharedSecrets;
import jdk.internal.event.ProcessStartEvent;
/**
@@ -202,6 +205,19 @@ public final class ProcessBuilder
private boolean redirectErrorStream;
private Redirect[] redirects;
+ // SapMachine 2024-06-12: process group extension
+ private boolean createNewProcessGroupOnSpawn;
+ static {
+ SharedSecrets.setJavaLangProcessBuilderAccess(
+ new JavaLangProcessBuilderAccess() {
+ @Override
+ public void createNewProcessGroupOnSpawn(ProcessBuilder pb, boolean value) {
+ pb.createNewProcessGroupOnSpawn = value;
+ }
+ }
+ );
+ }
+
/**
* Constructs a process builder with the specified operating
* system program and arguments. This constructor does not
@@ -1085,7 +1101,9 @@ private Process start(Redirect[] redirects) throws IOException {
environment,
dir,
redirects,
- redirectErrorStream);
+ redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ createNewProcessGroupOnSpawn);
ProcessStartEvent event = new ProcessStartEvent();
if (event.isEnabled()) {
event.directory = dir;
diff --git a/src/java.base/share/classes/java/nio/Bits.java b/src/java.base/share/classes/java/nio/Bits.java
index 07dd6a918903..8b7830f4331b 100644
--- a/src/java.base/share/classes/java/nio/Bits.java
+++ b/src/java.base/share/classes/java/nio/Bits.java
@@ -68,6 +68,8 @@ static long swap(long x) {
// -- Processor and memory-system properties --
private static int PAGE_SIZE = -1;
+ // SapMachine 2025-02-05: Report error for DirectMemoryOom to exit the VM
+ private static boolean reportErrorOnDirectMemoryOom = Boolean.getBoolean("jdk.nio.reportErrorOnDirectMemoryOom");
static int pageSize() {
if (PAGE_SIZE == -1)
@@ -182,10 +184,15 @@ static void reserveMemory(long size, long cap) {
Thread.sleep(INITIAL_SLEEP << sleeps);
++sleeps; // Only increment if sleep completed.
} else {
- throw new OutOfMemoryError
+ // SapMachine 2025-02-05: Report error for DirectMemoryOom to exit the VM
+ OutOfMemoryError error = new OutOfMemoryError
("Cannot reserve "
+ size + " bytes of direct buffer memory (allocated: "
+ RESERVED_MEMORY.get() + ", limit: " + MAX_MEMORY +")");
+ if (reportErrorOnDirectMemoryOom) {
+ UNSAFE.reportJavaOutOfMemory0(error.getMessage());
+ }
+ throw error;
}
} catch (InterruptedException e) {
interrupted = true;
diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangProcessAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangProcessAccess.java
new file mode 100644
index 000000000000..42978decc398
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangProcessAccess.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.internal.access;
+
+import java.io.IOException;
+
+public interface JavaLangProcessAccess {
+ public void destroyProcessGroup(Process leader, boolean force) throws IOException;
+}
diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangProcessBuilderAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangProcessBuilderAccess.java
new file mode 100644
index 000000000000..12be91dffb15
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangProcessBuilderAccess.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.internal.access;
+
+public interface JavaLangProcessBuilderAccess {
+ public void createNewProcessGroupOnSpawn(ProcessBuilder pb, boolean value);
+}
diff --git a/src/java.base/share/classes/jdk/internal/access/JdkNioZipfsAccess.java b/src/java.base/share/classes/jdk/internal/access/JdkNioZipfsAccess.java
new file mode 100644
index 000000000000..5c71d8e0f121
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/access/JdkNioZipfsAccess.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.internal.access;
+
+import java.nio.file.Path;
+
+/**
+ * SharedSecrets interface used for the access from jdk.nio.zipfs
+ */
+
+public interface JdkNioZipfsAccess {
+
+ public boolean isSymbolicLink(Path path);
+}
diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java
index b0a71529fa7b..c02f76f28a91 100644
--- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java
+++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java
@@ -80,6 +80,9 @@ public class SharedSecrets {
@Stable private static JavaLangAccess javaLangAccess;
@Stable private static JavaLangInvokeAccess javaLangInvokeAccess;
@Stable private static JavaLangModuleAccess javaLangModuleAccess;
+ // SapMachine 2024-06-12: process group extension
+ @Stable private static JavaLangProcessBuilderAccess javaLangProcessBuilderAccess;
+ @Stable private static JavaLangProcessAccess javaLangProcessAccess;
@Stable private static JavaLangRefAccess javaLangRefAccess;
@Stable private static JavaLangReflectAccess javaLangReflectAccess;
@Stable private static JavaIOAccess javaIOAccess;
@@ -106,6 +109,8 @@ public class SharedSecrets {
@Stable private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
@Stable private static JavaxCryptoSpecAccess javaxCryptoSpecAccess;
@Stable private static JavaxSecurityAccess javaxSecurityAccess;
+ // SapMachine 2026-04-14: Support for symlink detection in zipfs.
+ @Stable private static JdkNioZipfsAccess jdkNioZipfsAccess;
public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) {
javaUtilCollectionAccess = juca;
@@ -201,6 +206,36 @@ public static JavaLangModuleAccess getJavaLangModuleAccess() {
return access;
}
+ // SapMachine 2024-06-12: process group extension
+ public static void setJavaLangProcessBuilderAccess(JavaLangProcessBuilderAccess jlpba) {
+ javaLangProcessBuilderAccess = jlpba;
+ }
+
+ // SapMachine 2024-06-12: process group extension
+ public static JavaLangProcessBuilderAccess getJavaLangProcessBuilderAccess() {
+ var access = javaLangProcessBuilderAccess;
+ if (access == null) {
+ ensureClassInitialized(ProcessBuilder.class);
+ access = javaLangProcessBuilderAccess;
+ }
+ return access;
+ }
+
+ // SapMachine 2024-07-01: process group extension
+ public static void setJavaLangProcessAccess(JavaLangProcessAccess jlpa) {
+ javaLangProcessAccess = jlpa;
+ }
+
+ // SapMachine 2024-07-01: process group extension
+ public static JavaLangProcessAccess getJavaLangProcessAccess() {
+ var access = javaLangProcessAccess;
+ if (access == null) {
+ ensureClassInitialized(Process.class);
+ access = javaLangProcessAccess;
+ }
+ return access;
+ }
+
public static void setJavaLangRefAccess(JavaLangRefAccess jlra) {
javaLangRefAccess = jlra;
}
@@ -504,4 +539,14 @@ private static void ensureClassInitialized(Class> c) {
MethodHandles.lookup().ensureInitialized(c);
} catch (IllegalAccessException e) {}
}
+
+ // SapMachine 2026-04-14
+ public static void setJdkNioZipfsAccess(JdkNioZipfsAccess access) {
+ jdkNioZipfsAccess = access;
+ }
+
+ // SapMachine 2026-04-14
+ public static JdkNioZipfsAccess getJdkNioZipfsAccess() {
+ return jdkNioZipfsAccess;
+ }
}
diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
index 016566ae6592..997000506a47 100644
--- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
+++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
@@ -3879,6 +3879,8 @@ private void putShortParts(Object o, long offset, byte i0, byte i1) {
private native int arrayBaseOffset0(Class> arrayClass); // public version returns long to promote correct arithmetic
private native int arrayIndexScale0(Class> arrayClass);
private native int getLoadAverage0(double[] loadavg, int nelems);
+ // SapMachine 2025-02-05: Report error for DirectMemoryOom to exit the VM
+ public native void reportJavaOutOfMemory0(String message);
/**
diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java
index 32afbc111003..c3407f5f784b 100644
--- a/src/java.base/share/classes/module-info.java
+++ b/src/java.base/share/classes/module-info.java
@@ -166,6 +166,10 @@
jdk.jfr,
jdk.management,
jdk.net,
+ // SapMachine 2024-06-12: process group extension
+ jdk.sapext,
+ // SapMachine 2026-04-13: symlink support for zipfs entries in sapext.
+ jdk.zipfs,
jdk.sctp,
jdk.crypto.cryptoki;
exports jdk.internal.classfile.components to
diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
index 41e1e3d0003c..2d88538363f7 100644
--- a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
+++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
@@ -76,6 +76,9 @@ static int getUserKeepAliveSeconds(String type) {
userKeepAliveProxy = getUserKeepAliveSeconds("proxy");
}
+ // SapMachine 2024-04-12: Provide additional key field for KeepAliveCache entries (for FRUN)
+ public static final ThreadLocal connectionID = new ThreadLocal<>();
+
/* maximum # keep-alive connections to maintain at once
* This should be 2 by the HTTP spec, but because we don't support pipe-lining
* a larger value is more appropriate. So we now set a default of 5, and the value
@@ -353,6 +356,9 @@ private void readObject(ObjectInputStream stream)
}
class KeepAliveKey {
+ // SapMachine 2024-04-12: Provide additional key field for KeepAliveCache entries (for FRUN)
+ private static boolean useKeyExtension = Boolean.getBoolean("com.sap.jvm.UseHttpKeepAliveCacheKeyExtension");
+
private final String protocol;
private final String host;
private final int port;
@@ -364,10 +370,25 @@ class KeepAliveKey {
* @param url the URL containing the protocol, host and port information
*/
public KeepAliveKey(URL url, Object obj) {
+ // SapMachine 2024-04-12: Provide additional key field for KeepAliveCache entries (for FRUN)
+ final record KeyObject(String connectionID, Object obj) {
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other instanceof KeyObject ok) {
+ return (connectionID == null ? ok.connectionID == null : connectionID.equals(ok.connectionID)) && obj == ok.obj;
+ } else {
+ return false;
+ }
+ }
+ };
+
this.protocol = url.getProtocol();
this.host = url.getHost();
this.port = url.getPort();
- this.obj = obj;
+ // SapMachine 2024-04-12: Provide additional key field for KeepAliveCache entries (for FRUN)
+ this.obj = useKeyExtension ? new KeyObject(KeepAliveCache.connectionID.get(), obj) : obj;
}
/**
@@ -381,7 +402,8 @@ public boolean equals(Object obj) {
return host.equals(kae.host)
&& (port == kae.port)
&& protocol.equals(kae.protocol)
- && this.obj == kae.obj;
+ // SapMachine 2024-04-12: Provide additional key field for KeepAliveCache entries (for FRUN)
+ && useKeyExtension ? this.obj.equals(kae.obj) : this.obj == kae.obj;
}
/**
diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security
index 976604b5cbcc..fb21ac626f39 100644
--- a/src/java.base/share/conf/security/java.security
+++ b/src/java.base/share/conf/security/java.security
@@ -1352,7 +1352,7 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep
#keystore.pkcs12.macIterationCount = 10000
#
-# Enhanced exception message information
+# Enhanced exception message information (see SapMachine comment below!!)
#
# Exception messages may include potentially sensitive information such as file
# names, host names, or port numbers. By default, socket related exceptions
@@ -1394,7 +1394,13 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep
# restricted setting with all categories disabled. The following is the default
# (out of the box) setting, meaning these categories are not restricted.
#
-jdk.includeInExceptions=hostInfoExclSocket
+#jdk.includeInExceptions=hostInfoExclSocket
+#
+# The SapMachine distribution opens up slightly more information in Exceptions,
+# compared to default OpenJDK. For hostInfo and jar file handling the potential
+# benefit of enhanced information in exception messages outweighs the risk and
+# helps to support our use cases.
+jdk.includeInExceptions=hostInfo,jar
#
# Disabled mechanisms for the Simple Authentication and Security Layer (SASL)
diff --git a/src/java.base/share/data/cacerts/sapglobalrootca b/src/java.base/share/data/cacerts/sapglobalrootca
new file mode 100644
index 000000000000..82e7cedbd337
--- /dev/null
+++ b/src/java.base/share/data/cacerts/sapglobalrootca
@@ -0,0 +1,43 @@
+Owner: CN=SAP Global Root CA, O=SAP AG, L=Walldorf, C=DE
+Issuer: CN=SAP Global Root CA, O=SAP AG, L=Walldorf, C=DE
+Serial number: 5d03d93d31615d8f488b3970c78f1b99
+Valid from: Thu Apr 26 15:41:55 GMT 2012 until: Mon Apr 26 15:46:27 GMT 2032
+Signature algorithm name: SHA256withRSA
+Subject Public Key Algorithm: 4096-bit RSA key
+Version: 3
+-----BEGIN CERTIFICATE-----
+MIIGTDCCBDSgAwIBAgIQXQPZPTFhXY9Iizlwx48bmTANBgkqhkiG9w0BAQsFADBO
+MQswCQYDVQQGEwJERTERMA8GA1UEBwwIV2FsbGRvcmYxDzANBgNVBAoMBlNBUCBB
+RzEbMBkGA1UEAwwSU0FQIEdsb2JhbCBSb290IENBMB4XDTEyMDQyNjE1NDE1NVoX
+DTMyMDQyNjE1NDYyN1owTjELMAkGA1UEBhMCREUxETAPBgNVBAcMCFdhbGxkb3Jm
+MQ8wDQYDVQQKDAZTQVAgQUcxGzAZBgNVBAMMElNBUCBHbG9iYWwgUm9vdCBDQTCC
+AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOrxJKFFA1eTrZg1Ux8ax6n/
+LQRHZlgLc2FZpfyAgwvkt71wLkPLiTOaRb3Bd1dyydpKcwJLy0dzGkunzNkPRSFz
+bKy2IPS0RS45hUCCPzhGnqQM6TcDYWeWpSUvygqujgb/cAG0mSJpvzAD3SMDQ+VJ
+Az5Ryq4IrP7LkfCb63LKZxLsHEkEcNKoGPsSsd4LTwuEIyM3ZHcCoA97m6hvgLWV
+GLzLIQMEblkswqX29z7JZH+zJopoqZB6eEogE2YpExkw52PufytEslDY3dyVubjp
+GlvD4T03F2zm6CYleMwgWbATLVYvk2I9WfqPAP+ln2IU9DZzegSMTWHCE+jizaiq
+b5f5s7m8f+cz7ndHSrz8KD/S9iNdWpuSlknHDrh+3lFTX/uWNBRs5mC/cdejcqS1
+v6erflyIfqPWWO6PxhIs49NL9Lix3ou6opJo+m8K757T5uP/rQ9KYALIXvl2uFP7
+0CqI+VGfossMlSXa1keagraW8qfplz6ffeSJQWO/+zifbfsf0tzUAC72zBuO0qvN
+E7rSbqAfpav/o010nKP132gbkb4uOkUfZwCuvZjA8ddsQ4udIBRj0hQlqnPLJOR1
+PImrAFC3PW3NgaDEo9QAJBEp5jEJmQghNvEsmzXgABebwLdI9u0VrDz4mSb6TYQC
+XTUaSnH3zvwAv8oMx7q7AgMBAAGjggEkMIIBIDAOBgNVHQ8BAf8EBAMCAQYwEgYD
+VR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUg8dB/Q4mTynBuHmOhnrhv7XXagMw
+gdoGA1UdIASB0jCBzzCBzAYKKwYBBAGFNgRkATCBvTAmBggrBgEFBQcCARYaaHR0
+cDovL3d3dy5wa2kuY28uc2FwLmNvbS8wgZIGCCsGAQUFBwICMIGFHoGCAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAFAAbwBsAGkAYwB5ACAAYQBuAGQAIABDAGUAcgB0
+AGkAZgBpAGMAYQB0AGkAbwBuACAAUAByAGEAYwB0AGkAYwBlACAAUwB0AGEAdABl
+AG0AZQBuAHQAIABvAGYAIABTAEEAUAAgAEEARzANBgkqhkiG9w0BAQsFAAOCAgEA
+0HpCIaC36me6ShB3oHDexA2a3UFcU149nZTABPKT+yUCnCQPzvK/6nJUc5I4xPfv
+2Q8cIlJjPNRoh9vNSF7OZGRmWQOFFrPWeqX5JA7HQPsRVURjJMeYgZWMpy4t1Tof
+lF13u6OY6xV6A5kQZIISFj/dOYLT3+O7wME5SItL+YsNh6BToNU0xAZt71Z8JNdY
+VJb2xSPMzn6bNXY8ioGzHlVxfEvzMqebV0KY7BTXR3y/Mh+v/RjXGmvZU6L/gnU7
+8mTRPgekYKY8JX2CXTqgfuW6QSnJ+88bHHMhMP7nPwv+YkPcsvCPBSY08ykzFATw
+SNoKP1/QFtERVUwrUXt3Cufz9huVysiy23dEyfAglgCCRWA+ZlaaXfieKkUWCJaE
+Kw/2Jqz02HDc7uXkFLS1BMYjr3WjShg1a+ulYvrBhNtseRoZT833SStlS/jzZ8Bi
+c1dt7UOiIZCGUIODfcZhO8l4mtjh034hdARLF0sUZhkVlosHPml5rlxh+qn8yJiJ
+GJ7CUQtNCDBVGksVlwew/+XnesITxrDjUMu+2297at7wjBwCnO93zr1/wsx1e2Um
+Xn+IfM6K/pbDar/y6uI9rHlyWu4iJ6cg7DAPJ2CCklw/YHJXhDHGwheO/qSrKtgz
+PGHZoN9jcvvvWDLUGtJkEotMgdFpEA2XWR83H4fVFVc=
+-----END CERTIFICATE-----
diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md
index 290f729c0ca8..abf262c616bd 100644
--- a/src/java.base/share/man/java.md
+++ b/src/java.base/share/man/java.md
@@ -20,6 +20,8 @@
# or visit www.oracle.com if you need additional information or have any
# questions.
#
+# SapMachine 2025-12-10 Changed description of UseTransparentHugePages which is
+# tested by us.
title: 'JAVA(1) JDK @@VERSION_SHORT@@ | JDK Commands'
date: @@COPYRIGHT_YEAR@@
@@ -1576,9 +1578,11 @@ These `java` options control the runtime behavior of the Java HotSpot VM.
[`-XX:+UseTransparentHugePages`]{#-XX__UseTransparentHugePages}
: **Linux only:** Enables the use of large pages that can dynamically grow or
- shrink. This option is disabled by default. You may encounter performance
- problems with transparent huge pages as the OS moves other pages around to
- create huge pages; this option is made available for experimentation.
+ shrink. The VM may enable this option by default in some environments. You
+ may encounter performance problems with transparent huge pages as the OS
+ moves other pages around to create huge pages. You may encounter other
+ performance problems when only small pages are used as the OS needs to
+ maintain a large page table and address translations may become slow.
[`-XX:+AllowUserSignalHandlers`]{#-XX__AllowUserSignalHandlers}
: **Non-Windows:** Enables installation of signal handlers by the application. By default,
diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c
index 4c3b503b08a0..5bd9a2b97775 100644
--- a/src/java.base/share/native/libjli/java.c
+++ b/src/java.base/share/native/libjli/java.c
@@ -916,6 +916,64 @@ CheckJvmType(int *pargc, char ***argv, jboolean speculative) {
return jvmtype;
}
+// SapMachine 2023-09-18: new malloc trace
+jboolean ShouldPreloadLibMallocHooks(int argc, char **argv) {
+#if defined(__APPLE__) || defined(LINUX)
+ jboolean uses_malloc_trace = JNI_FALSE;
+#if defined(__APPLE__)
+ char const* env_name = "DYLD_INSERT_LIBRARIES";
+ char const* libpath = "libmallochooks.dylib";
+#else
+ char const* env_name = "LD_PRELOAD";
+ char const* libpath = "libmallochooks.so";
+#endif
+
+ char const* old_env = getenv(env_name);
+
+ /* Check if we have already preloaded the lib. We don't catch
+ all possble ways in which it could have been added (e.g. symlinks),
+ but this should be no problem. */
+ if (old_env != NULL) {
+ size_t len = JLI_StrLen(libpath);
+ char const* pos = old_env;
+
+ while ((pos = JLI_StrStr(pos, libpath)) != NULL) {
+ if ((pos[len] == ':') || (pos[len] == '\0')) {
+ if ((pos == old_env) || (pos[-1] == '/') || (pos[-1] == ':')) {
+ // Already preloaded, so we don't have to.
+ return JNI_FALSE;
+ }
+ }
+ }
+ }
+
+ for (int i = 1; i < argc; i++) {
+ char const* arg = argv[i];
+
+ if ((JLI_StrCmp("-XX:+UseMallocHooks", arg) == 0) ||
+ (JLI_StrCmp("-J-XX:+UseMallocHooks", arg) == 0)) {
+ uses_malloc_trace = JNI_TRUE;
+ continue;
+ }
+
+ if (!IsJavaArgs()) {
+ if (IsWhiteSpaceOption(arg)) {
+ i += 1;
+ continue;
+ }
+
+ if (arg[0] != '-') {
+ break;
+ }
+ }
+ }
+
+ return uses_malloc_trace;
+#endif
+
+ return JNI_FALSE;
+}
+
/* copied from HotSpot function "atomll()" */
static int
parse_size(const char *s, jlong *result) {
diff --git a/src/java.base/share/native/libjli/java.h b/src/java.base/share/native/libjli/java.h
index 5586cef5cbc8..354fea752515 100644
--- a/src/java.base/share/native/libjli/java.h
+++ b/src/java.base/share/native/libjli/java.h
@@ -156,6 +156,9 @@ void AddOption(char *str, void *info);
jboolean IsWhiteSpaceOption(const char* name);
jlong CurrentTimeMicros();
+// SapMachine 2023-09-18: new malloc trace
+jboolean ShouldPreloadLibMallocHooks(int argc, char **argv);
+
// Utility function defined in args.c
int isTerminalOpt(char *arg);
jboolean IsJavaw();
diff --git a/src/java.base/unix/classes/java/lang/ProcessImpl.java b/src/java.base/unix/classes/java/lang/ProcessImpl.java
index d9a4547848f0..04c3fe8d338f 100644
--- a/src/java.base/unix/classes/java/lang/ProcessImpl.java
+++ b/src/java.base/unix/classes/java/lang/ProcessImpl.java
@@ -134,7 +134,9 @@ static Process start(String[] cmdarray,
java.util.Map environment,
String dir,
ProcessBuilder.Redirect[] redirects,
- boolean redirectErrorStream)
+ boolean redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ boolean createNewProcessGroupOnSpawn)
throws IOException
{
assert cmdarray != null && cmdarray.length > 0;
@@ -217,7 +219,9 @@ static Process start(String[] cmdarray,
toCString(dir),
std_fds,
forceNullOutputStream,
- redirectErrorStream);
+ redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ createNewProcessGroupOnSpawn);
if (redirects != null) {
// Copy the fd's if they are to be redirected to another process
if (std_fds[0] >= 0 &&
@@ -270,7 +274,9 @@ private native int forkAndExec(int mode, byte[] helperpath,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
- boolean redirectErrorStream)
+ boolean redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ boolean createNewProcessGroupOnSpawn)
throws IOException;
private ProcessImpl(final byte[] prog,
@@ -279,7 +285,9 @@ private ProcessImpl(final byte[] prog,
final byte[] dir,
final int[] fds,
final boolean forceNullOutputStream,
- final boolean redirectErrorStream)
+ final boolean redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ final boolean createNewProcessGroupOnSpawn)
throws IOException {
pid = forkAndExec(launchMechanism.ordinal() + 1,
@@ -289,7 +297,9 @@ private ProcessImpl(final byte[] prog,
envBlock, envc,
dir,
fds,
- redirectErrorStream);
+ redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ createNewProcessGroupOnSpawn);
processHandle = ProcessHandleImpl.getInternal(pid);
initStreams(fds, forceNullOutputStream);
@@ -539,6 +549,16 @@ public String toString() {
private static native void init();
+ // SapMachine 2024-07-01: process group extension
+ private static native int terminateProcessGroup(long pid, boolean force);
+
+ void terminateProcessGroup(boolean force) throws IOException {
+ int rc = terminateProcessGroup(pid, force);
+ if (rc != 0) {
+ throw new IOException("Failed to kill process group (errno = " + rc + ")");
+ }
+ }
+
static {
init();
}
diff --git a/src/java.base/unix/native/libjava/ProcessImpl_md.c b/src/java.base/unix/native/libjava/ProcessImpl_md.c
index 69af948d2da6..09be694f1b79 100644
--- a/src/java.base/unix/native/libjava/ProcessImpl_md.c
+++ b/src/java.base/unix/native/libjava/ProcessImpl_md.c
@@ -46,6 +46,8 @@
#include
#include
#include
+/* SapMachine 2024-06-12: process group extension */
+#include
#include
#include "childproc.h"
@@ -692,7 +694,9 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
jbyteArray envBlock, jint envc,
jbyteArray dir,
jintArray std_fds,
- jboolean redirectErrorStream)
+ jboolean redirectErrorStream,
+ /* SapMachine 2024-06-12: process group extension */
+ jboolean createNewProcessGroupOnSpawn)
{
int resultPid = -1;
int in[2], out[2], err[2], fail[2], childenv[2];
@@ -764,6 +768,9 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
c->redirectErrorStream = redirectErrorStream;
c->mode = mode;
+ /* SapMachine 2024-06-12: process group extension */
+ c->createNewProcessGroupOnSpawn = createNewProcessGroupOnSpawn;
+
/* In posix_spawn mode, require the child process to signal aliveness
* right after it comes up. This is because there are implementations of
* posix_spawn() which do not report failed exec()s back to the caller
@@ -895,3 +902,11 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
closeSafely(err[0]); err[0] = -1;
goto Finally;
}
+
+/* SapMachine 2024-06-12: process group extension */
+JNIEXPORT jint JNICALL
+Java_java_lang_ProcessImpl_terminateProcessGroup(JNIEnv *env, jclass ignore, jlong pid_of_leader, jboolean force)
+{
+ int rc = kill(-(pid_t)pid_of_leader, force ? SIGKILL : SIGTERM);
+ return ((rc == -1 && errno != 0) ? errno : rc);
+}
diff --git a/src/java.base/unix/native/libjava/childproc.c b/src/java.base/unix/native/libjava/childproc.c
index 6bc15dfb40c4..234195a20edb 100644
--- a/src/java.base/unix/native/libjava/childproc.c
+++ b/src/java.base/unix/native/libjava/childproc.c
@@ -386,6 +386,15 @@ childProcess(void *arg)
/* error information for WhyCantJohnnyExec */
errcode_t errcode;
+ /* SapMachine 2024-06-12: process group extension */
+ if (p->createNewProcessGroupOnSpawn) {
+ /* Make this process leader of its own process group (see setpgid(2)). */
+ if (setpgid(0, 0) != 0) {
+ buildErrorCode(&errcode, ESTEP_SETPGID_FAIL, 0, errno);
+ goto WhyCantJohnnyExec;
+ }
+ }
+
/* Child shall signal aliveness to parent at the very first
* moment. */
if (p->sendAlivePing && !sendAlivePing(fail_pipe_fd)) {
diff --git a/src/java.base/unix/native/libjava/childproc.h b/src/java.base/unix/native/libjava/childproc.h
index 0b02df7f3ddf..ef11a43c569e 100644
--- a/src/java.base/unix/native/libjava/childproc.h
+++ b/src/java.base/unix/native/libjava/childproc.h
@@ -97,6 +97,8 @@ typedef struct _ChildStuff
const char *pdir;
int redirectErrorStream;
int sendAlivePing;
+ /* SapMachine 2024-06-12: process group extension */
+ int createNewProcessGroupOnSpawn;
} ChildStuff;
/* following used in addition when mode is SPAWN */
diff --git a/src/java.base/unix/native/libjava/childproc_errorcodes.h b/src/java.base/unix/native/libjava/childproc_errorcodes.h
index 8379db4ad2b8..bbf37fa21b33 100644
--- a/src/java.base/unix/native/libjava/childproc_errorcodes.h
+++ b/src/java.base/unix/native/libjava/childproc_errorcodes.h
@@ -109,6 +109,9 @@ bool sendAlivePing(int fd);
/* Failed to change signal disposition for SIGPIPE to default */
#define ESTEP_SET_SIGPIPE 20
+/* SapMachine 2024-06-12: process group extension */
+#define ESTEP_SETPGID_FAIL 29
+
/* Expand if needed ... */
/* All modes: exec() failed */
diff --git a/src/java.base/unix/native/libjli/java_md.c b/src/java.base/unix/native/libjli/java_md.c
index a75795af5804..547ccc2c92a2 100644
--- a/src/java.base/unix/native/libjli/java_md.c
+++ b/src/java.base/unix/native/libjli/java_md.c
@@ -334,6 +334,36 @@ CreateExecutionEnvironment(int *pargc, char ***pargv,
mustsetenv = RequiresSetenv(jvmpath);
JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");
+ /* SapMachine 2023-09-18: new malloc trace */
+#if defined(LINUX)
+ if (ShouldPreloadLibMallocHooks(*pargc, *pargv) == JNI_TRUE) {
+ char const* env_name = "LD_PRELOAD";
+ char const* libpath = "/lib/libmallochooks.so";
+ char const* old_env = getenv(env_name);
+ char* env_entry;
+
+ if ((old_env == NULL) || (old_env[0] == '0')) {
+ size_t size = JLI_StrLen(jdkroot) + JLI_StrLen(libpath) + JLI_StrLen(env_name) + 2;
+ env_entry = JLI_MemAlloc(size);
+
+ snprintf(env_entry, size, "%s=%s%s", env_name, jdkroot, libpath);
+ } else {
+ size_t size = JLI_StrLen(jdkroot) + JLI_StrLen(libpath) + JLI_StrLen(env_name) +
+ 3 + JLI_StrLen(old_env);
+ env_entry = JLI_MemAlloc(size);
+
+ snprintf(env_entry, size, "%s=%s%s:%s", env_name, jdkroot, libpath, old_env);
+ }
+
+ if (putenv(env_entry) == 0) {
+ /* We exec here, since the code below might return without exec.
+ * This can lead to double exec in the worst case, but we don't care.
+ */
+ execve(execname, argv, environ);
+ }
+ }
+#endif
+
if (mustsetenv == JNI_FALSE) {
return;
}
diff --git a/src/java.base/unix/native/libmallochooks/mallochooks.c b/src/java.base/unix/native/libmallochooks/mallochooks.c
new file mode 100644
index 000000000000..9090a658c693
--- /dev/null
+++ b/src/java.base/unix/native/libmallochooks/mallochooks.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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.
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(__APPLE__)
+#include
+#else
+#include
+#endif
+
+#include "mallochooks.h"
+
+// The log level. 0 is none, 1 is basic logging.
+#define LOG_LEVEL 0
+
+// If > 0 we sync after each write.
+#define SYNC_WRITE 0
+
+void write_safe(int fd, char const* buf, size_t len) {
+ int errno_backup = errno;
+ size_t left = len;
+ ssize_t result;
+
+ while ((result = write(fd, buf, left)) > 0) {
+ buf += result;
+ left -= result;
+ }
+
+#if SYNC_WRITE > 0
+ fsync(fd);
+#endif
+
+ errno = errno_backup;
+}
+
+static void print_error(char const* msg) {
+ write_safe(2, msg, strlen(msg));
+}
+
+
+static void unexpected_call() {
+ print_error("Uninitialized function called. libmallochooks.so must be the first preloaded library.\n");
+ exit(1);
+}
+
+// The tag for malloc functions which should be loaded by dl_sym.
+#define LOAD_DYNAMIC ((void*) unexpected_call)
+
+#if defined(__APPLE__)
+
+static real_malloc_funcs_t impl = {
+ malloc,
+ calloc,
+ realloc,
+ free,
+ posix_memalign,
+ NULL,
+ NULL,
+ valloc,
+ NULL,
+ (malloc_size_func_t*) malloc_size
+};
+
+#define REPLACE_NAME(x) x##_interpose
+
+#elif defined(__GLIBC__)
+
+void* __libc_malloc(size_t size);
+void* __libc_calloc(size_t elems, size_t size);
+void* __libc_realloc(void* ptr, size_t size);
+void __libc_free(void* ptr);
+void* __libc_memalign(size_t align, size_t size);
+void* __libc_valloc(size_t size);
+void* __libc_pvalloc(size_t size);
+
+static real_malloc_funcs_t impl = {
+ __libc_malloc,
+ __libc_calloc,
+ __libc_realloc,
+ __libc_free,
+ (posix_memalign_func_t*) LOAD_DYNAMIC,
+ __libc_memalign,
+ (aligned_alloc_func_t*) LOAD_DYNAMIC,
+ __libc_valloc,
+ __libc_pvalloc,
+ malloc_usable_size
+};
+
+#elif defined(MUSL_LIBC)
+
+static void* calloc_by_malloc(size_t elems, size_t size);
+static int posix_memalign_by_aligned_alloc(void** ptr, size_t align, size_t size);
+static void* memalign_by_aligned_alloc(size_t align, size_t size);
+
+#define NEEDS_MALLOC_FALLBACK
+
+static real_malloc_funcs_t impl = {
+ (malloc_func_t*) LOAD_DYNAMIC,
+ calloc_by_malloc,
+ (realloc_func_t*) LOAD_DYNAMIC,
+ (free_func_t*) LOAD_DYNAMIC,
+ posix_memalign_by_aligned_alloc,
+ memalign_by_aligned_alloc,
+ (aligned_alloc_func_t*) LOAD_DYNAMIC,
+ NULL,
+ NULL,
+ malloc_usable_size
+};
+
+/* musl calloc would call the redirected malloc, so we call the right malloc here. */
+static void* calloc_by_malloc(size_t elems, size_t size) {
+ /* Check for overflow */
+ if (size > 0 && (elems > ((size_t) -1) / size)) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ void* result = impl.malloc(elems * size);
+
+ if (result != NULL) {
+ bzero(result, elems * size);
+ }
+
+ return result;
+}
+
+/* musl posix_memalign would call the redirected aligned_alloc, so we call the right aligned_alloc here. */
+static int posix_memalign_by_aligned_alloc(void** ptr, size_t align, size_t size) {
+ if (align < sizeof(void *)) {
+ return EINVAL;
+ }
+
+ void* result = impl.aligned_alloc(align, size);
+
+ if (ptr != NULL) {
+ *ptr = result;
+
+ return 0;
+ }
+
+ return errno;
+}
+
+/* musl memalign would call the redirected aligned_alloc, so we call the right aligned_alloc here. */
+static void* memalign_by_aligned_alloc(size_t align, size_t size) {
+ return impl.aligned_alloc(align, size);
+}
+
+#else
+#error "Unexpected platform"
+#endif
+
+#if LOG_LEVEL > 0
+
+static void print(char const* str);
+static void print_ptr(void* ptr);
+static void print_size(size_t size);
+
+#else
+
+#define print_ptr(x)
+#define print_size(x)
+#define print(x)
+
+#endif
+
+#ifndef REPLACE_NAME
+#define REPLACE_NAME(x) x
+#endif
+
+static void assign_function(void** dest, char const* symbol) {
+ if (*dest != LOAD_DYNAMIC) {
+ print("Don't need to load '");
+ print(symbol);
+ print("'\n");
+
+ return;
+ }
+
+ print("Resolving '");
+ print(symbol);
+ print("'\n");
+
+ *dest = dlsym(RTLD_NEXT, symbol);
+
+ if (*dest == NULL) {
+ print_error(symbol);
+ print_error(" not found!\n");
+ exit(1);
+ }
+
+ print("Found at ");
+ print_ptr(*dest);
+ print("\n");
+}
+
+#if defined(NEEDS_MALLOC_FALLBACK)
+static char fallback_buffer[1024 * 1024];
+static char* fallback_buffer_pos = fallback_buffer;
+static char* fallback_buffer_end = &fallback_buffer[sizeof(fallback_buffer)];
+static int still_needs_malloc_fallback = 1;
+#endif
+
+#define LIB_INIT __attribute__((constructor))
+#define EXPORT __attribute__((visibility("default")))
+
+static void LIB_INIT init(void) {
+ assign_function((void**) &impl.malloc, "malloc");
+ assign_function((void**) &impl.calloc, "calloc");
+ assign_function((void**) &impl.realloc, "realloc");
+ assign_function((void**) &impl.free, "free");
+#if defined(NEEDS_MALLOC_FALLBACK)
+ still_needs_malloc_fallback = 0;
+#endif
+ assign_function((void**) &impl.memalign, "memalign");
+ assign_function((void**) &impl.posix_memalign, "posix_memalign");
+ assign_function((void**) &impl.aligned_alloc, "aligned_alloc");
+ assign_function((void**) &impl.valloc, "valloc");
+ assign_function((void**) &impl.pvalloc, "pvalloc");
+}
+
+static registered_hooks_t empty_registered_hooks = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static registered_hooks_t* volatile registered_hooks = &empty_registered_hooks;
+
+EXPORT registered_hooks_t* malloc_hooks_register_hooks(registered_hooks_t* hooks) {
+ registered_hooks_t* old_hooks = registered_hooks;
+
+ if (hooks == NULL) {
+ print("Deregistered hooks\n");
+ registered_hooks = &empty_registered_hooks;
+ } else {
+ print("Registered hooks\n");
+ registered_hooks = hooks;
+ }
+
+ return old_hooks == &empty_registered_hooks ? NULL : old_hooks;
+}
+
+EXPORT registered_hooks_t* malloc_hooks_active_hooks() {
+ if (registered_hooks == &empty_registered_hooks) {
+ return NULL;
+ }
+
+ return (registered_hooks_t*) registered_hooks;
+}
+
+EXPORT real_malloc_funcs_t* malloc_hooks_get_real_malloc_funcs() {
+ return &impl;
+}
+
+#if LOG_LEVEL > 0
+
+#define LOG_FUNC(func) \
+ print(#func);
+
+#define LOG_ALIGN(align) \
+ print(" alignment "); \
+ print_size(align);
+
+#define LOG_PTR(ptr) \
+ print(" "); \
+ print_ptr(ptr);
+
+#define LOG_PTR_WITH_SIZE(ptr) \
+ LOG_PTR(ptr); \
+ if (ptr != NULL) { \
+ size_t size = impl.malloc_size(ptr); \
+ if (size > 0) { \
+ print(" (size "); \
+ print_size(size); \
+ print(")"); \
+ } \
+ }
+
+#define LOG_ELEMS(elems) \
+ print(" #elems "); \
+ print_size(elems);
+
+#define LOG_SIZE(size) \
+ print(" size "); \
+ print_size(size);
+
+#define LOG_ALLOCATION_RESULT(result) \
+ if (result == NULL) { \
+ print(" failed with errno "); \
+ print_size(errno); \
+ } else { \
+ print(" allocated at"); \
+ LOG_PTR_WITH_SIZE(result); \
+ }
+
+#define LOG_RESULT(result) \
+ print(" result "); \
+ print_size(result);
+
+#define LOG_HOOK \
+ print(hook ? " with hook\n" : " without hook\n");
+
+#else
+
+#define LOG_FUNC(func)
+#define LOG_ALIGN(align)
+#define LOG_PTR(ptr)
+#define LOG_PTR_WITH_SIZE(ptr)
+#define LOG_ELEMS(elems)
+#define LOG_SIZE(size)
+#define LOG_ALLOCATION_RESULT(result)
+#define LOG_RESULT(result)
+#define LOG_HOOK
+
+#endif
+
+EXPORT void* REPLACE_NAME(malloc)(size_t size) {
+ malloc_hook_t* hook = registered_hooks->malloc_hook;
+ void* result;
+
+#if defined(NEEDS_MALLOC_FALLBACK)
+ if (still_needs_malloc_fallback) {
+ // Align to 16 byte and add 16 bytes for the header.
+ size_t real_size = 16 + ((size - 1) | 15) + 1;
+
+ if (fallback_buffer_pos + real_size >= fallback_buffer_end) {
+ return NULL;
+ }
+
+ void* result = fallback_buffer_pos + 16;
+ ((size_t*) result)[-1] = real_size;
+ fallback_buffer_pos += real_size;
+
+ return result;
+ }
+#endif
+
+ LOG_FUNC(malloc);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(size, __builtin_return_address(0));
+ } else {
+ result = impl.malloc(size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+
+EXPORT void* REPLACE_NAME(calloc)(size_t elems, size_t size) {
+ calloc_hook_t* hook = registered_hooks->calloc_hook;
+ void* result;
+
+ LOG_FUNC(calloc);
+ LOG_ELEMS(elems);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(elems, size, __builtin_return_address(0));
+ } else {
+ result = impl.calloc(elems, size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+
+EXPORT void* REPLACE_NAME(realloc)(void* ptr, size_t size) {
+ realloc_hook_t* hook = registered_hooks->realloc_hook;
+ void* result;
+
+ LOG_FUNC(realloc);
+ LOG_PTR_WITH_SIZE(ptr);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(ptr, size, __builtin_return_address(0));
+ } else {
+ result = impl.realloc(ptr, size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+
+EXPORT void REPLACE_NAME(free)(void* ptr) {
+ free_hook_t* hook = registered_hooks->free_hook;
+
+#if defined(NEEDS_MALLOC_FALLBACK)
+ if (((char*) ptr >= fallback_buffer) && ((char*) ptr < fallback_buffer_end)) {
+ // This memory was allocated by the fallback malloc.
+ if (((char*) ptr) + ((size_t*) ptr)[-1] - 16 == fallback_buffer_pos) {
+ // We can undo the last allocation.
+ fallback_buffer_pos -= ((size_t*) ptr)[-1];
+ }
+
+ return;
+ }
+#endif
+
+ LOG_FUNC(free);
+ LOG_PTR_WITH_SIZE(ptr);
+
+ if (hook != NULL) {
+ hook(ptr, __builtin_return_address(0));
+ } else {
+ impl.free(ptr);
+ }
+
+ LOG_HOOK;
+}
+
+EXPORT int REPLACE_NAME(posix_memalign)(void** ptr, size_t align, size_t size) {
+ posix_memalign_hook_t* hook = registered_hooks->posix_memalign_hook;
+ int result;
+
+ LOG_FUNC(posix_memalign);
+ LOG_ALIGN(align);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(ptr, align, size, __builtin_return_address(0));
+ } else {
+ result = impl.posix_memalign(ptr, align, size);
+ }
+
+ LOG_ALLOCATION_RESULT(*ptr);
+ LOG_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+
+#if !defined(__APPLE__)
+EXPORT void* REPLACE_NAME(memalign)(size_t align, size_t size) {
+ memalign_hook_t* hook = registered_hooks->memalign_hook;
+ void* result;
+
+ LOG_FUNC(memalign);
+ LOG_ALIGN(align);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(align, size, __builtin_return_address(0));
+ } else {
+ result = impl.memalign(align, size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+#endif
+
+EXPORT void* REPLACE_NAME(aligned_alloc)(size_t align, size_t size) {
+ memalign_hook_t* hook = registered_hooks->aligned_alloc_hook;
+ void* result;
+
+ LOG_FUNC(aligned_alloc);
+ LOG_ALIGN(align);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(align, size, __builtin_return_address(0));
+ } else {
+ result = impl.aligned_alloc(align, size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+
+#if !defined(MUSL_LIBC)
+EXPORT void* REPLACE_NAME(valloc)(size_t size) {
+ valloc_hook_t* hook = registered_hooks->valloc_hook;
+ void* result;
+
+ LOG_FUNC(valloc);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(size, __builtin_return_address(0));
+ } else {
+ result = impl.valloc(size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+#endif
+
+#if !defined(MUSL_LIBC)
+EXPORT void* REPLACE_NAME(pvalloc)(size_t size) {
+ pvalloc_hook_t* hook = registered_hooks->pvalloc_hook;
+ void* result;
+
+ LOG_FUNC(pvalloc);
+ LOG_SIZE(size);
+
+ if (hook != NULL) {
+ result = hook(size, __builtin_return_address(0));
+ } else {
+ result = impl.pvalloc(size);
+ }
+
+ LOG_ALLOCATION_RESULT(result);
+ LOG_HOOK;
+
+ return result;
+}
+#endif
+
+#if defined(__APPLE__)
+
+#define DYLD_INTERPOSE(_replacement,_replacee) \
+ __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
+ __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };
+
+DYLD_INTERPOSE(REPLACE_NAME(malloc), malloc)
+DYLD_INTERPOSE(REPLACE_NAME(calloc), calloc)
+DYLD_INTERPOSE(REPLACE_NAME(realloc), realloc)
+DYLD_INTERPOSE(REPLACE_NAME(free), free)
+DYLD_INTERPOSE(REPLACE_NAME(posix_memalign), posix_memalign)
+
+// We compile for 10.12 but aligned_alloc is only available in 10.15 and up
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability-new"
+DYLD_INTERPOSE(REPLACE_NAME(aligned_alloc), aligned_alloc)
+#pragma clang diagnostic pop
+
+DYLD_INTERPOSE(REPLACE_NAME(valloc), valloc)
+
+#endif
+
+
+// D E B U G C O D E
+
+
+#if LOG_LEVEL > 0
+
+#define DEBUG_FD 2
+
+static void print(char const* str) {
+ write_safe(DEBUG_FD, str, strlen(str));
+}
+
+static void print_ptr(void* ptr) {
+ char buf[18];
+ int shift = 64;
+ buf[0] = '0';
+ buf[1] = 'x';
+ char* p = buf + 2;
+ print("0x");
+
+ do {
+ shift -= 4;
+ *p = "0123456789abcdef"[((((size_t) ptr) >> shift) & 15)];
+ ++p;
+ } while (shift > 0);
+
+ write_safe(DEBUG_FD, buf, p - buf);
+}
+
+static void print_size(size_t size) {
+ char buf[20];
+ size_t pos = sizeof(buf);
+
+ do {
+ buf[--pos] = '0' + (size % 10);
+ size /= 10;
+ } while (size > 0);
+
+ write_safe(DEBUG_FD, buf + pos, sizeof(buf) - pos);
+}
+#endif
+
diff --git a/src/java.base/unix/native/libmallochooks/mallochooks.h b/src/java.base/unix/native/libmallochooks/mallochooks.h
new file mode 100644
index 000000000000..3ad02fe95efe
--- /dev/null
+++ b/src/java.base/unix/native/libmallochooks/mallochooks.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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.
+ *
+ */
+
+#ifndef __SAPMACHINE_MALLOC_HOOK
+#define __SAPMACHINE_MALLOC_HOOK
+
+typedef void* malloc_func_t(size_t size);
+typedef void* calloc_func_t(size_t elems, size_t size);
+typedef void* realloc_func_t(void* ptr, size_t size);
+typedef void free_func_t(void* ptr);
+typedef int posix_memalign_func_t(void** ptr, size_t align, size_t size);
+typedef void* memalign_func_t(size_t align, size_t size);
+typedef void* aligned_alloc_func_t(size_t align, size_t size);
+typedef void* valloc_func_t(size_t size);
+typedef void* pvalloc_func_t(size_t size);
+
+typedef size_t malloc_size_func_t(void* ptr);
+typedef void* malloc_hook_t(size_t size, void* caller);
+typedef void* calloc_hook_t(size_t elems, size_t size, void* caller);
+typedef void* realloc_hook_t(void* ptr, size_t size, void* caller);
+typedef void free_hook_t(void* ptr, void* caller);
+typedef int posix_memalign_hook_t(void** ptr, size_t align, size_t size, void* caller);
+typedef void* memalign_hook_t(size_t align, size_t size, void* caller);
+typedef void* aligned_alloc_hook_t(size_t align, size_t size, void* caller);
+typedef void* valloc_hook_t(size_t size, void* caller);
+typedef void* pvalloc_hook_t(size_t size, void* caller);
+
+typedef struct {
+ malloc_hook_t* malloc_hook;
+ calloc_hook_t* calloc_hook;
+ realloc_hook_t* realloc_hook;
+ free_hook_t* free_hook;
+ posix_memalign_hook_t* posix_memalign_hook;
+ memalign_hook_t* memalign_hook;
+ aligned_alloc_hook_t* aligned_alloc_hook;
+ valloc_hook_t* valloc_hook;
+ pvalloc_hook_t* pvalloc_hook;
+} registered_hooks_t;
+
+typedef struct {
+ malloc_func_t* malloc;
+ calloc_func_t* calloc;
+ realloc_func_t* realloc;
+ free_func_t* free;
+ posix_memalign_func_t* posix_memalign;
+ memalign_func_t* memalign;
+ aligned_alloc_func_t* aligned_alloc;
+ valloc_func_t* valloc;
+ pvalloc_func_t* pvalloc;
+ malloc_size_func_t* malloc_size;
+} real_malloc_funcs_t;
+
+typedef registered_hooks_t* register_hooks_t(registered_hooks_t* registered_hooks);
+typedef registered_hooks_t* active_hooks_t();
+typedef real_malloc_funcs_t* get_real_malloc_funcs_t();
+
+#define REGISTER_HOOKS_NAME "malloc_hooks_register_hooks"
+#define ACTIVE_HOOKS_NAME "malloc_hooks_active_hooks"
+#define GET_REAL_MALLOC_FUNCS_NAME "malloc_hooks_get_real_malloc_funcs"
+
+#endif
+
diff --git a/src/java.base/windows/classes/java/lang/ProcessImpl.java b/src/java.base/windows/classes/java/lang/ProcessImpl.java
index 26d7afc5363a..511e5394aecd 100644
--- a/src/java.base/windows/classes/java/lang/ProcessImpl.java
+++ b/src/java.base/windows/classes/java/lang/ProcessImpl.java
@@ -87,7 +87,9 @@ static Process start(String cmdarray[],
java.util.Map environment,
String dir,
ProcessBuilder.Redirect[] redirects,
- boolean redirectErrorStream)
+ boolean redirectErrorStream,
+ // SapMachine 2024-06-12: process group extension
+ boolean createNewProcessGroupOnSpawn)
throws IOException
{
String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
@@ -156,7 +158,8 @@ static Process start(String cmdarray[],
}
Process p = new ProcessImpl(cmdarray, envblock, dir,
- stdHandles, forceNullOutputStream, redirectErrorStream);
+ // SapMachine 2024-07-01: process group extension
+ stdHandles, forceNullOutputStream, redirectErrorStream, createNewProcessGroupOnSpawn);
if (redirects != null) {
// Copy the handles's if they are to be redirected to another process
if (stdHandles[0] >= 0
@@ -422,12 +425,17 @@ private static int countLeadingBackslash(int verificationType,
private InputStream stdout_stream;
private InputStream stderr_stream;
+ // SapMachine 2024-07-01: process group extension
+ private final long hJob;
+
private ProcessImpl(String cmd[],
final String envblock,
final String path,
final long[] stdHandles,
boolean forceNullOutputStream,
- final boolean redirectErrorStream)
+ final boolean redirectErrorStream,
+ // SapMachine 2024-07-01: process group extension
+ final boolean createNewProcessGroupOnSpawn)
throws IOException
{
String cmdstr;
@@ -485,11 +493,18 @@ private ProcessImpl(String cmd[],
cmd);
}
+ // SapMachine 2024-07-01: process group extension
+ final long[] local_hJob = (createNewProcessGroupOnSpawn) ? new long[1] : null;
handle = create(cmdstr, envblock, path,
- stdHandles, redirectErrorStream);
+ stdHandles, redirectErrorStream, local_hJob);
+ hJob = (createNewProcessGroupOnSpawn) ? local_hJob[0] : 0;
+
// Register a cleaning function to close the handle
final long local_handle = handle; // local to prevent capture of this
- CleanerFactory.cleaner().register(this, () -> closeHandle(local_handle));
+ // SapMachine 2024-07-01: process group extension
+ CleanerFactory.cleaner().register(this, createNewProcessGroupOnSpawn ?
+ () -> closeHandle(local_handle) :
+ () -> {closeHandle(local_handle); closeHandle(local_hJob[0]);});
processHandle = ProcessHandleImpl.getInternal(getProcessId0(handle));
@@ -631,6 +646,17 @@ public Process destroyForcibly() {
private static native void terminateProcess(long handle);
+ // SapMachine 2024-07-01: process group extension
+ private static native void terminateProcessGroup(long hJob);
+
+ void terminateProcessGroup(boolean force) {
+ if (hJob == 0) {
+ destroy();
+ } else {
+ terminateProcessGroup(hJob);
+ }
+ }
+
@Override
public long pid() {
return processHandle.pid();
@@ -684,7 +710,9 @@ private static synchronized native long create(String cmdstr,
String envblock,
String dir,
long[] stdHandles,
- boolean redirectErrorStream)
+ boolean redirectErrorStream,
+ // SapMachine 2024-07-01: process group extension
+ long[] processGroup)
throws IOException;
/**
diff --git a/src/java.base/windows/native/launcher/icons/awt.ico b/src/java.base/windows/native/launcher/icons/awt.ico
index 8d2c9571ed9e..a8507d716d80 100644
Binary files a/src/java.base/windows/native/launcher/icons/awt.ico and b/src/java.base/windows/native/launcher/icons/awt.ico differ
diff --git a/src/java.base/windows/native/libjava/ProcessImpl_md.c b/src/java.base/windows/native/libjava/ProcessImpl_md.c
index 127c27981ba3..84436d9f1619 100644
--- a/src/java.base/windows/native/libjava/ProcessImpl_md.c
+++ b/src/java.base/windows/native/libjava/ProcessImpl_md.c
@@ -270,7 +270,9 @@ static jlong processCreate(
const jchar *penvBlock,
const jchar *pdir,
jlong *handles,
- jboolean redirectErrorStream)
+ jboolean redirectErrorStream,
+ /* SapMachine 2024-07-01: process group extension */
+ jlongArray processGroup)
{
jlong ret = 0L;
STARTUPINFOW si = {sizeof(si)};
@@ -328,6 +330,10 @@ static jlong processCreate(
processFlag &= ~CREATE_NO_WINDOW;
}
+ /* SapMachine 2024-07-01: process group extension */
+ if (processGroup)
+ processFlag |= CREATE_SUSPENDED;
+
si.dwFlags = STARTF_USESTDHANDLES;
if (!CreateProcessW(
NULL, /* executable name */
@@ -343,6 +349,17 @@ static jlong processCreate(
{
win32Error(env, L"CreateProcess");
} else {
+ /* SapMachine 2024-07-01: process group extension */
+ if (processGroup) {
+ __declspec(align(8)) HANDLE hJob = CreateJobObject(NULL, NULL);
+ if (!hJob) {
+ win32Error(env, L"CreateJobObject");
+ } else {
+ (*env)->SetLongArrayRegion(env, processGroup, 0, 1, (jlong *) &hJob);
+ AssignProcessToJobObject(hJob, pi.hProcess);
+ }
+ ResumeThread(pi.hThread);
+ }
closeSafely(pi.hThread);
ret = (jlong)pi.hProcess;
}
@@ -364,7 +381,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
jstring envBlock,
jstring dir,
jlongArray stdHandles,
- jboolean redirectErrorStream)
+ jboolean redirectErrorStream,
+ /* SapMachine 2024-07-01: process group extension */
+ jlongArray processGroup)
{
jlong ret = 0;
if (cmd != NULL && stdHandles != NULL) {
@@ -393,7 +412,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
penvBlock,
pdir,
handles,
- redirectErrorStream);
+ redirectErrorStream,
+ /* SapMachine 2024-07-01: process group extension */
+ processGroup);
free(pcmdCopy); // free mutable command line
}
(*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
@@ -477,6 +498,15 @@ Java_java_lang_ProcessImpl_closeHandle(JNIEnv *env, jclass ignored, jlong handle
return (jboolean) CloseHandle((HANDLE) handle);
}
+/* SapMachine 2024-07-01: process group extension */
+JNIEXPORT void JNICALL
+Java_java_lang_ProcessImpl_terminateProcessGroup(JNIEnv *env, jclass ignored, jlong hJob)
+{
+ if (TerminateJobObject((HANDLE) hJob, 1) == 0) {
+ win32Error(env, L"TerminateJobObject");
+ }
+}
+
JNIEXPORT jlong JNICALL
Java_java_lang_ProcessImpl_openForAtomicAppend(JNIEnv *env, jclass ignored, jstring path)
{
diff --git a/src/java.desktop/macosx/data/macosxicons/JavaApp.icns b/src/java.desktop/macosx/data/macosxicons/JavaApp.icns
index fc60195cd0e9..9c93b4629828 100644
Binary files a/src/java.desktop/macosx/data/macosxicons/JavaApp.icns and b/src/java.desktop/macosx/data/macosxicons/JavaApp.icns differ
diff --git a/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.c b/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.c
new file mode 100644
index 000000000000..b47f938b8a2b
--- /dev/null
+++ b/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. All rights reserved.
+ * Copyright (c) 2018, 2024, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include
+#include
+#include
+#include "jni.h"
+#include "jdwpTransport.h"
+#include "fileSocketTransport.h"
+
+#ifdef _WIN32
+#include
+#include
+#define MAX_FILE_SOCKET_PATH_LEN UNIX_PATH_MAX
+#else
+#include
+#include
+#include
+#define MAX_FILE_SOCKET_PATH_LEN sizeof(((struct sockaddr_un *) 0)->sun_path)
+#endif
+
+#define MAX_DATA_SIZE 1000
+#define HANDSHAKE "JDWP-Handshake"
+
+/* Since the jdwp agent sometimes kills the VM outright when
+ * the connection fails, we always fake a successful
+ * connection and instead fail in the read/write packet methods,
+ * which does not cause the VM to exit.
+ */
+static jboolean fake_open = JNI_FALSE;
+static jboolean initialized = JNI_FALSE;
+static JavaVM *jvm;
+static char path[MAX_FILE_SOCKET_PATH_LEN];
+static jdwpTransportCallback *callback;
+static char last_error[2048];
+static struct jdwpTransportNativeInterface_ nif;
+static jdwpTransportEnv single_env = (jdwpTransportEnv) &nif;
+
+void fileSocketTransport_logError(char const* format, ...) {
+ char* tmp = (*callback->alloc)(sizeof(last_error));
+ va_list ap;
+
+ if (tmp != NULL) {
+ va_start(ap, format);
+ vsnprintf(tmp, sizeof(last_error) - 1, format, ap);
+ tmp[sizeof(last_error) - 1] = '\0';
+ va_end(ap);
+
+ printf("Error: %s\n", tmp);
+
+ memcpy(last_error, tmp, sizeof(last_error));
+ (*callback->free)(tmp);
+ } else {
+ printf("Could not get memory to print error.\n");
+ }
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_GetCapabilities(jdwpTransportEnv* env, JDWPTransportCapabilities *capabilities_ptr) {
+ JDWPTransportCapabilities result;
+
+ memset(&result, 0, sizeof(result));
+ result.can_timeout_attach = JNI_FALSE;
+ result.can_timeout_accept = JNI_FALSE;
+ result.can_timeout_handshake = JNI_FALSE;
+
+ *capabilities_ptr = result;
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_SetTransportConfiguration(jdwpTransportEnv* env, jdwpTransportConfiguration *config) {
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_Close(jdwpTransportEnv* env) {
+ if (fileSocketTransport_HasValidHandle()) {
+ fileSocketTransport_CloseImpl();
+ }
+
+ fake_open = JNI_FALSE;
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_Attach(jdwpTransportEnv* env, const char* address, jlong attach_timeout, jlong handshake_timeout) {
+ /* We don't support attach. */
+ fileSocketTransport_logError("Only server=y mode is supported by dt_filesocket");
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_StartListening(jdwpTransportEnv* env, const char* address, char** actual_address) {
+ /* Only make sure we have no open connection. */
+ fileSocketTransport_Close(env);
+
+ if (address == NULL) {
+ fileSocketTransport_logError("Default address not supported");
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+
+ *actual_address = (*callback->alloc)((int)strlen(address) + 1);
+
+ if (*actual_address == NULL) {
+ return JDWPTRANSPORT_ERROR_OUT_OF_MEMORY;
+ } else {
+ strcpy(*actual_address, address);
+ }
+
+ if (strlen(address) < MAX_FILE_SOCKET_PATH_LEN) {
+ strcpy(path, address);
+ } else {
+ fileSocketTransport_logError("Address too long: %s", address);
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_StopListening(jdwpTransportEnv* env) {
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jboolean JNICALL fileSocketTransport_IsOpen(jdwpTransportEnv* env) {
+ return fake_open || fileSocketTransport_HasValidHandle();
+}
+
+static int fileSocketTransport_ReadFully(char* buf, int len) {
+ int read = 0;
+
+ while (len > 0) {
+ int n = fileSocketTransport_ReadImpl(buf, len);
+
+ if (n < 0) {
+ return n;
+ } else if (n == 0) {
+ break;
+ }
+
+ buf += n;
+ len -= n;
+ read += n;
+ }
+
+ return read;
+}
+
+static int fileSocketTransport_WriteFully(char* buf, int len) {
+ int written = 0;
+
+ while (len > 0) {
+ int n = fileSocketTransport_WriteImpl(buf, len);
+
+ if (n < 0) {
+ return n;
+ } else if (n == 0) {
+ break;
+ }
+
+ buf += n;
+ len -= n;
+ written += n;
+ }
+
+ return written;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_Accept(jdwpTransportEnv* env, jlong accept_timeout, jlong handshake_timeout) {
+ fileSocketTransport_AcceptImpl(path);
+
+ if (!fileSocketTransport_HasValidHandle()) {
+ fake_open = JNI_TRUE;
+ } else {
+ char buf[sizeof(HANDSHAKE)];
+ fileSocketTransport_ReadFully(buf, (int) strlen(HANDSHAKE));
+ fileSocketTransport_WriteFully(HANDSHAKE, (int) strlen(HANDSHAKE));
+
+ if (strcmp(buf, HANDSHAKE) != 0) {
+ fake_open = JNI_TRUE;
+ }
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_ReadPacket(jdwpTransportEnv* env, jdwpPacket *packet) {
+ jint length, data_len, n;
+
+ if (!fileSocketTransport_HasValidHandle()) {
+ fake_open = JNI_FALSE;
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ } else if (fake_open) {
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ if (packet == NULL) {
+ fileSocketTransport_logError("Packet is null while reading");
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+
+ /* Taken mostly from socketTransport.c */
+ n = fileSocketTransport_ReadFully((char *) &length, sizeof(jint));
+
+ if (n == 0) {
+ packet->type.cmd.len = 0;
+ return JDWPTRANSPORT_ERROR_NONE;
+ }
+
+ if (n != sizeof(jint)) {
+ fileSocketTransport_logError("Only read %d instead of %d bytes for length field", (int) n, (int) sizeof(jint));
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ length = (jint) ntohl(length);
+ packet->type.cmd.len = length;
+ n = fileSocketTransport_ReadFully((char *)&(packet->type.cmd.id), sizeof(jint));
+
+ if (n < (int)sizeof(jint)) {
+ fileSocketTransport_logError("Only read %d instead of %d bytes for command id", (int) n, (int) sizeof(jint));
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ packet->type.cmd.id = (jint) ntohl(packet->type.cmd.id);
+ n = fileSocketTransport_ReadFully((char *)&(packet->type.cmd.flags), sizeof(jbyte));
+
+ if (n < (int) sizeof(jbyte)) {
+ fileSocketTransport_logError("Only read %d bytes for flags", (int) n);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ if (packet->type.cmd.flags & JDWPTRANSPORT_FLAGS_REPLY) {
+ n = fileSocketTransport_ReadFully((char *)&(packet->type.reply.errorCode), sizeof(jbyte));
+ if (n < (int)sizeof(jshort)) {
+ fileSocketTransport_logError("Only read %d bytes for error code", (int) n);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+ } else {
+ n = fileSocketTransport_ReadFully((char *)&(packet->type.cmd.cmdSet), sizeof(jbyte));
+ if (n < (int)sizeof(jbyte)) {
+ fileSocketTransport_logError("Only read %d bytes for command set", (int) n);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ n = fileSocketTransport_ReadFully((char *)&(packet->type.cmd.cmd), sizeof(jbyte));
+ if (n < (int)sizeof(jbyte)) {
+ fileSocketTransport_logError("Only read %d bytes for command", (int) n);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+ }
+
+ data_len = length - ((sizeof(jint) * 2) + (sizeof(jbyte) * 3));
+
+ if (data_len < 0) {
+ fileSocketTransport_logError("Inavlid data length %d of read packet", (int) data_len);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ } else if (data_len == 0) {
+ packet->type.cmd.data = NULL;
+ } else {
+ packet->type.cmd.data = (*callback->alloc)(data_len);
+
+ if (packet->type.cmd.data == NULL) {
+ return JDWPTRANSPORT_ERROR_OUT_OF_MEMORY;
+ }
+
+ n = fileSocketTransport_ReadFully((char *) packet->type.cmd.data, data_len);
+
+ if (n < data_len) {
+ fileSocketTransport_logError("Only read %d bytes for JDWP payload but expected %d", (int) n, (int) data_len);
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_WritePacket(jdwpTransportEnv* env, const jdwpPacket* packet) {
+ jint len, data_len, id, n;
+ char header[JDWP_HEADER_SIZE + MAX_DATA_SIZE];
+ jbyte *data;
+
+ if (!fileSocketTransport_HasValidHandle()) {
+ fake_open = JNI_FALSE;
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ } else if (fake_open) {
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
+
+ /* Taken mostly from sockectTransport.c */
+ if (packet == NULL) {
+ fileSocketTransport_logError("Packet is null when writing");
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+
+ len = packet->type.cmd.len;
+ data_len = len - JDWP_HEADER_SIZE;
+
+ if (data_len < 0) {
+ fileSocketTransport_logError("Packet to write has illegal data length %d", (int) data_len);
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+
+ /* prepare the header for transmission */
+ len = (jint) htonl(len);
+ id = (jint) htonl(packet->type.cmd.id);
+
+ memcpy(header + 0, &len, 4);
+ memcpy(header + 4, &id, 4);
+ header[8] = packet->type.cmd.flags;
+
+ if (packet->type.cmd.flags & JDWPTRANSPORT_FLAGS_REPLY) {
+ jshort errorCode = htons(packet->type.reply.errorCode);
+ memcpy(header + 9, &errorCode, 2);
+ } else {
+ header[9] = packet->type.cmd.cmdSet;
+ header[10] = packet->type.cmd.cmd;
+ }
+
+ data = packet->type.cmd.data;
+
+ /* Do one send for short packets, two for longer ones */
+ if (data_len <= MAX_DATA_SIZE) {
+ memcpy(header + JDWP_HEADER_SIZE, data, data_len);
+ if ((n = fileSocketTransport_WriteFully((char *) &header, JDWP_HEADER_SIZE + data_len)) !=
+ JDWP_HEADER_SIZE + data_len) {
+ fileSocketTransport_logError("Could only write %d bytes instead of %d of the packet", (int) n, (int) (JDWP_HEADER_SIZE + data_len));
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+ } else {
+ memcpy(header + JDWP_HEADER_SIZE, data, MAX_DATA_SIZE);
+ if ((n = fileSocketTransport_WriteFully((char *) &header, JDWP_HEADER_SIZE + MAX_DATA_SIZE)) !=
+ JDWP_HEADER_SIZE + MAX_DATA_SIZE) {
+ fileSocketTransport_logError("Could only write %d bytes instead of %d of the packet", (int) n, (int) (JDWP_HEADER_SIZE + MAX_DATA_SIZE));
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+ /* Send the remaining data bytes right out of the data area. */
+ if ((n = fileSocketTransport_WriteFully((char *) data + MAX_DATA_SIZE,
+ data_len - MAX_DATA_SIZE)) != data_len - MAX_DATA_SIZE) {
+ fileSocketTransport_logError("Could only write %d bytes instead of %d of the packet", (int) n, (int) (data_len - MAX_DATA_SIZE));
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
+ }
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL fileSocketTransport_GetLastError(jdwpTransportEnv* env, char** error) {
+ *error = (*callback->alloc)(sizeof(last_error));
+
+ if (*error == NULL) {
+ return JDWPTRANSPORT_ERROR_OUT_OF_MEMORY;
+ }
+
+ memcpy(*error, last_error, sizeof(last_error));
+ (*error)[sizeof(last_error) - 1] = '\0';
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+JNIEXPORT jint JNICALL
+jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* callbacks, jint version, jdwpTransportEnv** env)
+{
+ if (version < JDWPTRANSPORT_VERSION_1_0 ||version > JDWPTRANSPORT_VERSION_1_1) {
+ return JNI_EVERSION;
+ }
+
+ if (initialized) {
+ return JNI_EEXIST;
+ }
+
+ initialized = JNI_TRUE;
+ jvm = vm;
+ callback = callbacks;
+
+ /* initialize interface table */
+ nif.GetCapabilities = fileSocketTransport_GetCapabilities;
+ nif.Attach = fileSocketTransport_Attach;
+ nif.StartListening = fileSocketTransport_StartListening;
+ nif.StopListening = fileSocketTransport_StopListening;
+ nif.Accept = fileSocketTransport_Accept;
+ nif.IsOpen = fileSocketTransport_IsOpen;
+ nif.Close = fileSocketTransport_Close;
+ nif.ReadPacket = fileSocketTransport_ReadPacket;
+ nif.WritePacket = fileSocketTransport_WritePacket;
+ nif.GetLastError = fileSocketTransport_GetLastError;
+ if (version >= JDWPTRANSPORT_VERSION_1_1) {
+ nif.SetTransportConfiguration = fileSocketTransport_SetTransportConfiguration;
+ }
+ *env = (jdwpTransportEnv*) &single_env;
+
+ return JNI_OK;
+}
diff --git a/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.h b/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.h
new file mode 100644
index 000000000000..29016886a2de
--- /dev/null
+++ b/src/jdk.jdwp.agent/share/native/libdt_filesocket/fileSocketTransport.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#ifndef FILE_SOCKET_TRANSPORT_H
+#define FILE_SOCKET_TRANSPORT_H
+
+#include "jni.h"
+
+void fileSocketTransport_logError(char const* format, ...);
+jboolean fileSocketTransport_HasValidHandle();
+void fileSocketTransport_CloseImpl();
+void fileSocketTransport_AcceptImpl(char const* name);
+int fileSocketTransport_ReadImpl(char* buffer, int size);
+int fileSocketTransport_WriteImpl(char* buffer, int size);
+
+#endif
diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c
index 1911ff1bed46..ea40b4dcb81f 100644
--- a/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c
+++ b/src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c
@@ -838,6 +838,8 @@ printUsage(void)
"launch= run debugger on event none\n"
"onthrow= debug on throw none\n"
"onuncaught=y|n debug on any uncaught? n\n"
+ // SapMachine 2022-12-12 Revert JDK-8226608, we should show the onjcmd=y|n option in jdwp usage
+ "onjcmd=y|n start debug via jcmd? n\n"
"timeout= for listen/attach in milliseconds n\n"
"includevirtualthreads=y|n List of all threads includes virtual threads as well as platform threads.\n"
" n\n"
diff --git a/src/jdk.jdwp.agent/unix/native/libdt_filesocket/fileSocketTransport_md.c b/src/jdk.jdwp.agent/unix/native/libdt_filesocket/fileSocketTransport_md.c
new file mode 100644
index 000000000000..575eced1dd8e
--- /dev/null
+++ b/src/jdk.jdwp.agent/unix/native/libdt_filesocket/fileSocketTransport_md.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include
+#include
+
+#include "jni.h"
+#include "fileSocketTransport.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define UNIX_PATH_MAX sizeof(((struct sockaddr_un *) 0)->sun_path)
+
+static int server_socket = -1;
+static int connection_socket = -1;
+
+static void closeSocket(int* socket) {
+ if (*socket != -1) {
+#if defined(_AIX)
+ int rv;
+
+ do {
+ rv = close(*socket);
+ } while ((rv != 0) && (errno == EINTR));
+#else
+ close(*socket);
+#endif
+
+ *socket = -1;
+ }
+}
+
+static char file_to_delete[UNIX_PATH_MAX];
+static volatile int file_to_delete_valid;
+
+static void memoryBarrier() {
+#if defined(__linux__) || defined(__APPLE__)
+ __sync_synchronize();
+#elif defined(_AIX)
+ __sync();
+#else
+#error "Unknown platform"
+#endif
+}
+
+static jboolean deleteFile(char const* name) {
+ return (access(name, F_OK) == -1) || (unlink(name) == 0) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void registerFileToDelete(char const* name) {
+ if (file_to_delete_valid == 0) {
+ if ((name != NULL) && (strlen(name) + 1 <= sizeof(file_to_delete))) {
+ strcpy(file_to_delete, name);
+ memoryBarrier();
+ file_to_delete_valid = 1;
+ memoryBarrier();
+ }
+ } else {
+ // Should never change.
+ assert(strcmp(name, file_to_delete) == 0);
+ }
+}
+
+static void cleanupSocketOnExit(void) {
+ memoryBarrier();
+
+ if (file_to_delete_valid) {
+ memoryBarrier();
+ deleteFile(file_to_delete);
+ }
+}
+
+jboolean fileSocketTransport_HasValidHandle() {
+ return connection_socket == -1 ? JNI_FALSE : JNI_TRUE;
+}
+
+void fileSocketTransport_CloseImpl() {
+ closeSocket(&server_socket);
+ closeSocket(&connection_socket);
+}
+
+void logAndCleanupFailedAccept(char const* error_msg, char const* name) {
+ fileSocketTransport_logError("%s: socket %s: %s", error_msg, name, strerror(errno));
+ fileSocketTransport_CloseImpl();
+}
+
+void fileSocketTransport_AcceptImpl(char const* name) {
+ static int already_called = 0;
+
+ if (!already_called) {
+ registerFileToDelete(name);
+ atexit(cleanupSocketOnExit);
+ already_called = 1;
+ }
+
+ if (server_socket == -1) {
+ socklen_t len = sizeof(struct sockaddr_un);
+ struct sockaddr_un addr;
+ int addr_size = sizeof(addr);
+
+ memset((void*)&addr, 0, len);
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, name, sizeof(addr.sun_path) - 1);
+
+#ifdef _AIX
+ addr.sun_len = strlen(addr.sun_path);
+ addr_size = SUN_LEN(&addr);
+#endif
+
+ server_socket = socket(PF_UNIX, SOCK_STREAM, 0);
+
+ if (server_socket == -1) {
+ logAndCleanupFailedAccept("Could not create domain socket", name);
+ return;
+ }
+
+ if (!deleteFile(name)) {
+ logAndCleanupFailedAccept("Could not remove file to create new file socket", name);
+ return;
+ }
+
+ if (bind(server_socket, (struct sockaddr*)&addr, addr_size) == -1) {
+ logAndCleanupFailedAccept("Could not bind file socket", name);
+ return;
+ }
+
+ if (chmod(name, (S_IREAD | S_IWRITE) & ~(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1) {
+ logAndCleanupFailedAccept("Chmod on file socket failed", name);
+ return;
+ }
+
+ if (chown(name, geteuid(), getegid()) == -1) {
+ logAndCleanupFailedAccept("Chown on file socket failed", name);
+ return;
+ }
+
+ if (listen(server_socket, 1) == -1) {
+ logAndCleanupFailedAccept("Could not listen on file socket", name);
+ return;
+ }
+ }
+
+ do {
+ connection_socket = accept(server_socket, NULL, NULL);
+ } while ((connection_socket == -1) && (errno == EINTR));
+
+ /* We can remove the file since we are connected (or it failed). */
+ deleteFile(name);
+ closeSocket(&server_socket);
+
+ if (connection_socket == -1) {
+ logAndCleanupFailedAccept("Could not accept on file socket", name);
+ return;
+ }
+
+ uid_t other_user = (uid_t)-1;
+ gid_t other_group = (gid_t)-1;
+
+ /* Check if the connected user is the same as the user running the VM. */
+#if defined(__linux__)
+ struct ucred cred_info;
+ socklen_t optlen = sizeof(cred_info);
+
+ if (getsockopt(connection_socket, SOL_SOCKET, SO_PEERCRED, (void*) &cred_info, &optlen) == -1) {
+ logAndCleanupFailedAccept("Failed to get socket option SO_PEERCRED of file socket", name);
+ return;
+ }
+
+ other_user = cred_info.uid;
+ other_group = cred_info.gid;
+#elif defined(__APPLE__)
+ if (getpeereid(connection_socket, &other_user, &other_group) != 0) {
+ logAndCleanupFailedAccept("Failed to get peer id of file socket", name);
+ return;
+ }
+#elif defined(_AIX)
+ struct peercred_struct cred_info;
+ socklen_t optlen = sizeof(cred_info);
+
+ if (getsockopt(connection_socket, SOL_SOCKET, SO_PEERID, (void*)&cred_info, &optlen) == -1) {
+ logAndCleanupFailedAccept("Failed to get socket option SO_PEERID of file socket", name);
+ return;
+ }
+
+ other_user = cred_info.euid;
+ other_group = cred_info.egid;
+#else
+#error "Unknown platform"
+#endif
+
+ /* Allow root too */
+ if (other_user != 0) {
+ if (other_user != geteuid()) {
+ fileSocketTransport_logError("Cannot allow user %d to connect to file socket %s of user %d",
+ (int)other_user, name, (int)geteuid());
+ fileSocketTransport_CloseImpl();
+ } else if (other_group != getegid()) {
+ fileSocketTransport_logError("Cannot allow user %d (group %d) to connect to file socket "
+ "%s of user %d (group %d)", (int)other_user, (int)other_group,
+ name, (int)geteuid(), (int)getegid());
+ fileSocketTransport_CloseImpl();
+ }
+ }
+}
+
+int fileSocketTransport_ReadImpl(char* buffer, int size) {
+ int result;
+
+ do {
+ result = read(connection_socket, buffer, size);
+ } while ((result < 0) && (errno == EINTR));
+
+ if (result < 0) {
+ fileSocketTransport_logError("Read failed with result %d: %s", result, strerror(errno));
+ }
+
+ return result;
+}
+
+int fileSocketTransport_WriteImpl(char* buffer, int size) {
+ int result;
+
+ do {
+ result = write(connection_socket, buffer, size);
+ } while ((result < 0) && (errno == EINTR));
+
+ if (result < 0) {
+ fileSocketTransport_logError("Write failed with result %d: %s", result, strerror(errno));
+ }
+
+ return result;
+}
diff --git a/src/jdk.jdwp.agent/windows/native/libdt_filesocket/fileSocketTransport_md.c b/src/jdk.jdwp.agent/windows/native/libdt_filesocket/fileSocketTransport_md.c
new file mode 100644
index 000000000000..51d4da80eba0
--- /dev/null
+++ b/src/jdk.jdwp.agent/windows/native/libdt_filesocket/fileSocketTransport_md.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include
+#include
+
+#include "jni.h"
+#include "fileSocketTransport.h"
+
+
+#include
+#include
+#include
+
+/* Make sure winsock is initialized on Windows */
+BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved)
+{
+ WSADATA wsadata;
+
+ if ((reason == DLL_PROCESS_ATTACH) && (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)) {
+ return JNI_FALSE;
+ }
+
+ if (reason == DLL_PROCESS_DETACH) {
+ WSACleanup();
+ }
+
+ return TRUE;
+}
+
+static SOCKET server_socket = INVALID_SOCKET;
+static SOCKET connection_socket = INVALID_SOCKET;
+
+static void closeSocket(SOCKET* socket) {
+ if (*socket != INVALID_SOCKET) {
+ closesocket(*socket);
+ *socket = INVALID_SOCKET;
+ }
+}
+
+static char file_to_delete[UNIX_PATH_MAX];
+static volatile int file_to_delete_valid;
+
+static jboolean deleteFile(char const* name) {
+ if ((!DeleteFile(name)) && (GetLastError() != ERROR_FILE_NOT_FOUND)) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static void registerFileToDelete(char const* name) {
+ if (file_to_delete_valid == 0) {
+ if ((name != NULL) && (strlen(name) + 1 <= sizeof(file_to_delete))) {
+ strcpy(file_to_delete, name);
+ MemoryBarrier();
+ file_to_delete_valid = 1;
+ MemoryBarrier();
+ }
+ } else {
+ // Should never change.
+ assert(strcmp(name, file_to_delete) == 0);
+ }
+}
+
+static char* getErrorMsg(char* buf, size_t len) {
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, (DWORD)len, NULL);
+ return buf;
+}
+
+static void cleanupSocketOnExit(void) {
+ MemoryBarrier();
+
+ if (file_to_delete_valid) {
+ MemoryBarrier();
+ deleteFile(file_to_delete);
+ }
+}
+
+jboolean fileSocketTransport_HasValidHandle() {
+ return connection_socket == INVALID_SOCKET ? JNI_FALSE : JNI_TRUE;
+}
+
+void fileSocketTransport_CloseImpl() {
+ closeSocket(&server_socket);
+ closeSocket(&connection_socket);
+}
+
+void logAndCleanupFailedAccept(char const* error_msg, char const* name) {
+ char buf[256];
+ fileSocketTransport_logError("%s: socket %s: %s", error_msg, name, getErrorMsg(buf, sizeof(buf)));
+ fileSocketTransport_CloseImpl();
+}
+
+void fileSocketTransport_AcceptImpl(char const* name) {
+ static int already_called = 0;
+
+ if (!already_called) {
+ registerFileToDelete(name);
+ atexit(cleanupSocketOnExit);
+ already_called = 1;
+ }
+
+ if (server_socket == INVALID_SOCKET) {
+ int len = (int) sizeof(struct sockaddr_un);
+ struct sockaddr_un addr;
+ int addr_size = sizeof(addr);
+
+ memset((void*)&addr, 0, len);
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, name, sizeof(addr.sun_path) - 1);
+
+ server_socket = socket(PF_UNIX, SOCK_STREAM, 0);
+
+ if (server_socket == INVALID_SOCKET) {
+ logAndCleanupFailedAccept("Could not create domain socket", name);
+ return;
+ }
+
+ if (!deleteFile(name)) {
+ logAndCleanupFailedAccept("Could not remove file to create new file socket", name);
+ return;
+ }
+
+ if (bind(server_socket, (struct sockaddr*)&addr, addr_size) == -1) {
+ logAndCleanupFailedAccept("Could not bind file socket", name);
+ return;
+ }
+
+ if (listen(server_socket, 1) == -1) {
+ logAndCleanupFailedAccept("Could not listen on file socket", name);
+ return;
+ }
+ }
+
+ do {
+ connection_socket = accept(server_socket, NULL, NULL);
+ } while ((connection_socket == INVALID_SOCKET) && (WSAGetLastError() == WSAEINTR));
+
+ /* We can remove the file since we are connected (or it failed). */
+ deleteFile(name);
+ closeSocket(&server_socket);
+
+ if (connection_socket == INVALID_SOCKET) {
+ logAndCleanupFailedAccept("Could not accept on file socket", name);
+ return;
+ }
+
+ ULONG peer_pid;
+ DWORD size;
+
+ if (WSAIoctl(connection_socket, SIO_AF_UNIX_GETPEERPID, NULL, 0, &peer_pid, sizeof(peer_pid), &size, NULL, NULL) != 0) {
+ logAndCleanupFailedAccept("Could not determine connected processed", name);
+ return;
+ }
+
+ HANDLE peer_proc = NULL;
+ HANDLE self_token = NULL;
+ HANDLE peer_token = NULL;
+ PTOKEN_USER self_user = NULL;
+ PTOKEN_USER peer_user = NULL;
+ TOKEN_ELEVATION peer_elevation;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &self_token)) {
+ logAndCleanupFailedAccept("Could not get own token", name);
+ } else if (GetTokenInformation(self_token, TokenUser, NULL, 0, &size)) {
+ logAndCleanupFailedAccept("Could not get own token user size", name);
+ } else if ((self_user = (PTOKEN_USER)malloc((size_t)size)) == NULL) {
+ logAndCleanupFailedAccept("Could not alloc own token user size", name);
+ } else if (!GetTokenInformation(self_token, TokenUser, self_user, size, &size)) {
+ logAndCleanupFailedAccept("Could not get own token user", name);
+ } else if ((peer_proc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, peer_pid)) == NULL) {
+ logAndCleanupFailedAccept("Could not open peer process", name);
+ } else if (!OpenProcessToken(peer_proc, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &peer_token)) {
+ logAndCleanupFailedAccept("Could not get peer token", name);
+ } else if (GetTokenInformation(peer_token, TokenUser, NULL, 0, &size)) {
+ logAndCleanupFailedAccept("Could not get peer token user size", name);
+ } else if ((peer_user = (PTOKEN_USER)malloc((size_t)size)) == NULL) {
+ logAndCleanupFailedAccept("Could not alloc peer token user size", name);
+ } else if (!GetTokenInformation(peer_token, TokenUser, peer_user, size, &size)) {
+ logAndCleanupFailedAccept("Could not get peer token information", name);
+ } else if (!GetTokenInformation(peer_token, TokenElevation, &peer_elevation, sizeof(peer_elevation), &size)) {
+ logAndCleanupFailedAccept("Could not get peer token information", name);
+ } else if (!peer_elevation.TokenIsElevated && !EqualSid(self_user->User.Sid, peer_user->User.Sid)) {
+ fileSocketTransport_logError("Connecting process is not the same user nor admin");
+ fileSocketTransport_CloseImpl();
+ }
+
+ if (peer_token != NULL) {
+ CloseHandle(peer_token);
+ }
+
+ if (self_token != NULL) {
+ CloseHandle(self_token);
+ }
+
+ if (peer_proc != NULL) {
+ CloseHandle(peer_proc);
+ }
+
+ free(self_user);
+ free(peer_user);
+}
+
+int fileSocketTransport_ReadImpl(char* buffer, int size) {
+ int result;
+
+ do {
+ result = recv(connection_socket, buffer, size, 0);
+ } while ((result < 0) && (WSAGetLastError() == WSAEINTR));
+
+ if (result < 0) {
+ char buf[256];
+ fileSocketTransport_logError("Read failed with result %d: %s", result, getErrorMsg(buf, sizeof(buf)));
+ }
+
+ return result;
+}
+
+int fileSocketTransport_WriteImpl(char* buffer, int size) {
+ int result;
+
+ do {
+ result = send(connection_socket, buffer, size, 0);
+ } while ((result < 0) && (WSAGetLastError() == WSAEINTR));
+
+ if (result < 0) {
+ char buf[256];
+ fileSocketTransport_logError("Write failed with result %d: %s", result, getErrorMsg(buf, sizeof(buf)));
+ }
+
+ return result;
+}
diff --git a/src/jdk.jfr/share/conf/jfr/gc.jfc b/src/jdk.jfr/share/conf/jfr/gc.jfc
new file mode 100755
index 000000000000..32309bae04b8
--- /dev/null
+++ b/src/jdk.jfr/share/conf/jfr/gc.jfc
@@ -0,0 +1,1148 @@
+
+
+
+
+
+
+ true
+ 1000 ms
+
+
+
+ false
+ everyChunk
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ everyChunk
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ 10 s
+
+
+
+ false
+ 10 s
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+
+
+
+ false
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ 20 ms
+
+
+
+ false
+ 20 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ true
+ 10 ms
+
+
+
+ true
+ false
+
+
+
+ false
+ everyChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ false
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ false
+
+
+
+ true
+ false
+
+
+
+ false
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+ true
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ false
+ 0 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ false
+
+
+
+ true
+
+
+
+ false
+ everyChunk
+
+
+
+ false
+
+
+
+ false
+ everyChunk
+
+
+
+ false
+
+
+
+ true
+ false
+ 0 ns
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ 1000 ms
+
+
+
+ false
+ beginChunk
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ 60 s
+
+
+
+ false
+
+
+
+ false
+
+
+
+ true
+
+
+
+ false
+ beginChunk
+
+
+
+ false
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ 10 s
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ endChunk
+
+
+
+ true
+ false
+
+
+
+ false
+ 5 s
+
+
+
+ false
+ 10 s
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ 150/s
+ false
+
+
+
+ false
+ everyChunk
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ true
+ false
+
+
+
+ false
+ 1000 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ false
+ 0 ns
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 1 ms
+
+
+
+ false
+ 0 ms
+
+
+
+ false
+ 0 ms
+
+
+
+ false
+ 0 ms
+
+
+
+ false
+ 0 ms
+
+
+
+ false
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ false
+ false
+
+
+
+ false
+ 0 ns
+ false
+
+
+
+ true
+ 5 s
+
+
+
+ true
+ 1 s
+ false
+
+
+
+ true
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ false
+ forRemoval
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 20 ms
+
+ 20 ms
+
+ 20 ms
+
+ false
+
+
+
diff --git a/src/jdk.jfr/share/conf/jfr/gc_details.jfc b/src/jdk.jfr/share/conf/jfr/gc_details.jfc
new file mode 100755
index 000000000000..cd8cf0660f45
--- /dev/null
+++ b/src/jdk.jfr/share/conf/jfr/gc_details.jfc
@@ -0,0 +1,1158 @@
+
+
+
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ true
+
+
+
+ true
+
+
+
+ true
+ true
+ 20 ms
+
+
+
+ true
+ true
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ true
+ true
+ 20 ms
+
+
+
+ true
+ true
+ 20 ms
+
+
+
+ true
+ true
+ 20 ms
+
+
+
+ true
+ true
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
+ true
+ true
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+
+
+
+ false
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ 20 ms
+
+
+
+ false
+ 20 ms
+
+
+
+ true
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ false
+ 10 ms
+
+
+
+ true
+ 10 ms
+
+
+
+ true
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ false
+
+
+
+ true
+ false
+
+
+
+ true
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+ true
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ false
+
+
+
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+ true
+ 0 ns
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ 1000 ms
+
+
+
+ false
+ beginChunk
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ 1000 ms
+
+
+
+ false
+ 60 s
+
+
+
+ false
+
+
+
+ false
+
+
+
+ true
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ everyChunk
+
+
+
+ true
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ 30 s
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ endChunk
+
+
+
+ true
+ true
+
+
+
+ true
+ 5 s
+
+
+
+ true
+ 10 s
+
+
+
+ true
+ beginChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ everyChunk
+
+
+
+ true
+ true
+
+
+
+ true
+ true
+
+
+
+ true
+ 150/s
+ true
+
+
+
+ true
+ everyChunk
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ false
+ 0 ms
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+ 20 ms
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ true
+ beginChunk
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ false
+ false
+
+
+
+ true
+ true
+
+
+
+ true
+ true
+
+
+
+ true
+ 1000 ms
+
+
+
+ true
+
+
+
+ true
+
+
+
+ false
+ 0 ns
+
+
+
+ true
+
+
+
+ true
+
+
+
+ true
+ true
+ 0 ms
+
+
+
+ true
+ true
+ 1 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ true
+ 0 ms
+
+
+
+ false
+ false
+
+
+
+ true
+ 0 ns
+ true
+
+
+
+ true
+ 5 s
+
+
+
+ true
+ 1 s
+ true
+
+
+
+ true
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ endChunk
+
+
+
+ false
+ false
+ forRemoval
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 20 ms
+
+ 20 ms
+
+ 20 ms
+
+ false
+
+
+
diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/AddSapMachineTools.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/AddSapMachineTools.java
new file mode 100644
index 000000000000..f3ab07251473
--- /dev/null
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/AddSapMachineTools.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2025 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package jdk.tools.jlink.internal.plugins;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import jdk.tools.jlink.internal.ExecutableImage;
+import jdk.tools.jlink.internal.Platform;
+import jdk.tools.jlink.internal.PostProcessor;
+import jdk.tools.jlink.plugin.PluginException;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolBuilder;
+
+/**
+ * Adds tools that are SapMachine specific
+ */
+public class AddSapMachineTools extends AbstractPlugin implements PostProcessor {
+
+ public AddSapMachineTools() {
+ super("add-sapmachine-tools");
+ }
+
+ @Override
+ public Category getType() {
+ return Category.ADDER;
+ }
+
+ @Override
+ public boolean hasArguments() {
+ return false;
+ }
+
+ @Override
+ public boolean hasRawArgument() {
+ return false;
+ }
+
+ private final String[] tools = {
+ "bin/asprof",
+ "lib/" + System.mapLibraryName("asyncProfiler"),
+ "lib/async-profiler.jar",
+ "legal/async/CHANGELOG.md",
+ "legal/async/LICENSE",
+ "legal/async/README.md"
+ };
+
+ @Override
+ public List process(ExecutableImage image) {
+ var targetPlatform = image.getTargetPlatform();
+ var runtimePlatform = Platform.runtime();
+
+ if (!targetPlatform.equals(runtimePlatform)) {
+ throw new PluginException("Cannot add SapMachine tools: target image platform " +
+ targetPlatform.toString() + " is different from runtime platform " +
+ runtimePlatform.toString());
+ }
+
+ var sourceJavaHome = Path.of(System.getProperty("java.home"));
+ var targetJavaHome = image.getHome();
+
+ for (String tool : tools) {
+ var path = sourceJavaHome.resolve(tool);
+ var target = targetJavaHome.resolve(tool);
+ if (Files.exists(path)) {
+ try {
+ Files.createDirectories(target.getParent());
+ Files.copy(path, target);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
+ return in;
+ }
+}
diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties
index 7e3c26fa7b8b..75a43bc09cc6 100644
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties
@@ -289,6 +289,13 @@ Invalid language tag: %s
include-locales.localedatanotfound=\
jdk.localedata module was not specified with --add-modules option
+# SapMachine 2025-09-01: SapMachine tools plugin
+add-sapmachine-tools.description=\
+Add SapMachine specific tools to the image.
+
+add-sapmachine-tools.usage=\
+\ --add-sapmachine-tools Add SapMachine specific tools to the image.
+
main.status.ok=Functional.
main.status.not.ok= Not functional.
diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties
index c313d6a348d3..5fa59a8f742d 100644
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins_de.properties
@@ -158,6 +158,11 @@ include-locales.invalidtag=Ungültiges Sprachtag: %s
include-locales.localedatanotfound=Modul jdk.localedata wurde mit der Option --add-modules nicht angegeben
+# SapMachine 2025-09-01: SapMachine tools plugin
+add-sapmachine-tools.description=Fügt SapMachine-spezifische Tools zum Image hinzu.
+
+add-sapmachine-tools.usage=\ --add-sapmachine-tools Fügt SapMachine-spezifische Tools zum Image hinzu.
+
main.status.ok=Funktional.
main.status.not.ok= Nicht funktional.
diff --git a/src/jdk.jlink/share/classes/module-info.java b/src/jdk.jlink/share/classes/module-info.java
index ba66da536048..5e876c879c28 100644
--- a/src/jdk.jlink/share/classes/module-info.java
+++ b/src/jdk.jlink/share/classes/module-info.java
@@ -81,5 +81,7 @@
jdk.tools.jlink.internal.plugins.VendorVMBugURLPlugin,
jdk.tools.jlink.internal.plugins.VendorVersionPlugin,
jdk.tools.jlink.internal.plugins.CDSPlugin,
+ // SapMachine 2025-01-09: SapMachine tools plugin
+ jdk.tools.jlink.internal.plugins.AddSapMachineTools,
jdk.tools.jlink.internal.plugins.SaveJlinkArgfilesPlugin;
}
diff --git a/src/jdk.sapext/share/classes/com/sap/jdk/ext/process/ProcessGroupHelper.java b/src/jdk.sapext/share/classes/com/sap/jdk/ext/process/ProcessGroupHelper.java
new file mode 100644
index 000000000000..be7f3003f4fa
--- /dev/null
+++ b/src/jdk.sapext/share/classes/com/sap/jdk/ext/process/ProcessGroupHelper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.sap.jdk.ext.process;
+
+import java.io.IOException;
+
+import jdk.internal.access.JavaLangProcessAccess;
+import jdk.internal.access.JavaLangProcessBuilderAccess;
+import jdk.internal.access.SharedSecrets;
+
+/**
+ * ProcessGroupHelper provides the possibility to create and terminate process groups.
+ */
+public final class ProcessGroupHelper {
+
+ private static JavaLangProcessAccess jlpa = SharedSecrets.getJavaLangProcessAccess();
+ private static JavaLangProcessBuilderAccess jlpba = SharedSecrets.getJavaLangProcessBuilderAccess();
+
+ /**
+ * With this API a ProcessBuilder instance can be configured to create a new process group upon spawning the process.
+ *
+ * @param pb The ProcessBuilder that shall be configured.
+ * @param value true if a new process group shall be created, false otherwise.
+ */
+ public static final void createNewProcessGroupOnSpawn(ProcessBuilder pb, boolean value) {
+ jlpba.createNewProcessGroupOnSpawn(pb, value);
+ }
+
+ /**
+ * Given a process supposed to be leader of a process group, attempts to kill that process group.
+ *
+ * @param pid Process id of the process leading the process group.
+ * @param force Kill forcibly or politely.
+ *
+ * @throws IOException Process not alive or not a process group leader (safemode = true); Operation failed.
+ * @throws UnsupportedOperationException Operation not supported on this platform
+ */
+ public static final void terminateProcessGroupForLeader(Process p, boolean force) throws IOException {
+ jlpa.destroyProcessGroup(p, force);
+ }
+
+ private ProcessGroupHelper() {
+ // don't instantiate
+ }
+}
diff --git a/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/Console.java b/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/Console.java
new file mode 100644
index 000000000000..2fb65fc2530a
--- /dev/null
+++ b/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/Console.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.sap.jdk.ext.util;
+
+import java.lang.annotation.Native;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * This class provides methods for console handling.
+ */
+@SuppressWarnings({"removal", "restricted"})
+public final class Console {
+
+ /**
+ * The base name of the jdk extensions library.
+ */
+ private static final String LIB_BASE_NAME = "jdksapext";
+
+ static {
+ if (System.getSecurityManager() == null) {
+ System.loadLibrary(LIB_BASE_NAME);
+ } else {
+ AccessController.doPrivileged((PrivilegedAction) () -> {
+ System.loadLibrary(LIB_BASE_NAME);
+ return null;
+ });
+ }
+ }
+
+ /**
+ * Enumeration of modes available for calls to {@link Console#setMode(Mode)}
+ */
+ public enum Mode {
+
+ /**
+ * This will restore the original console mode,
+ * if it had been changed before by {@link Console#setMode(Mode)}
+ */
+ DEFAULT,
+
+ /**
+ * This sets the console to non canonical mode, allowing single character input.
+ */
+ NON_CANONICAL
+ }
+
+ // values that are used in the native implementation
+ @Native private static final int MODE_DEFAULT = 0;
+ @Native private static final int MODE_NON_CANONICAL = 1;
+
+ /**
+ * Operation that moves to the beginning of the buffer (ctrl-a).
+ */
+ @Native public static final short CHR_MOVE_TO_BEG = 1;
+
+ /**
+ * Operation that exits the command prompt.
+ */
+ @Native public static final short CHR_CANCEL = 3;
+
+ /**
+ * Operation that exits the command prompt.
+ */
+ @Native public static final short CHR_EXIT = 4;
+
+ /**
+ * Operation that moves to the end of the buffer (ctrl-e).
+ */
+ @Native public static final short CHR_MOVE_TO_END = 5;
+
+ /**
+ * Operation that issues a backspace.
+ */
+ @Native public static final short CHR_DELETE_PREV_CHAR = 8;
+
+ /**
+ * Operation that performs completion operation on the current word.
+ */
+ @Native public static final short CHR_COMPLETE = 9;
+
+ /**
+ * Operation that issues a newline.
+ */
+ @Native public static final short CHR_NEWLINE_1 = 10;
+
+ /**
+ * Operation that deletes the buffer from the current character to the end (ctrl-k).
+ */
+ @Native public static final short CHR_KILL_LINE = 11;
+
+ /**
+ * Operation that issues a newline.
+ */
+ @Native public static final short CHR_NEWLINE_2 = 13;
+
+ /**
+ * Operation that clears whatever text is on the current line (ESC).
+ */
+ @Native public static final short CHR_CLEAR_LINE = 27;
+
+ /**
+ * Operation that issues a delete (DEL).
+ */
+ @Native public static final short CHR_DELETE_NEXT_CHAR = 127;
+
+ /**
+ * Operation that moved to the previous character in the buffer (arrow left)
+ */
+ @Native public static final short CHR_PREV_CHAR = 331;
+
+ /**
+ * Operation that moves to the next character in the buffer (arrow right).
+ */
+ @Native public static final short CHR_NEXT_CHAR = 333;
+
+ /**
+ * Operation that sets the buffer to the next history item (arrow down).
+ */
+ @Native public static final short CHR_NEXT_HISTORY = 336;
+
+ /**
+ * Operation that sets the buffer to the previous history item (arrow up).
+ */
+ @Native public static final short CHR_PREV_HISTORY = 328;
+
+ /**
+ * The console object.
+ */
+ private static Console console = new Console();
+
+ // objects used for synchronizing calls
+ private static Object modeLock = new Object();
+ private static Object readLock = new Object();
+ private static Object queryLock = new Object();
+
+ /**
+ * Private constructor to prohibit instantiation.
+ */
+ private Console() {}
+
+ // native methods
+ private static native void setMode0(int mode);
+ private static native int readChar0();
+ private static native int getWidth0();
+
+ /**
+ * Sets the console mode to one of the values of {@link Mode}.
+ *
+ * @param mode The mode to set.
+ */
+ public void setMode(Mode mode) {
+ synchronized (modeLock) {
+ switch (mode) {
+ case DEFAULT:
+ setMode0(MODE_DEFAULT);
+ return;
+ case NON_CANONICAL:
+ setMode0(MODE_NON_CANONICAL);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Reads a character from the console
+ *
+ * @return the character read from the console
+ */
+ public int readChar() {
+ synchronized (readLock) {
+ return readChar0();
+ }
+ }
+
+ /**
+ * Returns the console width or -1 if not available.
+ *
+ * @return the console width or -1 if not available.
+ */
+ public int getWidth() {
+ synchronized (queryLock) {
+ return getWidth0();
+ }
+ }
+
+ /**
+ * Returns the console instance
+ *
+ * @return the console instance
+ */
+ public static Console get() {
+ return console;
+ }
+}
diff --git a/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/ZipfsUtils.java b/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/ZipfsUtils.java
new file mode 100644
index 000000000000..b319a0ae3300
--- /dev/null
+++ b/src/jdk.sapext/share/classes/com/sap/jdk/ext/util/ZipfsUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2026 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+ package com.sap.jdk.ext.util;
+
+import java.nio.file.Path;
+
+import jdk.internal.access.JdkNioZipfsAccess;
+import jdk.internal.access.SharedSecrets;
+
+public class ZipfsUtils {
+
+ // Silence warning about implicit ctor.
+ private ZipfsUtils() {
+ }
+
+ /**
+ * Returns true if the given path is a jdk.nio.zipfs.ZipPath which
+ * represents a symbolic link.
+ *
+ * @param path The path in the zipfs.
+ * @return true if the path represents a symbolic link.
+ */
+ public static boolean isSymbolicLink(Path path) {
+ JdkNioZipfsAccess access = SharedSecrets.getJdkNioZipfsAccess();
+
+ if (access == null) {
+ return false;
+ }
+
+ return access.isSymbolicLink(path);
+ }
+}
diff --git a/src/jdk.sapext/share/classes/module-info.java b/src/jdk.sapext/share/classes/module-info.java
new file mode 100644
index 000000000000..2e53ce0344b2
--- /dev/null
+++ b/src/jdk.sapext/share/classes/module-info.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/**
+ * Exposes SapMachine only functionality.
+ *
+ * @moduleGraph
+ */
+module jdk.sapext {
+ exports com.sap.jdk.ext.process;
+ exports com.sap.jdk.ext.util;
+}
diff --git a/src/jdk.sapext/unix/native/libjdksapext/Console.c b/src/jdk.sapext/unix/native/libjdksapext/Console.c
new file mode 100644
index 000000000000..183c61c5c27c
--- /dev/null
+++ b/src/jdk.sapext/unix/native/libjdksapext/Console.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include "com_sap_jdk_ext_util_Console.h"
+
+#include
+#include
+#include
+#include
+
+struct termios consoleMode, *consoleModePtr = NULL;
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: setMode0
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_com_sap_jdk_ext_util_Console_setMode0
+ (JNIEnv *env, jclass cls, jint mode)
+{
+ if (!isatty(STDIN_FILENO)) {
+ return;
+ }
+
+ if (mode == com_sap_jdk_ext_util_Console_MODE_NON_CANONICAL) {
+ if (consoleModePtr == NULL) {
+ if (tcgetattr(STDIN_FILENO, &consoleMode) < 0) {
+ return;
+ }
+ consoleModePtr = &consoleMode;
+ }
+
+ struct termios newTermState = consoleMode;
+ newTermState.c_lflag &= ~(ECHO | ICANON);
+ newTermState.c_cc [VMIN] = 1;
+ newTermState.c_cc [VTIME] = 1;
+ tcsetattr(STDIN_FILENO, TCSANOW, &newTermState);
+ } else if (mode == com_sap_jdk_ext_util_Console_MODE_DEFAULT) {
+ if (consoleModePtr == NULL) {
+ return;
+ }
+
+ tcsetattr(STDIN_FILENO, TCSANOW, consoleModePtr);
+ }
+}
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: readChar0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_sap_jdk_ext_util_Console_readChar0
+ (JNIEnv *env, jclass cls)
+{
+ // in Unix terminals, arrow keys are represented by a sequence of 3 characters. E.g., the up arrow key yields 27, 91, 68
+ int c = getc(stdin);
+ if (c == 27) {
+ c = getc(stdin);
+ if (c == 91) {
+ c = getc(stdin);
+ switch (c) {
+ case 65: return com_sap_jdk_ext_util_Console_CHR_PREV_HISTORY; /* Map arrow keys */
+ case 66: return com_sap_jdk_ext_util_Console_CHR_NEXT_HISTORY;
+ case 67: return com_sap_jdk_ext_util_Console_CHR_NEXT_CHAR;
+ case 68: return com_sap_jdk_ext_util_Console_CHR_PREV_CHAR;
+ case 51: c = getc(stdin); /* Map DEL key */
+ if (c == 126) {
+ return com_sap_jdk_ext_util_Console_CHR_DELETE_NEXT_CHAR;
+ }
+ break;
+ case 72: return com_sap_jdk_ext_util_Console_CHR_MOVE_TO_BEG; /* Map Pos1 to Ctrl-A */
+ case 70: return com_sap_jdk_ext_util_Console_CHR_MOVE_TO_END; /* Map End to Ctrl-E */
+ }
+ }
+ } else if (c == 127) {
+ c = com_sap_jdk_ext_util_Console_CHR_DELETE_PREV_CHAR; /* Map Backspace */
+ }
+ return c;
+}
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: getConsoleWidth0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_sap_jdk_ext_util_Console_getWidth0
+ (JNIEnv *env, jclass cls)
+{
+#if defined(TIOCGWINSZ)
+ struct winsize w;
+
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &w) == 0 || ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0 || ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) {
+ return w.ws_col;
+ }
+#endif
+ return -1;
+}
diff --git a/src/jdk.sapext/windows/native/libjdksapext/Console.c b/src/jdk.sapext/windows/native/libjdksapext/Console.c
new file mode 100644
index 000000000000..398b100fd10f
--- /dev/null
+++ b/src/jdk.sapext/windows/native/libjdksapext/Console.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018, 2024 SAP SE. 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include "com_sap_jdk_ext_util_Console.h"
+
+#include
+#include
+
+static DWORD consoleMode = -1;
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: setMode0
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL Java_com_sap_jdk_ext_util_Console_setMode0
+ (JNIEnv *env, jclass cls, jint mode)
+{
+ HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE);
+
+ if (hConsole == INVALID_HANDLE_VALUE) {
+ return;
+ }
+
+ if (mode == com_sap_jdk_ext_util_Console_MODE_NON_CANONICAL) {
+ if (consoleMode == -1) {
+ if (!GetConsoleMode(hConsole, &consoleMode)) {
+ return;
+ }
+ }
+
+ SetConsoleMode(hConsole, consoleMode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT));
+ } else if (mode == com_sap_jdk_ext_util_Console_MODE_DEFAULT) {
+ if (consoleMode == -1) {
+ return;
+ }
+
+ SetConsoleMode(hConsole, consoleMode);
+ }
+}
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: readChar0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_sap_jdk_ext_util_Console_readChar0
+ (JNIEnv *env, jclass cls)
+{
+ int c = getch();
+ if (c == 224) {
+ c = getch();
+ switch (c) {
+ case 71: return com_sap_jdk_ext_util_Console_CHR_MOVE_TO_BEG; /* Map Pos1 to Ctrl-A */
+ case 79: return com_sap_jdk_ext_util_Console_CHR_MOVE_TO_END; /* Map End to Ctrl-E */
+ case 83: return com_sap_jdk_ext_util_Console_CHR_DELETE_NEXT_CHAR; /* Maps DEL */
+ default: return c += 256; /* Arrow keys */
+ }
+ }
+ return c;
+}
+
+/*
+ * Class: com_sap_jdk_ext_util_Console
+ * Method: getConsoleWidth0
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_com_sap_jdk_ext_util_Console_getWidth0
+ (JNIEnv *env, jclass cls)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
+ if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &csbi)) {
+ return -1;
+ }
+ }
+
+ return csbi.dwSize.X;
+}
diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
index 0280f7c33bb4..be4725214af0 100644
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
@@ -3649,4 +3649,17 @@ public boolean equals(Object other) {
oname, 0, oname.length);
}
}
+
+ // SapMachine 2026-04-14: Returns true if the resolved path points to a symnbolic link.
+ boolean isSymlink(byte[] path) {
+ IndexNode inode = getInode(path);
+
+ if ((inode != null) && (inode.pos != -1)) {
+ long attrEx = ZipConstants.CENATX(cen, inode.pos);
+
+ return (attrEx & 0xF0000000L) == 0xA0000000L;
+ }
+
+ return false;
+ }
}
diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java
index adfa975c1c37..5e82833a1f61 100644
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java
@@ -62,6 +62,25 @@ final class ZipPath implements Path {
private volatile int[] offsets;
private int hashcode = 0; // cached hashcode (created lazily)
+ // SapMachine 2026-04-14: Support for symlink detection.
+ static class JdkNioZipfsAccessImpl implements jdk.internal.access.JdkNioZipfsAccess {
+ public boolean isSymbolicLink(Path path) {
+ if (!(path instanceof ZipPath)) {
+ return false;
+ }
+
+ ZipPath zipPath = (ZipPath) path;
+ byte[] resolvedPath = zipPath.getResolvedPath();
+
+ return zipPath.zfs.isSymlink(resolvedPath);
+ }
+ }
+
+ // SapMachine 2026-04-14: Support for symlink detection.
+ static {
+ jdk.internal.access.SharedSecrets.setJdkNioZipfsAccess(new JdkNioZipfsAccessImpl());
+ }
+
ZipPath(ZipFileSystem zfs, byte[] path) {
this(zfs, path, false);
}
diff --git a/test/hotspot/gtest/vitals/test_vitals.cpp b/test/hotspot/gtest/vitals/test_vitals.cpp
new file mode 100644
index 000000000000..b1c7f902602c
--- /dev/null
+++ b/test/hotspot/gtest/vitals/test_vitals.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, 2025 SAP SE. 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.
+ *
+ */
+
+#include "runtime/globals.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/ostream.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "unittest.hpp"
+#include "vitals/vitals.hpp"
+
+//#define LOG(s) tty->print_raw(s);
+#define LOG(s)
+
+TEST_VM(vitals, report_with_explicit_default_settings) {
+ char tmp[64*K];
+ stringStream ss(tmp, sizeof(tmp));
+ sapmachine_vitals::print_info_t info;
+ ::memset(&info, 0xBB, sizeof(info));
+ sapmachine_vitals::default_settings(&info);
+ sapmachine_vitals::print_report(&ss, &info);
+ LOG(tmp);
+ if (EnableVitals) {
+ ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)nullptr);
+ }
+}
+
+TEST_VM(vitals, report_with_implicit_default_settings) {
+ char tmp[64*K];
+ stringStream ss(tmp, sizeof(tmp));
+ sapmachine_vitals::print_report(&ss, nullptr);
+ LOG(tmp);
+ if (EnableVitals) {
+ ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)nullptr);
+ }
+}
+
+TEST_VM(vitals, report_with_nownow) {
+ char tmp[64*K];
+ stringStream ss(tmp, sizeof(tmp));
+ sapmachine_vitals::print_info_t info;
+ ::memset(&info, 0xBB, sizeof(info));
+ sapmachine_vitals::default_settings(&info);
+ info.sample_now = true;
+ for (int i = 0; i < 100; i ++) {
+ ss.reset();
+ sapmachine_vitals::print_report(&ss, nullptr);
+ if (EnableVitals) {
+ ASSERT_NE(::strstr(tmp, "--jvm--"), (char*)nullptr);
+ }
+ }
+}
diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt
index 422e6ddd0600..8329026e7a20 100644
--- a/test/hotspot/jtreg/ProblemList.txt
+++ b/test/hotspot/jtreg/ProblemList.txt
@@ -95,6 +95,8 @@ gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java#default 8382335
# :hotspot_runtime
+# SapMachine 2022-12-16 Exclude a failing test
+runtime/jni/daemonDestroy/TestDaemonDestroy.java windows-all
runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64
runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x
runtime/os/TestTracePageSizes.java#no-options 8267460 linux-aarch64
@@ -145,6 +147,11 @@ serviceability/jvmti/stress/StackTrace/NotSuspended/GetStackTraceNotSuspendedStr
#############################################################################
+# SapMachine 2023-11-20 These make trouble in AIX CI
+gtest/GTestWrapper.java aix-ppc64
+gtest/MetaspaceGtests.java#balanced-no-ccs aix-ppc64
+gtest/MetaspaceGtests.java#balanced-with-guards aix-ppc64
+gtest/MetaspaceGtests.java#default-debug aix-ppc64
#############################################################################
diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT
index 9717da055220..95271f8e0a90 100644
--- a/test/hotspot/jtreg/TEST.ROOT
+++ b/test/hotspot/jtreg/TEST.ROOT
@@ -56,8 +56,10 @@ requires.extraPropDefns.vmOpts = \
-XX:+WhiteBoxAPI \
--add-exports java.base/jdk.internal.foreign=ALL-UNNAMED \
--add-exports java.base/jdk.internal.misc=ALL-UNNAMED
+# SapMachine 2025-06-11: reduce number of cds/jsa archives
requires.properties= \
sun.arch.data.model \
+ vm.vendor \
vm.simpleArch \
vm.bits \
vm.flightRecorder \
diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups
index e09235f6a390..ae328cc4447a 100644
--- a/test/hotspot/jtreg/TEST.groups
+++ b/test/hotspot/jtreg/TEST.groups
@@ -351,7 +351,10 @@ hotspot_gc_shenandoah = \
:tier2_gc_shenandoah \
:tier3_gc_shenandoah
+# SapMachine 2019-02-24 : Add tests where SAPMachine has different behavior to tier1,
+# or which tests downstream-only features.
tier1_runtime = \
+ :tier1_sapmachine \
runtime/ \
-runtime/6626217/bug_21227.java \
-runtime/7100935 \
@@ -627,6 +630,12 @@ tier1_serviceability = \
-serviceability/sa/TestJmapCore.java \
-serviceability/sa/TestJmapCoreMetaspace.java
+# SapMachine 2019-02-24 : Add tests where SAPMachine has different behavior to tier1,
+# or which tests downstream-only features.
+tier1_sapmachine = \
+ runtime/Vitals \
+ runtime/malloctrace
+
tier1 = \
:tier1_common \
:tier1_compiler \
diff --git a/test/hotspot/jtreg/compiler/codecache/CheckSegmentedCodeCache.java b/test/hotspot/jtreg/compiler/codecache/CheckSegmentedCodeCache.java
index a99f6b83cc63..db301d9dce36 100644
--- a/test/hotspot/jtreg/compiler/codecache/CheckSegmentedCodeCache.java
+++ b/test/hotspot/jtreg/compiler/codecache/CheckSegmentedCodeCache.java
@@ -231,12 +231,13 @@ public static void main(String[] args) throws Exception {
// Reserved code cache is set, NonNmethod segment size is set, two other segments is automatically
// adjusted to half of the remaining space
+ // SapMachine 2025-12-10 We support 2MB transparent huge pages, so we need a multiple of it.
pb = ProcessTools.createLimitedTestJavaProcessBuilder("-XX:+SegmentedCodeCache",
- "-XX:ReservedCodeCacheSize=100M",
+ "-XX:ReservedCodeCacheSize=102M", // original: 100M
"-XX:NonNMethodCodeHeapSize=10M",
"-XX:+PrintFlagsFinal",
"-version");
- verifyCodeHeapSize(pb, " ProfiledCodeHeapSize", 47185920);
+ verifyCodeHeapSize(pb, " ProfiledCodeHeapSize", 48234496); // = 46M, original: 47185920
// Reserved code cache is set but NonNmethodCodeHeapSize is not set.
// It's calculated based on the number of compiler threads
diff --git a/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/GenericCodeHeapSizeRunner.java b/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/GenericCodeHeapSizeRunner.java
index aa7e9e67f373..d1aa9393892e 100644
--- a/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/GenericCodeHeapSizeRunner.java
+++ b/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/GenericCodeHeapSizeRunner.java
@@ -44,7 +44,8 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
Long.toString(expectedValues.reserved),
String.format("%s should have value %d.",
BlobType.All.sizeOptionName, expectedValues.reserved),
- testCaseDescription.getTestOptions(options));
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ testCaseDescription.getTestOptions(options, "-XX:-UseLargePages"));
CommandLineOptionTest.verifyOptionValueForSameVM(
BlobType.NonNMethod.sizeOptionName,
@@ -52,7 +53,8 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
String.format("%s should have value %d.",
BlobType.NonNMethod.sizeOptionName,
expectedValues.nonNmethods),
- testCaseDescription.getTestOptions(options));
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ testCaseDescription.getTestOptions(options, "-XX:-UseLargePages"));
CommandLineOptionTest.verifyOptionValueForSameVM(
BlobType.MethodNonProfiled.sizeOptionName,
@@ -60,7 +62,8 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
String.format("%s should have value %d.",
BlobType.MethodNonProfiled.sizeOptionName,
expectedValues.nonProfiled),
- testCaseDescription.getTestOptions(options));
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ testCaseDescription.getTestOptions(options, "-XX:-UseLargePages"));
CommandLineOptionTest.verifyOptionValueForSameVM(
BlobType.MethodProfiled.sizeOptionName,
@@ -68,7 +71,8 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
String.format("%s should have value %d.",
BlobType.MethodProfiled.sizeOptionName,
expectedValues.profiled),
- testCaseDescription.getTestOptions(options));
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ testCaseDescription.getTestOptions(options, "-XX:-UseLargePages"));
CommandLineOptionTest.verifyOptionValueForSameVM(
BlobType.MethodHot.sizeOptionName,
@@ -76,6 +80,7 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
String.format("%s should have value %d.",
BlobType.MethodHot.sizeOptionName,
expectedValues.hot),
- testCaseDescription.getTestOptions(options));
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ testCaseDescription.getTestOptions(options, "-XX:-UseLargePages"));
}
}
diff --git a/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/JVMStartupRunner.java b/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/JVMStartupRunner.java
index e5507efcee3f..9b8305e86711 100644
--- a/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/JVMStartupRunner.java
+++ b/test/hotspot/jtreg/compiler/codecache/cli/codeheapsize/JVMStartupRunner.java
@@ -48,7 +48,8 @@ public void run(CodeCacheCLITestCase.Description testCaseDescription,
new String[]{ INCONSISTENT_CH_SIZES_ERROR },
"JVM startup should not fail with consistent code heap sizes",
"JVM output should not contain warning about inconsistent code "
- + "heap sizes", ExitCode.OK, options.prepareOptions());
+ // SapMachine 2025-12-10 We don't get exact matches when rounding to large page sizes.
+ + "heap sizes", ExitCode.OK, options.prepareOptions("-XX:-UseLargePages"));
verifySingleInconsistentValue(options);
verifyAllInconsistentValues(options);
diff --git a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
index fdcc35685d2f..19438cba0c72 100644
--- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
+++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java
@@ -121,8 +121,10 @@ private static void do_test(boolean CDS) throws IOException {
}
public static void main(String[] args) throws Exception {
- System.out.println("Test with CDS");
- do_test(true);
+ // SapMachine 2025-06-11: reduce number of cds/jsa archives
+ // -Xshare:on can only be used for a configuration for which we have a jsa cds archive.
+ //System.out.println("Test with CDS");
+ //do_test(true);
System.out.println("Test without CDS");
do_test(false);
}
diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java
index 61d017d22648..ea66863f07e1 100644
--- a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java
+++ b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java
@@ -40,6 +40,8 @@
/*
* @test id=no_coh_cds
* @summary Test that dereferencing a Klass that is the result of a decode(0) crashes accessing the nKlass guard zone
+ * @comment SapMachine 2025-06-11: reduce number of cds/jsa archives, test would fail
+ * @requires vm.vendor != "SAP SE"
* @requires vm.cds & vm.bits == 64 & vm.debug == true & vm.flagless
* @requires os.family != "aix"
* @comment This test relies on crashing which conflicts with ASAN checks
diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestExitOnDirectOutOfMemoryError.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestExitOnDirectOutOfMemoryError.java
new file mode 100644
index 000000000000..27fbf7c5b909
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestExitOnDirectOutOfMemoryError.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022 THL A29 Limited, a Tencent company. 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 TestExitOnDirectOutOfMemoryError
+ * @summary Test using -XX:ExitOnOutOfMemoryError
+ * @modules java.base/jdk.internal.misc
+ * @library /test/lib
+ * @requires vm.flagless
+ * @run driver TestExitOnDirectOutOfMemoryError
+ */
+
+import java.nio.ByteBuffer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.process.OutputAnalyzer;
+
+public class TestExitOnDirectOutOfMemoryError {
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 1) {
+ // This should guarantee to throw:
+ // java.lang.OutOfMemoryError: Cannot reserve 2147483647 bytes of direct buffer memory (allocated: 0, limit: 10485760)
+ try {
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
+ } catch (OutOfMemoryError err) {
+ throw new Error("OOME didn't terminate JVM!");
+ }
+ }
+
+ // else this is the main test
+ ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder("-XX:+ExitOnOutOfMemoryError",
+ "-Xmx20m", "-XX:MaxDirectMemorySize=10m", "-Djdk.nio.reportErrorOnDirectMemoryOom=true",
+ TestExitOnDirectOutOfMemoryError.class.getName(), "throwOOME");
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+
+ /*
+ * Actual output should look like this:
+ * Terminating due to java.lang.OutOfMemoryError: Cannot reserve 2147483647 bytes of direct buffer memory (allocated: 0, limit: 10485760)
+ */
+ output.shouldHaveExitValue(3);
+ output.stdoutShouldNotBeEmpty();
+ output.shouldContain("Terminating due to java.lang.OutOfMemoryError: Cannot reserve 2147483647 bytes of direct" +
+ " buffer memory (allocated: 0, limit: 10485760)");
+ System.out.println("PASSED");
+ }
+}
diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestSAPSpecificOnOutOfMemoryError.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestSAPSpecificOnOutOfMemoryError.java
new file mode 100644
index 000000000000..32b82e53c585
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestSAPSpecificOnOutOfMemoryError.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2023 SAP SE. 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 TestSAPSpecificOnOutOfMemoryError
+ * @summary Test SapMachine/SapJVM specific behavior
+ * @modules java.base/jdk.internal.misc
+ * @library /test/lib
+ * @run driver TestSAPSpecificOnOutOfMemoryError
+ */
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import java.util.ArrayList;
+
+public class TestSAPSpecificOnOutOfMemoryError {
+
+ static OutputAnalyzer run_test(String ... vm_args) throws Exception {
+ ArrayList args = new ArrayList<>();
+ for (String s : vm_args) {
+ args.add(s);
+ }
+ args.add("-Xmx128m");
+ args.add(TestSAPSpecificOnOutOfMemoryError.class.getName());
+ args.add("throwOOME");
+ ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(args);
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.reportDiagnosticSummary();
+ int exitValue = output.getExitValue();
+ if (0 == exitValue) {
+ //expecting a non zero value
+ throw new Error("Expected to get non zero exit value");
+ }
+ return output;
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 1) {
+ // This should guarantee to throw:
+ // java.lang.OutOfMemoryError: Requested array size exceeds VM limit
+ Object[] oa = new Object[Integer.MAX_VALUE];
+ return;
+ }
+
+ final String aborting_due = "Aborting due to java.lang.OutOfMemoryError";
+ final String terminating_due = "Terminating due to java.lang.OutOfMemoryError";
+ final String java_frames = "Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)";
+ final String a_fatal_error = "# A fatal error has been detected by the Java Runtime Environment";
+ final String no_core = "CreateCoredumpOnCrash turned off, no core file dumped";
+ final String yes_core_1 = "Core dump will be written."; // If limit > 0
+ final String yes_core_2 = "Core dumps have been disabled."; // if limit == 0. For the purpose of this test this is still okay
+ final String summary_from_hs_err = "S U M M A R Y";
+
+ // CrashOnOutOfMemoryError, without cores explicitly enabled:
+ // - thread stack
+ // - aborting with hs-err file
+ // - no core
+ {
+ OutputAnalyzer output = run_test("-XX:+CrashOnOutOfMemoryError");
+
+ output.shouldContain(aborting_due);
+ output.shouldContain(java_frames);
+ output.shouldContain(a_fatal_error);
+ output.shouldContain(no_core);
+
+ output.shouldNotContain(terminating_due);
+ output.shouldNotContain(yes_core_1);
+ output.shouldNotContain(yes_core_2);
+ }
+
+ // ExitVMOnOutOfMemoryError is a SAP specific alias for CrashOnOutOfMemoryError
+ {
+ OutputAnalyzer output = run_test("-XX:+ExitVMOnOutOfMemoryError");
+
+ output.shouldContain(aborting_due);
+ output.shouldContain(java_frames);
+ output.shouldContain(a_fatal_error);
+ output.shouldContain(no_core);
+
+ output.shouldNotContain(terminating_due);
+ output.shouldNotContain(yes_core_1);
+ output.shouldNotContain(yes_core_2);
+ }
+
+ // CrashOnOutOfMemoryError, with cores explicitly enabled:
+ // - thread stack
+ // - aborting with hs-err file
+ // - core is to be written (or, attempted, if ulimit = 0)
+ {
+ OutputAnalyzer output = run_test("-XX:+CrashOnOutOfMemoryError", "-XX:+CreateCoredumpOnCrash");
+
+ output.shouldContain(aborting_due);
+ output.shouldContain(java_frames);
+ output.shouldContain(a_fatal_error);
+ output.shouldMatch("(" + yes_core_1 + "|" + yes_core_2 + ")");
+
+ output.shouldNotContain(no_core);
+ output.shouldNotContain(terminating_due);
+ }
+
+ // ExitOnOutOfMemoryError should:
+ // - print thread stack
+ // - terminate the VM
+ {
+ OutputAnalyzer output = run_test("-XX:+ExitOnOutOfMemoryError");
+
+ output.shouldContain(terminating_due);
+ output.shouldContain(java_frames);
+
+ output.shouldNotContain(aborting_due);
+ output.shouldNotContain(a_fatal_error);
+ output.shouldNotContain(yes_core_1);
+ output.shouldNotContain(yes_core_2);
+ output.shouldNotContain(no_core);
+ }
+
+ // Test that giving ErrorFileToStdout in combination with CrashOnOutOfMemoryError will
+ // print the hs-err file - including the limits, which may be important here - to stdout
+ {
+ OutputAnalyzer output = run_test("-XX:+CrashOnOutOfMemoryError", "-XX:+ErrorFileToStdout");
+
+ output.shouldContain(aborting_due);
+ output.shouldContain(java_frames);
+ output.shouldContain(a_fatal_error);
+ output.shouldContain(no_core);
+ output.shouldContain(summary_from_hs_err);
+
+ output.shouldNotContain(terminating_due);
+ output.shouldNotContain(yes_core_1);
+ output.shouldNotContain(yes_core_2);
+ }
+
+ // HeapDumpOnOutOfMemoryError should:
+ // - print thread stack
+ // - The VM should just run on. OOM will bubble up and end the program.
+ {
+ OutputAnalyzer output = run_test("-XX:+HeapDumpOnOutOfMemoryError");
+
+ output.shouldContain(java_frames);
+
+ output.shouldNotContain(terminating_due);
+ output.shouldNotContain(aborting_due);
+ output.shouldNotContain(a_fatal_error);
+ output.shouldNotContain(yes_core_1);
+ output.shouldNotContain(yes_core_2);
+ output.shouldNotContain(no_core);
+ }
+ }
+}
diff --git a/test/hotspot/jtreg/runtime/Vitals/CSVParser.java b/test/hotspot/jtreg/runtime/Vitals/CSVParser.java
new file mode 100644
index 000000000000..c7a9227b1fb5
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/Vitals/CSVParser.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2022, SAP SE. 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+public class CSVParser {
+
+ public final static class CSVHeader {
+ final List columns = new ArrayList<>();
+ Hashtable columnPositions = new Hashtable<>();
+
+ int size() {
+ return columns.size();
+ }
+
+ String at(int position) {
+ return columns.get(position);
+ }
+
+ int findColumn(String name) {
+ Integer i = columnPositions.get(name);
+ return (i == null) ? -1 : i;
+ }
+
+ boolean hasColumn(String name) {
+ return findColumn(name) != -1;
+ }
+
+ void addColumn(String name) {
+ if (columnPositions.containsKey(name)) {
+ throw new RuntimeException("Already have column " + name);
+ }
+ columns.add(name);
+ columnPositions.put(name, columns.size() - 1);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bld = new StringBuilder();
+ for (String s : columns) {
+ bld.append(s);
+ bld.append(",");
+ }
+ return bld.toString();
+ }
+ }
+
+ public final static class CSVDataLine {
+ ArrayList data = new ArrayList<>();
+
+ int size() {
+ return data.size();
+ }
+
+ String at(int position) {
+ return data.get(position);
+ }
+
+ boolean isEmpty(int position) {
+ String s = at(position);
+ return s == null || s.isEmpty() || s.equals("?");
+ }
+
+ long numberAt(int position) throws NumberFormatException {
+ if (isEmpty(position)) {
+ throw new RuntimeException("no data at position " + position);
+ }
+ return Long.parseLong(at(position));
+ }
+
+ void addData(String s) {
+ // If data was surrounded by quotes, remove quotes
+ if (s.startsWith("\"") && s.endsWith("\"")) {
+ s = s.substring(1, s.length() - 1);
+ }
+ s = s.trim();
+ data.add(s);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder bld = new StringBuilder();
+ for (String s : data) {
+ bld.append(s);
+ bld.append(",");
+ }
+ return bld.toString();
+ }
+ }
+
+ public final static class CSV {
+ public CSVHeader header;
+ public CSVDataLine[] lines;
+
+ // Convenience function. Given a column name and a data line number, return value for that column in that line
+ String getContentOfCell(String columnname, int lineno) {
+ return lines[lineno].at(header.findColumn(columnname));
+ }
+
+ long getContentOfCellAsNumber(String columnname, int lineno) {
+ return Long.parseLong(getContentOfCell(columnname, lineno));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CSV: " + header.size() + " columns, " + lines.length + " data lines.\n");
+ builder.append(header);
+ builder.append("\n");
+ for (CSVDataLine line : lines) {
+ builder.append(line);
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+ }
+
+ public static class CSVParseException extends Exception {
+ public CSVParseException(String message) {
+ super("CSV parse error " + ": " + message);
+ }
+ public CSVParseException(String message, int errorLine) {
+ super("CSV parse error at line " + errorLine + ": " + message);
+ }
+ }
+
+ /**
+ * Parses the given lines as CSV. The first lines must be the header, all subsequent lines
+ * valid data.
+ * @param lines
+ * @return
+ */
+ public static final CSV parseCSV(String [] lines) throws CSVParseException {
+
+ if (lines.length < 2) {
+ throw new CSVParseException("Not enough data", -1);
+ }
+
+ System.out.println("--- CSV parser input: ---");
+ for (String s : lines) {
+ System.out.println(s);
+ }
+ System.out.println("--- /CSV parser input: ---");
+
+ int lineno = 0;
+ CSVHeader header = new CSVHeader();
+ ArrayList datalines = new ArrayList<>();
+
+ try {
+ // Parse header line
+ String[] parts = lines[lineno].split(",");
+ for (String s : parts) {
+ header.addColumn(s);
+ }
+
+ lineno ++;
+
+ // Parse Data
+ while (lineno < lines.length) {
+ CSVDataLine dataLine = new CSVDataLine();
+ parts = lines[lineno].split(",");
+ if (parts.length == 1 && parts[0].isEmpty()) {
+ // We start a new section here. Skip the rest.
+ break;
+ }
+ for (String s : parts) {
+ dataLine.addData(s);
+ }
+ if (dataLine.size() != header.size()) {
+ // We have more or less data than columns. Print some helpful message to stderr, then abort.
+ String s = "Line " + lineno + ": expected " + header.size() + " entries, found " + dataLine.size() + ".";
+ System.err.println(s);
+ System.err.println("Header: " + header.toString());
+ System.err.println("Data: " + dataLine.toString());
+ for (int i = 0; i < header.size() || i < dataLine.size(); i ++) {
+ String col = (i < header.size() ? header.at(i) : "");
+ String dat = (i < dataLine.size() ? dataLine.at(i) : "");
+ System.err.println("pos: " + i + " column: " + col + " data: " + dat);
+ }
+ throw new CSVParseException(s, lineno);
+ }
+ datalines.add(dataLine);
+ lineno ++;
+ }
+
+ } catch (Exception e) {
+ System.err.println("--- CSV parse error : " + e.getMessage() + "---");
+ e.printStackTrace();
+ System.err.println("--- /CSV parse error : " + e.getMessage() + "---");
+ throw new CSVParseException(e.getMessage(), lineno);
+ }
+
+ CSV csv = new CSV();
+ csv.header = header;
+ CSVDataLine[] arr = new CSVDataLine[datalines.size()];
+ csv.lines = datalines.toArray(arr);
+
+ System.out.println("---- parsed ----");
+ System.out.println(csv);
+ System.out.println("---- /parsed ----");
+
+ return csv;
+
+ }
+
+}
diff --git a/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java b/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java
new file mode 100644
index 000000000000..b21f19af055c
--- /dev/null
+++ b/test/hotspot/jtreg/runtime/Vitals/TestHiMemReport.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2021, 2023 SAP SE. 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 TestHiMemReport-print
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestHiMemReport print
+ */
+
+/*
+ * @test TestHiMemReport-dump
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestHiMemReport dump
+ */
+
+/*
+ * @test TestHiMemReport-dump-with-exec-to-reportdir
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @requires !jdk.static
+ * @run driver TestHiMemReport dump-with-exec-to-reportdir
+ */
+
+/*
+ * @test TestHiMemReport-dump-with-exec-to-stderr
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @requires !jdk.static
+ * @run driver TestHiMemReport dump-with-exec-to-stderr
+ */
+
+/*
+ * @test TestHiMemReport-natural-max
+ * @library /test/lib
+ * @requires os.family == "linux"
+ * @run driver TestHiMemReport natural-max
+ */
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+import java.io.File;
+import java.io.IOException;
+
+public class TestHiMemReport {
+
+ // Match the output file suffix we generate with the HiMemReportFacility
+ // (xxx_pid______.yyy)
+ static final String patterFileSuffix = "_pid\\d+_\\d+_\\d+_\\d+_\\d+_\\d+_\\d+";
+
+ // These tests are very simple. We start the VM with a footprint (rss) higher than
+ // what we set as HiMemReportMax. Therefore, the HiMem reporter should genereate a
+ // 100% report right away.
+ // Testing anything more is much more complicated since the test would have to
+ // predict, with a certain correctness, rss and swap.
+
+ static final String[] beforeReport = {
+ "HiMemoryReport: rss\\+swap=.* - alert level increased to 3 \\(>=\\d+%\\).",
+ "HiMemoryReport: ... seems we passed alert level 1 \\(\\d+%\\) without noticing.",
+ "HiMemoryReport: ... seems we passed alert level 2 \\(\\d+%\\) without noticing.",
+ };
+
+ static final String[] reportHeader = {
+ "# High Memory Report:",
+ "# rss\\+swap .* larger than \\d+% of HiMemReportMax.*",
+ "# Spike number: 1",
+ };
+
+ static final String[] reportBody = {
+ ".*Vitals.*",
+ "Now:",
+ "Native Memory Tracking:",
+ "Total: reserved=.*, committed=.*",
+ "# END: High Memory Report"
+ };
+
+ static void testPrint() throws Exception {
+ ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
+ "-XX:+HiMemReport", "-XX:HiMemReportMax=128m",
+ "-XX:NativeMemoryTracking=summary",
+ "-Xmx128m", "-Xms128m", "-XX:+AlwaysPreTouch",
+ TestHiMemReport.class.getName(),
+ "sleep", "2" // num seconds to sleep to give the reporter thread time to generate output
+ );
+
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.reportDiagnosticSummary();
+ output.shouldHaveExitValue(0);
+
+ String[] lines = VitalsUtils.stderrAsLines(output);
+ int line = VitalsUtils.matchPatterns(lines, 0, beforeReport);
+ line = VitalsUtils.matchPatterns(lines, line + 1, reportHeader);
+ line = VitalsUtils.matchPatterns(lines, line + 1, reportBody);
+ }
+
+ static void testDump() throws Exception {
+ ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
+ "-XX:+HiMemReport", "-XX:HiMemReportMax=128m",
+ "-XX:NativeMemoryTracking=summary",
+ "-XX:HiMemReportDir=himemreport-1",
+ "-Xmx128m", "-Xms128m", "-XX:+AlwaysPreTouch",
+ TestHiMemReport.class.getName(),
+ "sleep", "2" // num seconds to sleep to give the reporter thread time to generate output
+ );
+
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.reportDiagnosticSummary();
+ output.shouldHaveExitValue(0);
+
+ // We expect, on stderr, just the report header
+ String[] lines = VitalsUtils.stderrAsLines(output);
+ int line = VitalsUtils.matchPatterns(lines, 0, beforeReport);
+ line = VitalsUtils.matchPatterns(lines, line + 1, reportHeader);
+
+ // We expect a report in a file inside HiMemReportDir, and a mentioning of it on
+ // stderr
+ line = VitalsUtils.matchPatterns(lines, line + 1,
+ new String[] { "# Printing to .*himemreport-1/sapmachine_himemalert" + patterFileSuffix + ".log",
+ "# Done\\." } );
+
+ String reportFile = output.firstMatch("# Printing to (.*himemreport-1/sapmachine_himemalert" + patterFileSuffix + ".log)", 1);
+ VitalsUtils.assertFileExists(reportFile);
+
+ VitalsUtils.assertFileContentMatches(new File(reportFile), reportBody);
+ }
+
+ static void testDumpWithExecToReportDir() throws Exception {
+ String reportDirName = "himemreport-2";
+ // Here we not only dump, but we also execute several jcmds. Therefore we take some more seconds to sleep
+ ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
+ "-XX:+HiMemReport", "-XX:HiMemReportMax=128m",
+ "-XX:HiMemReportDir=himemreport-2",
+ "-XX:HiMemReportExec=VM.flags -all;VM.metaspace show-loaders;GC.heap_dump",
+ "-XX:NativeMemoryTracking=summary",
+ "-Xmx128m", "-Xms128m", "-XX:+AlwaysPreTouch",
+ TestHiMemReport.class.getName(),
+ "sleep", "12" // num seconds to sleep to give the reporter thread time to generate output
+ );
+
+ OutputAnalyzer output = new OutputAnalyzer(pb.start());
+ output.reportDiagnosticSummary();
+ output.shouldHaveExitValue(0);
+
+ // We expect, on stderr, just the report header
+ String[] lines = VitalsUtils.stderrAsLines(output);
+ int line = VitalsUtils.matchPatterns(lines, 0, beforeReport);
+ line = VitalsUtils.matchPatterns(lines, line + 1, reportHeader);
+
+ // We expect a report in a file inside HiMemReportDir, and a mentioning of it on
+ // stderr
+ line = VitalsUtils.matchPatterns(lines, line + 1,
+ new String[] { "# Printing to .*himemreport-2/sapmachine_himemalert" + patterFileSuffix + ".log",
+ "# Done\\." } );
+
+ String reportFile = output.firstMatch("# Printing to (.*himemreport-2/sapmachine_himemalert" + patterFileSuffix + ".log)", 1);
+ VitalsUtils.assertFileExists(reportFile);
+ VitalsUtils.assertFileContentMatches(new File(reportFile), reportBody);
+
+ // We also expect, in the report dir, several more files
+ String reportDir = output.firstMatch("# Printing to (.*himemreport-2/)sapmachine_himemalert" + patterFileSuffix + ".log", 1);
+ VitalsUtils.assertFileExists(reportDir);
+ File reportDirAsFile = new File(reportDir);
+
+ ////////
+ // HiMemReportExec should have caused three jcmds to fire...
+
+ line = VitalsUtils.matchPatterns(lines, line + 1,
+ new String[] { "HiMemReport: Successfully executed \"VM.flags -all\" \\(\\d+ ms\\), output redirected to report dir",
+ "HiMemReport: Successfully executed \"VM.metaspace show-loaders\" \\(\\d+ ms\\), output redirected to report dir",
+ "HiMemReport: Successfully executed \"GC.heap_dump .*himemreport-2/GC.heap_dump" + patterFileSuffix + ".dump\" \\(\\d+ ms\\), output redirected to report dir" } );
+
+ // VM.flags:
+
+ // I expect two files, VM.flags_pidXXXX_1_100.err and VM.flags_pidXXXX_1_100.out, in the report dir...
+ File VMflagsOutFile = VitalsUtils.findFirstMatchingFileInDirectory(reportDirAsFile, "VM.flags" + patterFileSuffix + ".out");
+ File VMflagsErrFile = VitalsUtils.findFirstMatchingFileInDirectory(reportDirAsFile, "VM.flags" + patterFileSuffix + ".err");
+
+ // The err file should be empty
+ VitalsUtils.assertFileisEmpty(VMflagsErrFile.getAbsolutePath());
+
+ // The out file should contain a valid flags report, containing, among other things, the flags we passed above
+ // Note that since we started VM.flags with -all, we have the full flags output
+ VitalsUtils.assertFileContentMatches(VMflagsOutFile, new String[] {
+ ".*HiMemReport *= *true.*",
+ ".*HiMemReportDir *= *himemreport-2.*",
+ ".*HiMemReportExec *= *VM.flags.*VM.metaspace.*GC.heap_dump.*",
+ ".*HiMemReportMax *= *134217728.*"
+ });
+
+ // "VM.metaspace":
+
+ // I expect two files, VM.metaspace_pidXXXX_1_100.err and VM.metaspace_pidXXXX_1_100.out, in the report dir...
+ File VMmetaspaceOutFile = VitalsUtils.findFirstMatchingFileInDirectory(reportDirAsFile, "VM.metaspace" + patterFileSuffix + ".out");
+ File VMmetaspaceErrFile = VitalsUtils.findFirstMatchingFileInDirectory(reportDirAsFile, "VM.metaspace" + patterFileSuffix + ".err");
+
+ // The err file should be empty
+ VitalsUtils.assertFileisEmpty(VMmetaspaceErrFile.getAbsolutePath());
+
+ // The out file should contain a valid flags report, containing, among other things, the flags we passed above
+ // Note that since we started VM.flags with -all, we have the full flags output
+ VitalsUtils.assertFileContentMatches(VMmetaspaceOutFile, new String[] {
+ "Usage per loader:",
+ ".*app.*",
+ ".*bootstrap.*",
+ "Total Usage.*",
+ "Virtual space.*",
+ "Settings.*",
+ "MaxMetaspaceSize.*"
+ });
+
+ // GC.heap_dump:
+
+ // I expect three files, the usual GC.heap_dump_pid