Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
57251e3
add support for dynamically loaded JNI
s-u Sep 16, 2024
95d0fe3
add dynamic JNI load/unload support
s-u Sep 18, 2024
da504fc
make dynload the default in this branch; minor fixes
s-u Sep 18, 2024
fe30e9a
feat: Implement dynamic JVM loading on Windows, removing static `jvm.…
e-kotov Dec 25, 2025
24d609a
feat: defer JVM discovery and harden cross-platform dynamic loading
e-kotov Oct 17, 2026
3a3ea3e
test: verify retry with a library without JNI exports
e-kotov Oct 17, 2026
9b6f625
ci: test dynamic rJava with installed downstream rJavaEnv consumers
e-kotov Oct 17, 2026
dec19f3
docs: define deferred loading acceptance boundary and limits
e-kotov Oct 17, 2026
42af82a
fix: make deferred Windows discovery helper visible to package checks
e-kotov Oct 17, 2026
5fc891f
fix: retain JVM library selection until the R process exits
e-kotov Oct 17, 2026
880d157
fix: reject another JVM already mapped outside the deferred loader
e-kotov Oct 17, 2026
ef4fb30
ci: use full commit SHA for pinned rJavaEnv checkout
e-kotov Oct 17, 2026
9c9baf4
build: regenerate configure with upstream's Autoconf 2.72
e-kotov Oct 17, 2026
123838e
fix: identify mapped JVM libraries by file identity and harden Window…
e-kotov Oct 17, 2026
0375881
feat: define deferred JVM selection order and attach to an embedding JVM
e-kotov Oct 17, 2026
48ef04e
test: remove obsolete manual Windows smoke script
e-kotov Oct 17, 2026
24d9a4b
ci: run upstream's check workflow on this branch again
e-kotov Oct 17, 2026
2adfce0
test: cover JDK 8-25, vendors, path aliases, fallback, JRI and DLL se…
e-kotov Oct 17, 2026
5f1a705
fix: keep rJava's JNI calls bound to djni under symbol interposition
e-kotov Oct 17, 2026
0eacc04
fix(windows): build JRI from its src/jri location
e-kotov Oct 17, 2026
2d1f9ae
test: preload a JVM library with global symbols before selection
e-kotov Oct 17, 2026
a7e300a
Revert "fix(windows): build JRI from its src/jri location"
e-kotov Oct 17, 2026
41e8673
test: attach to a JVM created by native code, and hide the system VC+…
e-kotov Oct 17, 2026
dbbc4ee
docs: state the deferred loading contract, Windows evidence and limits
e-kotov Oct 17, 2026
f679c67
docs: report the hidden-runtime results for all six JDKs
e-kotov Oct 17, 2026
b6174de
fix: find mapped JVM libraries by their exports, not only by name
e-kotov Oct 17, 2026
1b1001e
ci: follow symbolic-link Java homes when checking JVM exports
e-kotov Oct 17, 2026
4cd072c
fix: avoid a helper collision in the macOS loader diagnostic
e-kotov Oct 17, 2026
84aec96
fix: identify a mapped JVM library by its mapped file, not its curren…
e-kotov Oct 17, 2026
731a78b
fix: attach to a created JVM without accepting its recorded name as a…
e-kotov Oct 17, 2026
67fc4fc
docs: distinguish automatic discovery from explicit selection [skip ci]
e-kotov Oct 17, 2026
88e73ce
fix: build Windows JRI and resolve bundled late-load VC++ dependencies
e-kotov Oct 18, 2026
4098626
test: exercise overlayfs with real pre-6.8 and current guest kernels
e-kotov Oct 18, 2026
5c906d8
test: verify bundled runtime origins and supply guest build dependencies
e-kotov Oct 18, 2026
8fdbd8f
test: distinguish distro overlay behavior from a frozen pre-6.8 kernel
e-kotov Oct 18, 2026
2a9b38f
test: schedule guest reboot after the SSH command exits [skip ci]
e-kotov Oct 18, 2026
fee5d72
fix: describe overlay identity failures without assuming a kernel ver…
e-kotov Oct 18, 2026
3f17fb2
fix: preserve pre-4.6 Windows JRI declarations and test R 4.5
e-kotov Oct 18, 2026
0fe44df
fix: resolve review findings and exercise runtime and overlay boundaries
e-kotov Oct 18, 2026
81e71f1
test: make runtime retention and unprivileged overlay fixtures precise
e-kotov Oct 18, 2026
f159b76
test: isolate host startup dependencies and verify cross-filesystem r…
e-kotov Oct 18, 2026
f4fc341
test: avoid PowerShell reserved HOME in old-runtime verification
e-kotov Oct 18, 2026
3f4b72c
test: use a task-specific overlay path variable
e-kotov Oct 18, 2026
f53daf4
test: cover Linux arm64, musl, R 3.6 and the CRAN xlsx package
e-kotov Oct 20, 2026
eb5e350
test: fix new coverage fixtures for Alpine, R 3.6 and xlsx
e-kotov Oct 20, 2026
9e248ec
test: check musl linkage without ldd and the CRAN xlsx source package
e-kotov Oct 20, 2026
b414fd9
fix: refuse a JVM library that the JDK's libraries would not find by …
e-kotov Oct 20, 2026
8efb0f0
test: run musl scenarios with their runtime on the startup library path
e-kotov Oct 20, 2026
4b29da3
fix: report a library that is no JVM before checking the JVM library …
e-kotov Oct 20, 2026
a87157d
docs: describe the Linux JVM library name check and the expanded matrix
e-kotov Oct 20, 2026
a1ba1ce
test: do not run the replaced-file scenarios on musl, and document why
e-kotov Oct 20, 2026
acb1639
test: report the error and state when the JRI host test selects JAVA_…
e-kotov Oct 20, 2026
b8e89bd
test: log the selected path and JVM mappings when the JRI host test f…
e-kotov Oct 20, 2026
91bbbfd
test: swap the JRI host runtimes with env, as dynamic.yaml does
e-kotov Oct 20, 2026
6e3a4a6
test: restore musl deleted and replaced alias coverage with either st…
e-kotov Oct 20, 2026
f443920
fix: identify mapped JVM images on Linux from their exports in memory
e-kotov Oct 20, 2026
9a3c487
docs: describe deferred selection in NEWS and the musl startup-path rule
e-kotov Oct 20, 2026
cd6bbe5
fix: read mapped images on Linux only while dl_iterate_phdr() runs
e-kotov Oct 20, 2026
2ddb149
fix: address filesystem identity and deferred-loading compatibility r…
e-kotov Oct 23, 2026
fdbe195
chore: keep integration infrastructure in the fork testing branch
e-kotov Oct 23, 2026
c5834c8
fix: retain bundled Windows runtimes for later Java native loads
e-kotov Oct 23, 2026
76d01fb
fix: preserve newer Windows runtimes and adopt preloaded JVMs
e-kotov Oct 23, 2026
dbe0c72
fix: report JVM adoption only after successful selection
e-kotov Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,6 +1,58 @@
NEWS/ChangeLog for rJava
--------------------------

1.0-19 (not yet released)
o The JVM run-time is no longer linked into rJava or loaded when
the namespace loads. The first .jinit() selects one: a JVM
already mapped in the process, then java.home (Windows) or
JAVA_HOME, the JavaSoft registry (Windows),
/usr/libexec/java_home (macOS), R CMD config JAVA_HOME (Linux),
and java on PATH. Linux thus retains the javareconf JDK as its
default when JAVA_HOME is unset. Until a
library is loaded, a failed .jinit() can be retried after
correcting the setting. .jloadJVM() selects a library by path;
.jvmState() reports "not-loaded" before any selection.

o A selected JVM library stays selected for the life of the R
process: .junloadJVM() fails once one is loaded, and another
run-time requires a new R process. A JVM library already mapped
into the process can be selected explicitly only by a path naming
the same file; a different or unverifiable file is refused before it is
loaded. Device mismatches on btrfs and overlayfs are not evidence
of a different file: matching inodes also require a loader
reference to the same image, without loading another library.

o If native code in the process has already mapped one JVM library
(e.g. JavaGD), .jinit() adopts it even before a VM exists and warns
if Java discovery selects a different path. If a Java application
embedding R through JRI has created a VM, .jinit() attaches to it
whatever JAVA_HOME names.

o Changed behavior: the Java home is now chosen when Java is
first used, not when rJava loads, and .onLoad() no longer changes
PATH (Windows) or DYLD_LIBRARY_PATH (macOS). On Linux with musl
(e.g. Alpine), only the run-time whose server directory was on
LD_LIBRARY_PATH when R started can be used; selecting another
is refused, and the refused library stays mapped, so a new R
process is needed (see ?.jinit).

o JVM loading is now always dynamic. --enable-dynload defaults to
yes; --disable-dynload is rejected instead of silently producing
a dynamic build under --as-needed. RJAVA_FORCE_LOAD is no longer
used, since namespace loading never requires a JVM.


o On Windows, retain MSVC runtime DLLs for native libraries Java
loads after startup or host attachment. Already loaded runtimes
keep precedence. For each runtime the JDK bundles, select the
newer of System32 and the bundled copy before loading jvm.dll,
preferring System32 on ties or unknown versions. This preserves
newer system runtimes for other native packages while supporting
missing or incomplete system VC++ installations. Retention lasts
for the process lifetime without changing PATH or retaining a
search directory. Windows JRI build policy is unchanged.


1.0-18 2026-04-08
o JRI: fix regression in 1.0-16 breaking older versions of R.

Expand Down
79 changes: 79 additions & 0 deletions R/java-runtime.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## Deferred JVM discovery. This runs at the first .jinit(),
## never at namespace load. The Java home is the first of:
## 1. getOption("java.home") (Windows only, as before) or JAVA_HOME; a value
## that is not an existing directory is ignored with a warning, as rJava
## did on Windows and macOS before deferred loading
## 2. on Windows, the CurrentVersion of the JavaSoft registry keys
## 3. on macOS, /usr/libexec/java_home
## 4. on Linux, R CMD config JAVA_HOME (the javareconf setting)
## 5. the java.home property reported by `java` on PATH
## An existing Java home without a JVM library is an error, not a fallback.
.find.java.runtime <- function() {
windows <- .Platform$OS.type == "windows"
javahome <- if (windows && !is.null(getOption("java.home"))) getOption("java.home") else Sys.getenv("JAVA_HOME")
if (!is.character(javahome) || length(javahome) != 1L || is.na(javahome)) javahome <- ""
if (nzchar(javahome) && !dir.exists(javahome)) {
if (windows) message("java.home option: ", getOption("java.home"))
message("JAVA_HOME environment variable: ", Sys.getenv("JAVA_HOME"))
warning("Java home setting is INVALID, it will be ignored.\nPlease do NOT set it unless you want to override system settings.")
javahome <- ""
}
if (!nzchar(javahome) && windows) javahome <- .find.java.registry()
if (!nzchar(javahome) && file.exists("/usr/libexec/java_home"))
javahome <- .java.output("/usr/libexec/java_home")
if (!nzchar(javahome) && identical(Sys.info()[["sysname"]], "Linux")) {
configured <- .java.output(file.path(R.home("bin"), "R"), c("CMD", "config", "JAVA_HOME"))
if (nzchar(configured) && dir.exists(configured)) javahome <- configured
}
if (!nzchar(javahome))
javahome <- .java.output("java", c("-cp", shQuote(system.file("java", package="rJava")), "getsp", "java.home"))
if (nzchar(javahome) && dir.exists(javahome)) javahome else ""
}

## first line of a command's standard output, or "" if it fails
.java.output <- function(command, args=character()) {
out <- tryCatch(suppressWarnings(system2(command, args, stdout=TRUE, stderr=FALSE)),
error=function(...) character())
if (!length(out) || !is.null(attr(out, "status")) || is.na(out[1L])) "" else out[1L]
}

.find.java.registry <- function() {
for (root in c("HLM", "HCU")) {
for (key in c(
"Software\\JavaSoft\\JRE",
"Software\\JavaSoft\\JDK",
"Software\\JavaSoft\\Java Runtime Environment",
"Software\\JavaSoft\\Java Development Kit"
)) {
hive <- try(utils::readRegistry(key, root, 2), silent = TRUE)
if (!inherits(hive, "try-error") && length(hive$CurrentVersion)) {
home <- hive[[hive$CurrentVersion]]$JavaHome
if (is.character(home) && length(home) == 1L && nzchar(home)) return(home)
}
}
}
""
}

## JVM libraries in a Java home, server VM first. Known layouts are checked
## before a recursive search: lib/server (JDK 9+ on Linux and macOS),
## jre/lib/<arch>/server (JDK 8 on Linux), jre/lib/server (JDK 8 on macOS),
## bin/server and jre/bin/server (Windows).
.find.jvm.library <- function(home) {
if (.Platform$OS.type == "windows") {
lib <- "jvm.dll"
base <- file.path(home, c("bin", "jre/bin"))
} else {
lib <- if (length(grep("^darwin", R.version$os))) "libjvm.dylib" else "libjvm.so"
base <- file.path(home, c("lib", "jre/lib"))
base <- c(base, list.dirs(base, recursive=FALSE))
}
found <- c(file.path(base, "server", lib), file.path(base, "client", lib))
found <- found[file.exists(found)]
if (!length(found)) {
found <- list.files(home, paste0("^", gsub(".", "[.]", lib, fixed=TRUE), "$"),
recursive=TRUE, full.names=TRUE)
found <- c(found[grepl("/server/", found, fixed=TRUE)], found[!grepl("/server/", found, fixed=TRUE)])
}
found
}
2 changes: 1 addition & 1 deletion R/jfirst.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"RgetShortArrayCont", "RgetStringArrayCont", "RidenticalRef", "RgetSimpleClassNames",
"RisAssignableFrom", "RpollException", "RsetField", "RthrowException",
"javaObjectCache", "initRJavaTools", "newRJavaLookupTable", "useDynamicSymbols",
"RgetJVMstate",
"RgetJVMstate", "RloadJVM", "RunloadJVM", "RgetMappedJVM", "RattachJVM",
# .External
"RcreateObject", "RgetStringValue", "RinitJVM", "RtoString", "RcallMethod",
# .C
Expand Down
36 changes: 36 additions & 0 deletions R/jinit.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,44 @@
.need.init <- function()
.Call(RJava_needs_init)

.jloadJVM <- function(path, silent=FALSE)
.Call(RloadJVM, path.expand(path), silent)

.junloadJVM <- function()
.Call(RunloadJVM)

.jfindAndLoadJVM <- function() {
## A mapped JVM is the only runtime this process can use, even before
## a VM exists (e.g. JavaGD links libjvm). Select the held image itself;
## explicit .jloadJVM(path) still requires a path naming that same file.
mapped <- .Call(RgetMappedJVM)
if (!is.null(mapped) && isTRUE(mapped$vms > 0L))
return(.Call(RattachJVM))
home <- .find.java.runtime()
if (!is.null(mapped)) {
selected <- .Call(RattachJVM)
discovered <- if (nzchar(home)) .find.jvm.library(home) else character()
if (length(discovered) &&
!identical(normalizePath(discovered[1], winslash="/"),
normalizePath(mapped$path, winslash="/", mustWork=FALSE)))
warning("Using the JVM library already mapped into this process: ", mapped$path,
"; Java discovery selected ", discovered[1],
". To select that runtime, start a new R process before loading packages that use Java.",
call.=FALSE)
return(selected)
}
if (!nzchar(home))
stop("Cannot find Java. Set JAVA_HOME to an existing Java home before initializing the JVM.")
libjvm <- .find.jvm.library(home)
if (!length(libjvm)) stop("Cannot find JVM library in ", home)
.jloadJVM(normalizePath(libjvm[1], winslash="/", mustWork=TRUE))
}

## initialization
.jinit <- function(classpath=NULL, parameters=getOption("java.parameters", NA), ..., silent=FALSE, force.init=FALSE) {
st <- .jvmState()
if (st$state == "not-loaded")
.jfindAndLoadJVM()
running.classpath <- character()
if (!.need.init()) {
running.classpath <- .jclassPath()
Expand Down
63 changes: 1 addition & 62 deletions R/windows/FirstLib.R
Original file line number Diff line number Diff line change
@@ -1,65 +1,4 @@
.msg <- message

.onLoad <-
function(libname, pkgname) {
OPATH <- Sys.getenv("PATH")
javahome <- if (!is.null(getOption("java.home"))) getOption("java.home") else Sys.getenv("JAVA_HOME")
if (nzchar(javahome) && !dir.exists(javahome)) {
.msg("java.home option: ", getOption("java.home"))
.msg("JAVA_HOME environment variable: ", Sys.getenv("JAVA_HOME"))
warning("Java home setting is INVALID, it will be ignored.\nPlease do NOT set it unless you want to override system settings.")
javahome <- ""
}
if(!nzchar(javahome)) { ## JAVA_HOME was not set explicitly
find.java <- function() {
for (root in c("HLM", "HCU"))
for(key in c(
"Software\\JavaSoft\\JRE",
"Software\\JavaSoft\\JDK",
"Software\\JavaSoft\\Java Runtime Environment",
"Software\\JavaSoft\\Java Development Kit"
)) {
hive <- try(utils::readRegistry(key, root, 2), silent=TRUE)
if (!inherits(hive, "try-error")) return(hive)
}
hive
}
hive <- find.java()
if (inherits(hive, "try-error"))
stop("JAVA_HOME cannot be determined from the Registry")
if (!length(hive$CurrentVersion))
stop("No CurrentVersion entry in Software/JavaSoft registry! Try re-installing Java and make sure R and Java have matching architectures.")
this <- hive[[hive$CurrentVersion]]
javahome <- this$JavaHome
paths <- if (is.character(this$RuntimeLib)) dirname(this$RuntimeLib) else character() # wrong on 64-bit
} else paths <- character()
if(is.null(javahome) || !length(javahome) || !nchar(javahome))
stop("JAVA_HOME is not set and could not be determined from the registry")
#else cat("using JAVA_HOME =", javahome, "\n")

## we need to add Java-related library paths to PATH
curPath <- OPATH
paths <- c(paths,
file.path(javahome, "bin", "client"), # 32-bit
file.path(javahome, "bin", "server"), # 64-bit
file.path(javahome, "bin"), # base (now needed for MSVCRT in recent Sun Java)
file.path(javahome, "jre", "bin", "server"), # old 64-bit (or manual JAVA_HOME setting to JDK)
file.path(javahome, "jre", "bin", "client")) # old 32-bit (or manual JAVA_HOME setting to JDK)
cpc <- strsplit(curPath, ";", fixed=TRUE)[[1]] ## split it up so we can check presence/absence of a path

## add paths only if they are not in already and they exist
for (path in unique(paths))
if (!path %in% cpc && file.exists(path)) curPath <- paste(path, curPath, sep=";")

## set PATH only if it's not correct already (cannot use identical/isTRUE because of PATH name attribute)
if (curPath != OPATH) {
Sys.setenv(PATH = curPath)
# check the resulting PATH - if they don't match then Windows has truncated it
if (curPath != Sys.getenv("PATH"))
warning("*** WARNING: your Windows system seems to suffer from truncated PATH bug which will likely prevent rJava from loading.\n Either reduce your PATH or read http://support.microsoft.com/kb/906469 on how to fix your system.")
}

.onLoad <- function(libname, pkgname) {
library.dynam("rJava", pkgname, libname)
Sys.setenv(PATH = OPATH)
.jfirst(libname, pkgname)
}
49 changes: 1 addition & 48 deletions R/zzz.R.in
Original file line number Diff line number Diff line change
@@ -1,52 +1,5 @@
# this should no longer be needed as an autoconf-edited file
.onLoad <- function(libname, pkgname) {
## we can skip all the detection logic if forced - assuming the user has loaded JVM already
if (!nzchar(Sys.getenv("RJAVA_FORCE_LOAD"))) {
## On OS X with Oracle Java we may need to work around Oracle bug:
## https://bugs.openjdk.java.net/browse/JDK-7131356
use.dynload <- FALSE
@USE_DYNLOAD_TRUE@use.dynload <- TRUE
if (length(grep("^darwin", R.version$os))) {
jh <- Sys.getenv("JAVA_HOME")
if (nzchar(jh) && !dir.exists(jh)) {
warning("JAVA_HOME is set incorrectly! Ingoring.")
jh <- ""
}
if (!nzchar(jh) && file.exists("/usr/libexec/java_home"))
jh <- system("/usr/libexec/java_home", intern=TRUE)[1L]
if (!nzchar(jh)) {
if (use.dynload)
stop("Cannot find Java. Install Java run-time and use R CMD javareconf.\nAs a last resort you can set JAVA_HOME appropriately.")
warning("Cannot find Java. Install Java run-time and use R CMD javareconf.\nAs a last resort you can set JAVA_HOME if you know the correct value.")
}
if (file.exists(file.path(jh, "jre/lib"))) jh <- file.path(jh, "jre")
dlp <- Sys.getenv("DYLD_LIBRARY_PATH")
if (nzchar(dlp)) dlp <- paste0(":", dlp)
## pre-load JLI due to Oracle bug
if (file.exists(jli <- file.path(jh, "lib/jli/libjli.dylib")))
dyn.load(jli, FALSE)
## this is mandatory - we need the JVM
if (file.exists(jvm <- file.path(jh, "lib/server/libjvm.dylib"))) {
dyn.load(jvm, FALSE)
Sys.setenv(DYLD_LIBRARY_PATH=paste0(file.path(jh, "lib/server"), dlp))
} else {
warning("Cannot find JVM library '", jvm, "'\nInstall Java and/or check JAVA_HOME (if in doubt, do NOT set it, it will be detected)")
if (use.dynload) stop("JVM could not be found")
}
} else if (use.dynload) {
jh <- Sys.getenv("JAVA_HOME")
if (nzchar(jh) && !dir.exists(jh)) {
warning("JAVA_HOME is set incorrectly! Ingoring.")
jh <- ""
}
if (!nzchar(jh))
stop("JAVA_HOME is requied for dynamically loaded JVM, but is not set.\nRe-run R CMD javareconf or set JAVA_HOME correctly.")
if (file.exists(file.path(jh, "jre/lib"))) jh <- file.path(jh, "jre")
if (!file.exists(jvm <- file.path(jh, "lib/server/libjvm.so")))
stop("Cannot find ", jvm,", re-configure R or use valid JAVA_HOME")
dyn.load(jvm, FALSE)
}
} ## RJAVA_FORCE_LOAD

library.dynam("rJava", pkgname, libname)
# pass on to the system-independent part
.jfirst(libname, pkgname)
Expand Down
Loading