|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. |
| 2 | + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
4 | 4 | *
|
5 | 5 | * This code is free software; you can redistribute it and/or modify it
|
|
38 | 38 | import java.nio.file.Path;
|
39 | 39 | import java.nio.file.Paths;
|
40 | 40 | import java.util.ArrayList;
|
| 41 | +import java.util.HashSet; |
| 42 | +import java.util.List; |
| 43 | +import java.util.Set; |
41 | 44 | import java.util.stream.Stream;
|
42 | 45 |
|
43 | 46 | public class ClassUnloadCommon {
|
@@ -67,6 +70,41 @@ public static void triggerUnloading() {
|
67 | 70 | wb.fullGC(); // will do class unloading
|
68 | 71 | }
|
69 | 72 |
|
| 73 | + /** |
| 74 | + * Calls triggerUnloading() in a retry loop for 2 seconds or until WhiteBox.isClassAlive |
| 75 | + * determines that no classes named in classNames are alive. |
| 76 | + * |
| 77 | + * This variant of triggerUnloading() accommodates the inherent raciness |
| 78 | + * of class unloading. For example, it's possible for a JIT compilation to hold |
| 79 | + * strong roots to types (e.g. in virtual call or instanceof profiles) that |
| 80 | + * are not released or converted to weak roots until the compilation completes. |
| 81 | + * |
| 82 | + * @param classNames the set of classes that are expected to be unloaded |
| 83 | + * @return the set of classes that have not been unloaded after exiting the retry loop |
| 84 | + */ |
| 85 | + public static Set<String> triggerUnloading(List<String> classNames) { |
| 86 | + WhiteBox wb = WhiteBox.getWhiteBox(); |
| 87 | + Set<String> aliveClasses = new HashSet<>(classNames); |
| 88 | + int attempt = 0; |
| 89 | + while (!aliveClasses.isEmpty() && attempt < 20) { |
| 90 | + ClassUnloadCommon.triggerUnloading(); |
| 91 | + for (String className : classNames) { |
| 92 | + if (aliveClasses.contains(className)) { |
| 93 | + if (wb.isClassAlive(className)) { |
| 94 | + try { |
| 95 | + Thread.sleep(100); |
| 96 | + } catch (InterruptedException ex) { |
| 97 | + } |
| 98 | + } else { |
| 99 | + aliveClasses.remove(className); |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + attempt++; |
| 104 | + } |
| 105 | + return aliveClasses; |
| 106 | + } |
| 107 | + |
70 | 108 | /**
|
71 | 109 | * Creates a class loader that loads classes from {@code ${test.class.path}}
|
72 | 110 | * before delegating to the system class loader.
|
|
0 commit comments