@@ -23,6 +23,11 @@ public final class JavaVirtualMachine: @unchecked Sendable {
23
23
/// The Java virtual machine instance.
24
24
private let jvm : JavaVMPointer
25
25
26
+ /// Adopt an existing JVM pointer.
27
+ private init ( adoptingJVM jvm: JavaVMPointer ) {
28
+ self . jvm = jvm
29
+ }
30
+
26
31
/// Initialize a new Java virtual machine instance.
27
32
///
28
33
/// - Parameters:
@@ -33,7 +38,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
33
38
/// be prefixed by the class-path argument described above.
34
39
/// - ignoreUnrecognized: Whether the JVM should ignore any VM options it
35
40
/// does not recognize.
36
- public init (
41
+ private init (
37
42
classPath: [ String ] = [ ] ,
38
43
vmOptions: [ String ] = [ ] ,
39
44
ignoreUnrecognized: Bool = true
@@ -74,8 +79,13 @@ public final class JavaVirtualMachine: @unchecked Sendable {
74
79
vmArgs. options = optionsBuffer. baseAddress
75
80
vmArgs. nOptions = jint ( optionsBuffer. count)
76
81
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
+
79
89
throw VMError . failedToCreateVM
80
90
}
81
91
@@ -88,7 +98,10 @@ public final class JavaVirtualMachine: @unchecked Sendable {
88
98
fatalError ( " Failed to destroy the JVM. " )
89
99
}
90
100
}
101
+ }
91
102
103
+ // MARK: Java thread management.
104
+ extension JavaVirtualMachine {
92
105
/// Produce the JNI environment for the active thread, attaching this
93
106
/// thread to the JVM if it isn't already.
94
107
///
@@ -133,10 +146,92 @@ public final class JavaVirtualMachine: @unchecked Sendable {
133
146
}
134
147
}
135
148
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
+
136
229
extension JavaVirtualMachine {
137
230
enum VMError : Error {
138
231
case failedToCreateVM
139
232
case failedToAttachThread
140
233
case failedToDetachThread
234
+ case failedToQueryVM
235
+ case existingVM
141
236
}
142
237
}
0 commit comments