@@ -23,6 +23,11 @@ public final class JavaVirtualMachine: @unchecked Sendable {
2323 /// The Java virtual machine instance.
2424 private let jvm : JavaVMPointer
2525
26+ /// Adopt an existing JVM pointer.
27+ private init ( adoptingJVM jvm: JavaVMPointer ) {
28+ self . jvm = jvm
29+ }
30+
2631 /// Initialize a new Java virtual machine instance.
2732 ///
2833 /// - Parameters:
@@ -33,7 +38,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
3338 /// be prefixed by the class-path argument described above.
3439 /// - ignoreUnrecognized: Whether the JVM should ignore any VM options it
3540 /// does not recognize.
36- public init (
41+ private init (
3742 classPath: [ String ] = [ ] ,
3843 vmOptions: [ String ] = [ ] ,
3944 ignoreUnrecognized: Bool = true
@@ -74,8 +79,13 @@ public final class JavaVirtualMachine: @unchecked Sendable {
7479 vmArgs. options = optionsBuffer. baseAddress
7580 vmArgs. nOptions = jint ( optionsBuffer. count)
7681
77- // Create the JVM.
78- if JNI_CreateJavaVM ( & jvm, & environment, & vmArgs) != JNI_OK {
82+ // Create the JVM instance.
83+ let createResult = JNI_CreateJavaVM ( & jvm, & environment, & vmArgs)
84+ if createResult != JNI_OK {
85+ if createResult == JNI_EEXIST {
86+ throw VMError . existingVM
87+ }
88+
7989 throw VMError . failedToCreateVM
8090 }
8191
@@ -88,7 +98,10 @@ public final class JavaVirtualMachine: @unchecked Sendable {
8898 fatalError ( " Failed to destroy the JVM. " )
8999 }
90100 }
101+ }
91102
103+ // MARK: Java thread management.
104+ extension JavaVirtualMachine {
92105 /// Produce the JNI environment for the active thread, attaching this
93106 /// thread to the JVM if it isn't already.
94107 ///
@@ -133,10 +146,92 @@ public final class JavaVirtualMachine: @unchecked Sendable {
133146 }
134147}
135148
149+ // MARK: Shared Java Virtual Machine management.
150+ extension JavaVirtualMachine {
151+ /// The globally shared JavaVirtualMachine instance, behind a lock.
152+ ///
153+ /// TODO: If the use of the lock itself ends up being slow, we could
154+ /// use an atomic here instead because our access pattern is fairly
155+ /// simple.
156+ private static let sharedJVM : LockedState < JavaVirtualMachine ? > = . init( initialState: nil )
157+
158+ /// Access the shared Java Virtual Machine instance.
159+ ///
160+ /// If there is no shared Java Virtual Machine, create one with the given
161+ /// arguments. Note that this function makes no attempt to try to augment
162+ /// an existing virtual machine instance with the options given, so it is
163+ /// up to clients to ensure that consistent arguments are provided to all
164+ /// calls.
165+ ///
166+ /// - Parameters:
167+ /// - classPath: The directories, JAR files, and ZIP files in which the JVM
168+ /// should look to find classes. This maps to the VM option
169+ /// `-Djava.class.path=`.
170+ /// - vmOptions: Options that should be passed along to the JVM, which will
171+ /// be prefixed by the class-path argument described above.
172+ /// - ignoreUnrecognized: Whether the JVM should ignore any VM options it
173+ /// does not recognize.
174+ public static func shared(
175+ classPath: [ String ] = [ ] ,
176+ vmOptions: [ String ] = [ ] ,
177+ ignoreUnrecognized: Bool = true
178+ ) throws -> JavaVirtualMachine {
179+ try sharedJVM. withLock { ( sharedJVMPointer: inout JavaVirtualMachine ? ) in
180+ // If we already have a JavaVirtualMachine instance, return it.
181+ if let existingInstance = sharedJVMPointer {
182+ return existingInstance
183+ }
184+
185+ while true {
186+ var wasExistingVM : Bool = false
187+ while true {
188+ // Query the JVM itself to determine whether there is a JVM
189+ // instance that we don't yet know about.
190+ var jvm : UnsafeMutablePointer < JavaVM ? > ? = nil
191+ var numJVMs : jsize = 0
192+ if JNI_GetCreatedJavaVMs ( & jvm, 1 , & numJVMs) == JNI_OK, numJVMs >= 1 {
193+ // Adopt this JVM into a new instance of the JavaVirtualMachine
194+ // wrapper.
195+ let javaVirtualMachine = JavaVirtualMachine ( adoptingJVM: jvm!)
196+ sharedJVMPointer = javaVirtualMachine
197+ return javaVirtualMachine
198+ }
199+
200+ precondition (
201+ !wasExistingVM,
202+ " JVM reports that an instance of the JVM was already created, but we didn't see it. "
203+ )
204+
205+ // Create a new instance of the JVM.
206+ let javaVirtualMachine : JavaVirtualMachine
207+ do {
208+ javaVirtualMachine = try JavaVirtualMachine (
209+ classPath: classPath,
210+ vmOptions: vmOptions,
211+ ignoreUnrecognized: ignoreUnrecognized
212+ )
213+ } catch VMError . existingVM {
214+ // We raced with code outside of this JavaVirtualMachine instance
215+ // that created a VM while we were trying to do the same. Go
216+ // through the loop again to pick up the underlying JVM pointer.
217+ wasExistingVM = true
218+ continue
219+ }
220+
221+ sharedJVMPointer = javaVirtualMachine
222+ return javaVirtualMachine
223+ }
224+ }
225+ }
226+ }
227+ }
228+
136229extension JavaVirtualMachine {
137230 enum VMError : Error {
138231 case failedToCreateVM
139232 case failedToAttachThread
140233 case failedToDetachThread
234+ case failedToQueryVM
235+ case existingVM
141236 }
142237}
0 commit comments