Skip to content

Commit a35573e

Browse files
authored
Refactor access to VM options and move some VM options to oal.util.Constants (#12754)
1 parent d6836d3 commit a35573e

File tree

7 files changed

+164
-133
lines changed

7 files changed

+164
-133
lines changed

lucene/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ Improvements
210210

211211
* GITHUB#12689: TaskExecutor to cancel all tasks on exception to avoid needless computation. (Luca Cavanna)
212212

213+
* GITHUB#12754: Refactor lookup of Hotspot VM options and do not initialize constants with NULL
214+
if SecurityManager prevents access. (Uwe Schindler)
215+
213216
Optimizations
214217
---------------------
215218
* GITHUB#12183: Make TermStates#build concurrent. (Shubham Chaudhary)

lucene/core/src/java/org/apache/lucene/internal/vectorization/VectorizationProvider.java

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.lang.StackWalker.StackFrame;
2222
import java.lang.invoke.MethodHandles;
2323
import java.lang.invoke.MethodType;
24-
import java.security.AccessController;
25-
import java.security.PrivilegedAction;
2624
import java.util.Locale;
2725
import java.util.Objects;
2826
import java.util.Optional;
@@ -31,7 +29,7 @@
3129
import java.util.function.Predicate;
3230
import java.util.logging.Logger;
3331
import java.util.stream.Stream;
34-
import org.apache.lucene.util.SuppressForbidden;
32+
import org.apache.lucene.util.Constants;
3533
import org.apache.lucene.util.VectorUtil;
3634

3735
/**
@@ -129,7 +127,7 @@ static VectorizationProvider lookup(boolean testMode) {
129127
"Vector bitsize and/or integer vectors enforcement; using default vectorization provider outside of testMode");
130128
return new DefaultVectorizationProvider();
131129
}
132-
if (isClientVM()) {
130+
if (Constants.IS_CLIENT_VM) {
133131
LOG.warning("C2 compiler is disabled; Java vector incubator API can't be enabled");
134132
return new DefaultVectorizationProvider();
135133
}
@@ -188,23 +186,6 @@ private static boolean isAffectedByJDK8301190() {
188186
&& !Objects.equals("I", "i".toUpperCase(Locale.getDefault()));
189187
}
190188

191-
@SuppressWarnings("removal")
192-
@SuppressForbidden(reason = "security manager")
193-
private static boolean isClientVM() {
194-
try {
195-
final PrivilegedAction<Boolean> action =
196-
() -> System.getProperty("java.vm.info", "").contains("emulated-client");
197-
return AccessController.doPrivileged(action);
198-
} catch (
199-
@SuppressWarnings("unused")
200-
SecurityException e) {
201-
LOG.warning(
202-
"SecurityManager denies permission to 'java.vm.info' system property, so state of C2 compiler can't be detected. "
203-
+ "In case of performance issues allow access to this property.");
204-
return false;
205-
}
206-
}
207-
208189
// add all possible callers here as FQCN:
209190
private static final Set<String> VALID_CALLERS = Set.of("org.apache.lucene.util.VectorUtil");
210191

lucene/core/src/java/org/apache/lucene/util/Constants.java

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,25 @@
1616
*/
1717
package org.apache.lucene.util;
1818

19+
import java.security.AccessController;
20+
import java.security.PrivilegedAction;
21+
import java.util.Objects;
22+
import java.util.logging.Logger;
23+
1924
/** Some useful constants. */
2025
public final class Constants {
2126
private Constants() {} // can't construct
2227

28+
private static final String UNKNOWN = "Unknown";
29+
2330
/** JVM vendor info. */
24-
public static final String JVM_VENDOR = System.getProperty("java.vm.vendor");
31+
public static final String JVM_VENDOR = getSysProp("java.vm.vendor", UNKNOWN);
2532

2633
/** JVM vendor name. */
27-
public static final String JVM_NAME = System.getProperty("java.vm.name");
34+
public static final String JVM_NAME = getSysProp("java.vm.name", UNKNOWN);
2835

2936
/** The value of <code>System.getProperty("os.name")</code>. * */
30-
public static final String OS_NAME = System.getProperty("os.name");
37+
public static final String OS_NAME = getSysProp("os.name", UNKNOWN);
3138

3239
/** True iff running on Linux. */
3340
public static final boolean LINUX = OS_NAME.startsWith("Linux");
@@ -45,36 +52,67 @@ private Constants() {} // can't construct
4552
public static final boolean FREE_BSD = OS_NAME.startsWith("FreeBSD");
4653

4754
/** The value of <code>System.getProperty("os.arch")</code>. */
48-
public static final String OS_ARCH = System.getProperty("os.arch");
55+
public static final String OS_ARCH = getSysProp("os.arch", UNKNOWN);
4956

5057
/** The value of <code>System.getProperty("os.version")</code>. */
51-
public static final String OS_VERSION = System.getProperty("os.version");
58+
public static final String OS_VERSION = getSysProp("os.version", UNKNOWN);
5259

5360
/** The value of <code>System.getProperty("java.vendor")</code>. */
54-
public static final String JAVA_VENDOR = System.getProperty("java.vendor");
61+
public static final String JAVA_VENDOR = getSysProp("java.vendor", UNKNOWN);
62+
63+
/** True iff the Java runtime is a client runtime and C2 compiler is not enabled */
64+
public static final boolean IS_CLIENT_VM =
65+
getSysProp("java.vm.info", "").contains("emulated-client");
5566

5667
/** True iff running on a 64bit JVM */
57-
public static final boolean JRE_IS_64BIT;
68+
public static final boolean JRE_IS_64BIT = is64Bit();
69+
70+
/** true iff we know fast FMA is supported, to deliver less error */
71+
public static final boolean HAS_FAST_FMA =
72+
(IS_CLIENT_VM == false)
73+
&& Objects.equals(OS_ARCH, "amd64")
74+
&& HotspotVMOptions.get("UseFMA").map(Boolean::valueOf).orElse(false);
5875

59-
static {
60-
boolean is64Bit = false;
61-
String datamodel = null;
76+
private static boolean is64Bit() {
77+
final String datamodel = getSysProp("sun.arch.data.model");
78+
if (datamodel != null) {
79+
return datamodel.contains("64");
80+
} else {
81+
return (OS_ARCH != null && OS_ARCH.contains("64"));
82+
}
83+
}
84+
85+
private static String getSysProp(String property) {
6286
try {
63-
datamodel = System.getProperty("sun.arch.data.model");
64-
if (datamodel != null) {
65-
is64Bit = datamodel.contains("64");
66-
}
87+
return doPrivileged(() -> System.getProperty(property));
6788
} catch (
6889
@SuppressWarnings("unused")
69-
SecurityException ex) {
90+
SecurityException se) {
91+
logSecurityWarning(property);
92+
return null;
7093
}
71-
if (datamodel == null) {
72-
if (OS_ARCH != null && OS_ARCH.contains("64")) {
73-
is64Bit = true;
74-
} else {
75-
is64Bit = false;
76-
}
94+
}
95+
96+
private static String getSysProp(String property, String def) {
97+
try {
98+
return doPrivileged(() -> System.getProperty(property, def));
99+
} catch (
100+
@SuppressWarnings("unused")
101+
SecurityException se) {
102+
logSecurityWarning(property);
103+
return def;
77104
}
78-
JRE_IS_64BIT = is64Bit;
105+
}
106+
107+
private static void logSecurityWarning(String property) {
108+
var log = Logger.getLogger(Constants.class.getName());
109+
log.warning("SecurityManager prevented access to system property: " + property);
110+
}
111+
112+
// Extracted to a method to be able to apply the SuppressForbidden annotation
113+
@SuppressWarnings("removal")
114+
@SuppressForbidden(reason = "security manager")
115+
private static <T> T doPrivileged(PrivilegedAction<T> action) {
116+
return AccessController.doPrivileged(action);
79117
}
80118
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.lucene.util;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
import java.util.function.Function;
23+
import java.util.logging.Logger;
24+
25+
/** Accessor to get Hotspot VM Options (if available). */
26+
final class HotspotVMOptions {
27+
private HotspotVMOptions() {} // can't construct
28+
29+
/** True if the Java VM is based on Hotspot and has the Hotspot MX bean readable by Lucene */
30+
public static final boolean IS_HOTSPOT;
31+
32+
/**
33+
* Returns an optional with the value of a Hotspot VM option. If the VM option does not exist or
34+
* is not readable, returns an empty optional.
35+
*/
36+
public static Optional<String> get(String name) {
37+
return ACCESSOR.apply(Objects.requireNonNull(name, "name"));
38+
}
39+
40+
private static final String MANAGEMENT_FACTORY_CLASS = "java.lang.management.ManagementFactory";
41+
private static final String HOTSPOT_BEAN_CLASS = "com.sun.management.HotSpotDiagnosticMXBean";
42+
private static final Function<String, Optional<String>> ACCESSOR;
43+
44+
static {
45+
boolean isHotspot = false;
46+
Function<String, Optional<String>> accessor = name -> Optional.empty();
47+
try {
48+
final Class<?> beanClazz = Class.forName(HOTSPOT_BEAN_CLASS);
49+
// we use reflection for this, because the management factory is not part
50+
// of java.base module:
51+
final Object hotSpotBean =
52+
Class.forName(MANAGEMENT_FACTORY_CLASS)
53+
.getMethod("getPlatformMXBean", Class.class)
54+
.invoke(null, beanClazz);
55+
if (hotSpotBean != null) {
56+
final Method getVMOptionMethod = beanClazz.getMethod("getVMOption", String.class);
57+
final Method getValueMethod = getVMOptionMethod.getReturnType().getMethod("getValue");
58+
isHotspot = true;
59+
accessor =
60+
name -> {
61+
try {
62+
final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, name);
63+
return Optional.of(getValueMethod.invoke(vmOption).toString());
64+
} catch (@SuppressWarnings("unused")
65+
ReflectiveOperationException
66+
| RuntimeException e) {
67+
return Optional.empty();
68+
}
69+
};
70+
}
71+
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
72+
isHotspot = false;
73+
final Logger log = Logger.getLogger(HotspotVMOptions.class.getName());
74+
final Module module = HotspotVMOptions.class.getModule();
75+
final ModuleLayer layer = module.getLayer();
76+
// classpath / unnamed module has no layer, so we need to check:
77+
if (layer != null
78+
&& layer.findModule("jdk.management").map(module::canRead).orElse(false) == false) {
79+
log.warning(
80+
"Lucene cannot access JVM internals to optimize algorithms or calculate object sizes, unless the 'jdk.management' Java module "
81+
+ "is readable [please add 'jdk.management' to modular application either by command line or its module descriptor].");
82+
} else {
83+
log.warning(
84+
"Lucene cannot optimize algorithms or calculate object sizes for JVMs that are not based on Hotspot or a compatible implementation.");
85+
}
86+
}
87+
IS_HOTSPOT = isHotspot;
88+
ACCESSOR = accessor;
89+
}
90+
}

lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.lang.reflect.Array;
2020
import java.lang.reflect.Field;
21-
import java.lang.reflect.Method;
2221
import java.lang.reflect.Modifier;
2322
import java.security.AccessControlException;
2423
import java.security.AccessController;
@@ -30,7 +29,6 @@
3029
import java.util.IdentityHashMap;
3130
import java.util.Locale;
3231
import java.util.Map;
33-
import java.util.logging.Logger;
3432
import org.apache.lucene.index.Term;
3533
import org.apache.lucene.search.BooleanClause;
3634
import org.apache.lucene.search.Query;
@@ -112,64 +110,16 @@ private RamUsageEstimator() {}
112110
/** For testing only */
113111
static final boolean JVM_IS_HOTSPOT_64BIT;
114112

115-
static final String MANAGEMENT_FACTORY_CLASS = "java.lang.management.ManagementFactory";
116-
static final String HOTSPOT_BEAN_CLASS = "com.sun.management.HotSpotDiagnosticMXBean";
117-
118113
/** Initialize constants and try to collect information about the JVM internals. */
119114
static {
120-
if (Constants.JRE_IS_64BIT) {
115+
if (Constants.JRE_IS_64BIT && HotspotVMOptions.IS_HOTSPOT) {
121116
// Try to get compressed oops and object alignment (the default seems to be 8 on Hotspot);
122117
// (this only works on 64 bit, on 32 bits the alignment and reference size is fixed):
123-
boolean compressedOops = false;
124-
int objectAlignment = 8;
125-
boolean isHotspot = false;
126-
try {
127-
final Class<?> beanClazz = Class.forName(HOTSPOT_BEAN_CLASS);
128-
// we use reflection for this, because the management factory is not part
129-
// of Java 8's compact profile:
130-
final Object hotSpotBean =
131-
Class.forName(MANAGEMENT_FACTORY_CLASS)
132-
.getMethod("getPlatformMXBean", Class.class)
133-
.invoke(null, beanClazz);
134-
if (hotSpotBean != null) {
135-
isHotspot = true;
136-
final Method getVMOptionMethod = beanClazz.getMethod("getVMOption", String.class);
137-
try {
138-
final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, "UseCompressedOops");
139-
compressedOops =
140-
Boolean.parseBoolean(
141-
vmOption.getClass().getMethod("getValue").invoke(vmOption).toString());
142-
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
143-
isHotspot = false;
144-
}
145-
try {
146-
final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, "ObjectAlignmentInBytes");
147-
objectAlignment =
148-
Integer.parseInt(
149-
vmOption.getClass().getMethod("getValue").invoke(vmOption).toString());
150-
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
151-
isHotspot = false;
152-
}
153-
}
154-
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
155-
isHotspot = false;
156-
final Logger log = Logger.getLogger(RamUsageEstimator.class.getName());
157-
final Module module = RamUsageEstimator.class.getModule();
158-
final ModuleLayer layer = module.getLayer();
159-
// classpath / unnamed module has no layer, so we need to check:
160-
if (layer != null
161-
&& layer.findModule("jdk.management").map(module::canRead).orElse(false) == false) {
162-
log.warning(
163-
"Lucene cannot correctly calculate object sizes on 64bit JVMs, unless the 'jdk.management' Java module "
164-
+ "is readable [please add 'jdk.management' to modular application either by command line or its module descriptor]");
165-
} else {
166-
log.warning(
167-
"Lucene cannot correctly calculate object sizes on 64bit JVMs that are not based on Hotspot or a compatible implementation.");
168-
}
169-
}
170-
JVM_IS_HOTSPOT_64BIT = isHotspot;
171-
COMPRESSED_REFS_ENABLED = compressedOops;
172-
NUM_BYTES_OBJECT_ALIGNMENT = objectAlignment;
118+
JVM_IS_HOTSPOT_64BIT = true;
119+
COMPRESSED_REFS_ENABLED =
120+
HotspotVMOptions.get("UseCompressedOops").map(Boolean::valueOf).orElse(false);
121+
NUM_BYTES_OBJECT_ALIGNMENT =
122+
HotspotVMOptions.get("ObjectAlignmentInBytes").map(Integer::valueOf).orElse(8);
173123
// reference size is 4, if we have compressed oops:
174124
NUM_BYTES_OBJECT_REF = COMPRESSED_REFS_ENABLED ? 4 : 8;
175125
// "best guess" based on reference size:

lucene/core/src/java20/org/apache/lucene/internal/vectorization/PanamaVectorUtilSupport.java

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -77,41 +77,9 @@ final class PanamaVectorUtilSupport implements VectorUtilSupport {
7777
VectorizationProvider.TESTS_FORCE_INTEGER_VECTORS || (isAMD64withoutAVX2 == false);
7878
}
7979

80-
private static final String MANAGEMENT_FACTORY_CLASS = "java.lang.management.ManagementFactory";
81-
private static final String HOTSPOT_BEAN_CLASS = "com.sun.management.HotSpotDiagnosticMXBean";
82-
83-
// best effort to see if FMA is fast (this is architecture-independent option)
84-
private static boolean hasFastFMA() {
85-
// on ARM cpus, FMA works fine but is a slight slowdown: don't use it.
86-
if (Constants.OS_ARCH.equals("amd64") == false) {
87-
return false;
88-
}
89-
try {
90-
final Class<?> beanClazz = Class.forName(HOTSPOT_BEAN_CLASS);
91-
// we use reflection for this, because the management factory is not part
92-
// of Java 8's compact profile:
93-
final Object hotSpotBean =
94-
Class.forName(MANAGEMENT_FACTORY_CLASS)
95-
.getMethod("getPlatformMXBean", Class.class)
96-
.invoke(null, beanClazz);
97-
if (hotSpotBean != null) {
98-
final var getVMOptionMethod = beanClazz.getMethod("getVMOption", String.class);
99-
final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, "UseFMA");
100-
return Boolean.parseBoolean(
101-
vmOption.getClass().getMethod("getValue").invoke(vmOption).toString());
102-
}
103-
return false;
104-
} catch (@SuppressWarnings("unused") ReflectiveOperationException | RuntimeException e) {
105-
return false;
106-
}
107-
}
108-
109-
// true if we know FMA is supported, to deliver less error
110-
static final boolean HAS_FAST_FMA = hasFastFMA();
111-
11280
// the way FMA should work! if available use it, otherwise fall back to mul/add
11381
private static FloatVector fma(FloatVector a, FloatVector b, FloatVector c) {
114-
if (HAS_FAST_FMA) {
82+
if (Constants.HAS_FAST_FMA) {
11583
return a.fma(b, c);
11684
} else {
11785
return a.mul(b).add(c);

0 commit comments

Comments
 (0)