1 /*
  2  * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package java.lang;
 27 
 28 import java.io.PrintStream;
 29 import java.lang.ref.WeakReference;
 30 import java.util.ArrayList;
 31 import java.util.Arrays;
 32 import java.util.List;
 33 import java.util.Map;
 34 import java.util.Objects;
 35 import java.util.stream.Collectors;
 36 import java.util.stream.Stream;
 37 import jdk.internal.misc.VM;
 38 
 39 /**
 40  * A thread group represents a set of threads. In addition, a thread
 41  * group can also include other thread groups. The thread groups form
 42  * a tree in which every thread group except the initial thread group
 43  * has a parent.
 44  *
 45  * <p> A thread group has a name and maximum priority. The name is specified
 46  * when creating the group and cannot be changed. The group's maximum priority
 47  * is the maximum priority for threads created in the group. It is initially
 48  * inherited from the parent thread group but may be changed using the {@link
 49  * #setMaxPriority(int)} method.
 50  *
 51  * <p> A thread group is weakly <a href="ref/package-summary.html#reachability">
 52  * <em>reachable</em></a> from its parent group so that it is eligible for garbage
 53  * collection when there are no {@linkplain Thread#isAlive() live} threads in the
 54  * group and is otherwise <i>unreachable</i>.
 55  *
 56  * <p> Unless otherwise specified, passing a {@code null} argument to a constructor
 57  * or method in this class will cause a {@link NullPointerException} to be thrown.
 58  *
 59  * @apiNote
 60  * Thread groups provided a way in early JDK releases to group threads and provide
 61  * a form of <i>job control</i> for threads. Thread groups supported the isolation
 62  * of applets and defined methods intended for diagnostic purposes. The concept
 63  * of thread group is obsolete. It should be rare for new applications to create
 64  * thread groups or interact with this API.
 65  *
 66  * @since   1.0
 67  */
 68 public class ThreadGroup implements Thread.UncaughtExceptionHandler {
 69     /**
 70      * All fields are accessed directly by the VM and from JVMTI functions.
 71      * Operations that require synchronization on more than one group in the
 72      * tree should synchronize on the parent group before synchronizing on
 73      * the child group.
 74      */
 75     private final ThreadGroup parent;
 76     private final String name;
 77     private volatile int maxPriority;
 78 
 79     // strongly reachable from this group
 80     private int ngroups;
 81     private ThreadGroup[] groups;
 82 
 83     // weakly reachable from this group
 84     private int nweaks;
 85     private WeakReference<ThreadGroup>[] weaks;
 86 
 87     /**
 88      * Creates the top-level "system" ThreadGroup.
 89      *
 90      * @apiNote This method is invoked by the VM early startup.
 91      */
 92     private ThreadGroup() {
 93         this.parent = null;
 94         this.name = "system";
 95         this.maxPriority = Thread.MAX_PRIORITY;
 96     }
 97 
 98     /**
 99      * Creates a ThreadGroup without any permission or other checks.
100      */
101     ThreadGroup(ThreadGroup parent, String name, int maxPriority) {
102         this.parent = parent;
103         this.name = name;
104         this.maxPriority = maxPriority;
105         if (VM.isBooted()) {
106             parent.synchronizedAddWeak(this);
107         } else {
108             // keep strong reference to the "main" and other groups created
109             // early in the VM startup to avoid use weak references during
110             // when starting the reference handlers.
111             parent.synchronizedAddStrong(this);
112         }
113     }
114 
115     private ThreadGroup(Void unused, ThreadGroup parent, String name) {
116         this(parent, name, parent.getMaxPriority());
117     }
118 
119     private static Void checkParentAccess(ThreadGroup parent) {
120         parent.checkAccess();
121         return null;
122     }
123 
124     /**
125      * Constructs a new thread group. The parent of this new group is
126      * the thread group of the currently running thread.
127      * <p>
128      * The {@code checkAccess} method of the parent thread group is
129      * called with no arguments; this may result in a security exception.
130      *
131      * @param   name   the name of the new thread group, can be {@code null}
132      * @throws  SecurityException  if the current thread cannot create a
133      *               thread in the specified thread group.
134      *
135      * @deprecated
136      * Thread groups provided a way in early JDK releases to group threads and
137      * provide a form of <i>job control</i> for threads. Thread groups supported
138      * the isolation of applets and defined methods intended for diagnostic
139      * purposes. The concept of thread group is obsolete. It should be rare for
140      * new applications to create thread groups.
141      *
142      * @see     java.lang.ThreadGroup#checkAccess()
143      */
144     @Deprecated(since = "99")
145     public ThreadGroup(String name) {
146         this(Thread.currentThread().getThreadGroup(), name);
147     }
148 
149     /**
150      * Creates a new thread group. The parent of this new group is the
151      * specified thread group.
152      * <p>
153      * The {@code checkAccess} method of the parent thread group is
154      * called with no arguments; this may result in a security exception.
155      *
156      * @param     parent   the parent thread group.
157      * @param     name     the name of the new thread group, can be {@code null}
158      * @throws    SecurityException  if the current thread cannot create a
159      *               thread in the specified thread group.
160      *
161      * @deprecated
162      * Thread groups provided a way in early JDK releases to group threads and
163      * provide a form of <i>job control</i> for threads. Thread groups supported
164      * the isolation of applets and defined methods intended for diagnostic
165      * purposes. The concept of thread group is obsolete. It should be rare for
166      * new applications to create thread groups.
167      *
168      * @see     java.lang.ThreadGroup#checkAccess()
169      */
170     @Deprecated(since = "99")
171     public ThreadGroup(ThreadGroup parent, String name) {
172         this(checkParentAccess(parent), parent, name);
173     }
174 
175     /**
176      * Returns the name of this thread group.
177      *
178      * @return  the name of this thread group, may be {@code null}
179      */
180     public final String getName() {
181         return name;
182     }
183 
184     /**
185      * Returns the parent of this thread group.
186      * <p>
187      * First, if the parent is not {@code null}, the
188      * {@code checkAccess} method of the parent thread group is
189      * called with no arguments; this may result in a security exception.
190      *
191      * @return  the parent of this thread group. The top-level thread group
192      *          is the only thread group whose parent is {@code null}.
193      * @throws  SecurityException  if the current thread cannot modify
194      *               this thread group.
195      * @see        java.lang.ThreadGroup#checkAccess()
196      * @see        java.lang.SecurityException
197      * @see        java.lang.RuntimePermission
198      */
199     public final ThreadGroup getParent() {
200         if (parent != null)
201             parent.checkAccess();
202         return parent;
203     }
204 
205     /**
206      * Returns the maximum priority of this thread group. This is the maximum
207      * priority for new threads created in the thread group.
208      *
209      * @return  the maximum priority for new threads created in the thread group
210      * @see     #setMaxPriority
211      */
212     public final int getMaxPriority() {
213         return maxPriority;
214     }
215 
216     /**
217      * Returns false.
218      *
219      * @return false
220      *
221      * @deprecated This method originally indicated if the thread group is a
222      *             <i>daemon thread group</i> that is automatically destroyed
223      *             when its last thread terminates. The concept of daemon
224      *             thread group no longer exists.
225      */
226     @Deprecated(since="16", forRemoval=true)
227     public final boolean isDaemon() {
228         return false;
229     }
230 
231     /**
232      * Returns false.
233      *
234      * @return false
235      *
236      * @deprecated This method originally indicated if the thread group is
237      *             destroyed. The ability to destroy a thread group and the
238      *             concept of a destroyed thread group no longer exists.
239      *
240      * @since   1.1
241      */
242     @Deprecated(since="16", forRemoval=true)
243     public boolean isDestroyed() {
244         return false;
245     }
246 
247     /**
248      * Does nothing.
249      *
250      * @param daemon  ignored
251      *
252      * @deprecated This method originally changed the <i>daemon status</i> of
253      *             the thread group. A daemon thread group was automatically
254      *             destroyed when its last thread terminated. The concept of
255      *             daemon thread group and the concept of a destroyed thread
256      *             group no longer exists.
257      */
258     @Deprecated(since="16", forRemoval=true)
259     public final void setDaemon(boolean daemon) {
260     }
261 
262     /**
263      * Sets the maximum priority of the group. Threads in the thread
264      * group that already have a higher priority are not affected.
265      * <p>
266      * First, the {@code checkAccess} method of this thread group is
267      * called with no arguments; this may result in a security exception.
268      * <p>
269      * If the {@code pri} argument is less than
270      * {@link Thread#MIN_PRIORITY} or greater than
271      * {@link Thread#MAX_PRIORITY}, the maximum priority of the group
272      * remains unchanged.
273      * <p>
274      * Otherwise, the priority of this ThreadGroup object is set to the
275      * smaller of the specified {@code pri} and the maximum permitted
276      * priority of the parent of this thread group. (If this thread group
277      * is the system thread group, which has no parent, then its maximum
278      * priority is simply set to {@code pri}.) Then this method is
279      * called recursively, with {@code pri} as its argument, for
280      * every thread group that belongs to this thread group.
281      *
282      * @param      pri   the new priority of the thread group.
283      * @throws     SecurityException  if the current thread cannot modify
284      *               this thread group.
285      * @see        #getMaxPriority
286      * @see        java.lang.SecurityException
287      * @see        java.lang.ThreadGroup#checkAccess()
288      */
289     public final void setMaxPriority(int pri) {
290         checkAccess();
291         if (pri >= Thread.MIN_PRIORITY && pri <= Thread.MAX_PRIORITY) {
292             synchronized (this) {
293                 if (parent == null) {
294                     maxPriority = pri;
295                 } else {
296                     maxPriority = Math.min(pri, parent.maxPriority);
297                 }
298                 subgroups().forEach(g -> g.setMaxPriority(pri));
299             }
300         }
301     }
302 
303     /**
304      * Tests if this thread group is either the thread group
305      * argument or one of its ancestor thread groups.
306      *
307      * @param   g   a thread group.
308      * @return  {@code true} if this thread group is the thread group
309      *          argument or one of its ancestor thread groups;
310      *          {@code false} otherwise.
311      */
312     public final boolean parentOf(ThreadGroup g) {
313         for (; g != null ; g = g.parent) {
314             if (g == this) {
315                 return true;
316             }
317         }
318         return false;
319     }
320 
321     /**
322      * Determines if the currently running thread has permission to
323      * modify this thread group.
324      * <p>
325      * If there is a security manager, its {@code checkAccess} method
326      * is called with this thread group as its argument. This may result
327      * in throwing a {@code SecurityException}.
328      *
329      * @throws     SecurityException  if the current thread is not allowed to
330      *               access this thread group.
331      * @see        java.lang.SecurityManager#checkAccess(java.lang.ThreadGroup)
332      * @deprecated This method is only useful in conjunction with
333      *       {@linkplain SecurityManager the Security Manager}, which is
334      *       deprecated and subject to removal in a future release.
335      *       Consequently, this method is also deprecated and subject to
336      *       removal. There is no replacement for the Security Manager or this
337      *       method.
338      */
339     @Deprecated(since="17", forRemoval=true)
340     public final void checkAccess() {
341         @SuppressWarnings("removal")
342         SecurityManager security = System.getSecurityManager();
343         if (security != null) {
344             security.checkAccess(this);
345         }
346     }
347 
348     /**
349      * Returns an estimate of the number of active (meaning
350      * {@linkplain Thread#isAlive() alive}) threads in this thread group
351      * and its subgroups. Recursively iterates over all subgroups in this
352      * thread group.
353      *
354      * <p> The value returned is only an estimate because the number of
355      * threads may change dynamically while this method traverses internal
356      * data structures, and might be affected by the presence of certain
357      * system threads. This method is intended primarily for debugging
358      * and monitoring purposes.
359      *
360      * @return  an estimate of the number of active threads in this thread
361      *          group and in any other thread group that has this thread
362      *          group as an ancestor
363      */
364     public int activeCount() {
365         int n = 0;
366         for (Thread thread : Thread.getAllThreads()) {
367             ThreadGroup g = thread.getThreadGroup();
368             if (parentOf(g)) {
369                 n++;
370             }
371         }
372         return n;
373     }
374 
375     /**
376      * Copies into the specified array every active (meaning {@linkplain
377      * Thread#isAlive() alive}) thread in this thread group and its subgroups.
378      *
379      * <p> An invocation of this method behaves in exactly the same
380      * way as the invocation
381      *
382      * <blockquote>
383      * {@linkplain #enumerate(Thread[], boolean) enumerate}{@code (list, true)}
384      * </blockquote>
385      *
386      * @param  list
387      *         an array into which to put the list of threads
388      *
389      * @return  the number of threads put into the array
390      *
391      * @throws  SecurityException
392      *          if {@linkplain #checkAccess checkAccess} determines that
393      *          the current thread cannot access this thread group
394      */
395     public int enumerate(Thread[] list) {
396         return enumerate(list, true);
397     }
398 
399     /**
400      * Copies into the specified array every active (meaning {@linkplain
401      * Thread#isAlive() alive}) thread in this thread group. If {@code
402      * recurse} is {@code true}, this method recursively enumerates all
403      * subgroups of this thread group and references to every active thread
404      * in these subgroups are also included. If the array is too short to
405      * hold all the threads, the extra threads are silently ignored.
406      *
407      * <p> An application might use the {@linkplain #activeCount activeCount}
408      * method to get an estimate of how big the array should be, however
409      * <i>if the array is too short to hold all the threads, the extra threads
410      * are silently ignored.</i>  If it is critical to obtain every active
411      * thread in this thread group, the caller should verify that the returned
412      * int value is strictly less than the length of {@code list}.
413      *
414      * <p> Due to the inherent race condition in this method, it is recommended
415      * that the method only be used for debugging and monitoring purposes.
416      *
417      * @apiNote {@linkplain java.lang.management.ThreadMXBean} supports
418      * monitoring and management of active threads in the Java virtual
419      * machine and may be a more suitable API some many debugging and
420      * monitoring purposes.
421      *
422      * @param  list
423      *         an array into which to put the list of threads
424      *
425      * @param  recurse
426      *         if {@code true}, recursively enumerate all subgroups of this
427      *         thread group
428      *
429      * @return  the number of threads put into the array
430      *
431      * @throws  SecurityException
432      *          if {@linkplain #checkAccess checkAccess} determines that
433      *          the current thread cannot access this thread group
434      */
435     public int enumerate(Thread[] list, boolean recurse) {
436         Objects.requireNonNull(list);
437         checkAccess();
438         int n = 0;
439         if (list.length > 0) {
440             for (Thread thread : Thread.getAllThreads()) {
441                 ThreadGroup g = thread.getThreadGroup();
442                 if (g == this || (recurse && parentOf(g))) {
443                     list[n++] = thread;
444                     if (n == list.length) {
445                         // list full
446                         break;
447                     }
448                 }
449             }
450         }
451         return n;
452     }
453 
454     /**
455      * Returns an estimate of the number of groups in this thread group and its
456      * subgroups. Recursively iterates over all subgroups in this thread group.
457      *
458      * <p> The value returned is only an estimate because the number of
459      * thread groups may change dynamically while this method traverses
460      * internal data structures. This method is intended primarily for
461      * debugging and monitoring purposes.
462      *
463      * @return  the number of thread groups with this thread group as
464      *          an ancestor
465      */
466     public int activeGroupCount() {
467         int n = 0;
468         for (ThreadGroup group : synchronizedSubgroups()) {
469             n = n + group.activeGroupCount() + 1;
470         }
471         return n;
472     }
473 
474     /**
475      * Copies into the specified array references to every subgroup in this
476      * thread group and its subgroups.
477      *
478      * <p> An invocation of this method behaves in exactly the same
479      * way as the invocation
480      *
481      * <blockquote>
482      * {@linkplain #enumerate(ThreadGroup[], boolean) enumerate}{@code (list, true)}
483      * </blockquote>
484      *
485      * @param  list
486      *         an array into which to put the list of thread groups
487      *
488      * @return  the number of thread groups put into the array
489      *
490      * @throws  SecurityException
491      *          if {@linkplain #checkAccess checkAccess} determines that
492      *          the current thread cannot access this thread group
493      */
494     public int enumerate(ThreadGroup[] list) {
495         return enumerate(list, true);
496     }
497 
498     /**
499      * Copies into the specified array references to every subgroup in this
500      * thread group. If {@code recurse} is {@code true}, this method recursively
501      * enumerates all subgroups of this thread group and references to every
502      * thread group in these subgroups are also included.
503      *
504      * <p> An application might use the
505      * {@linkplain #activeGroupCount activeGroupCount} method to
506      * get an estimate of how big the array should be, however <i>if the
507      * array is too short to hold all the thread groups, the extra thread
508      * groups are silently ignored.</i>  If it is critical to obtain every
509      * subgroup in this thread group, the caller should verify that
510      * the returned int value is strictly less than the length of
511      * {@code list}.
512      *
513      * <p> Due to the inherent race condition in this method, it is recommended
514      * that the method only be used for debugging and monitoring purposes.
515      *
516      * @param  list
517      *         an array into which to put the list of thread groups
518      *
519      * @param  recurse
520      *         if {@code true}, recursively enumerate all subgroups
521      *
522      * @return  the number of thread groups put into the array
523      *
524      * @throws  SecurityException
525      *          if {@linkplain #checkAccess checkAccess} determines that
526      *          the current thread cannot access this thread group
527      */
528     public int enumerate(ThreadGroup[] list, boolean recurse) {
529         Objects.requireNonNull(list);
530         checkAccess();
531         return enumerate(list, 0, recurse);
532     }
533 
534     /**
535      * Add a reference to each subgroup to the given array, starting at
536      * the given index. Returns the new index.
537      */
538     private int enumerate(ThreadGroup[] list, int i, boolean recurse) {
539         List<ThreadGroup> subgroups = synchronizedSubgroups();
540         for (int j = 0; j < subgroups.size() && i < list.length; j++) {
541             ThreadGroup group = subgroups.get(j);
542             list[i++] = group;
543             if (recurse) {
544                 i = group.enumerate(list, i, true);
545             }
546         }
547         return i;
548     }
549 
550     /**
551      * Throws {@code UnsupportedOperationException}.
552      *
553      * @deprecated This method was originally specified to stop all threads in
554      *             the thread group. It was inherently unsafe.
555      */
556     @Deprecated(since="1.2", forRemoval=true)
557     public final void stop() {
558         throw new UnsupportedOperationException();
559     }
560 
561     /**
562      * Interrupts all active (meaning {@linkplain Thread#isAlive() alive}) in
563      * this thread group and its subgroups.
564      *
565      * @throws     SecurityException  if the current thread is not allowed
566      *               to access this thread group or any of the threads in
567      *               the thread group.
568      * @see        java.lang.Thread#interrupt()
569      * @see        java.lang.SecurityException
570      * @see        java.lang.ThreadGroup#checkAccess()
571      * @since      1.2
572      */
573     public final void interrupt() {
574         checkAccess();
575         for (Thread thread : Thread.getAllThreads()) {
576             ThreadGroup g = thread.getThreadGroup();
577             if (parentOf(g)) {
578                 thread.interrupt();
579             }
580         }
581     }
582 
583     /**
584      * Throws {@code UnsupportedOperationException}.
585      *
586      * @deprecated This method was originally specified to suspend all threads
587      *             in the thread group.
588      */
589     @Deprecated(since="1.2", forRemoval=true)
590     public final void suspend() {
591         throw new UnsupportedOperationException();
592     }
593 
594     /**
595      * Throws {@code UnsupportedOperationException}.
596      *
597      * @deprecated This method was originally specified to resume all threads
598      *             in the thread group.
599      */
600     @Deprecated(since="1.2", forRemoval=true)
601     public final void resume() {
602         throw new UnsupportedOperationException();
603     }
604 
605     /**
606      * Does nothing.
607      *
608      * @deprecated This method was originally specified to destroy an empty
609      *             thread group. The ability to destroy a thread group no
610      *             longer exists.
611      */
612     @Deprecated(since="16", forRemoval=true)
613     public final void destroy() {
614     }
615 
616     /**
617      * Prints information about this thread group to the standard
618      * output. This method is useful only for debugging.
619      */
620     public void list() {
621         Map<ThreadGroup, List<Thread>> map = Stream.of(Thread.getAllThreads())
622                 .collect(Collectors.groupingBy(Thread::getThreadGroup));
623         list(map, System.out, 0);
624     }
625 
626     private void list(Map<ThreadGroup, List<Thread>> map, PrintStream out, int indent) {
627         out.print(" ".repeat(indent));
628         out.println(this);
629         indent += 4;
630         List<Thread> threads = map.get(this);
631         if (threads != null) {
632             for (Thread thread : threads) {
633                 out.print(" ".repeat(indent));
634                 out.println(thread);
635             }
636         }
637         for (ThreadGroup group : synchronizedSubgroups()) {
638             group.list(map, out, indent);
639         }
640     }
641 
642     /**
643      * Called by the Java Virtual Machine when a thread in this
644      * thread group stops because of an uncaught exception, and the thread
645      * does not have a specific {@link Thread.UncaughtExceptionHandler}
646      * installed.
647      * <p>
648      * The {@code uncaughtException} method of
649      * {@code ThreadGroup} does the following:
650      * <ul>
651      * <li>If this thread group has a parent thread group, the
652      *     {@code uncaughtException} method of that parent is called
653      *     with the same two arguments.
654      * <li>Otherwise, this method checks to see if there is a
655      *     {@linkplain Thread#getDefaultUncaughtExceptionHandler default
656      *     uncaught exception handler} installed, and if so, its
657      *     {@code uncaughtException} method is called with the same
658      *     two arguments.
659      * <li>Otherwise, this method determines if the {@code Throwable}
660      *     argument is an instance of {@link ThreadDeath}. If so, nothing
661      *     special is done. Otherwise, a message containing the
662      *     thread's name, as returned from the thread's {@link
663      *     Thread#getName getName} method, and a stack backtrace,
664      *     using the {@code Throwable}'s {@link
665      *     Throwable#printStackTrace() printStackTrace} method, is
666      *     printed to the {@linkplain System#err standard error stream}.
667      * </ul>
668      * <p>
669      * Applications can override this method in subclasses of
670      * {@code ThreadGroup} to provide alternative handling of
671      * uncaught exceptions.
672      *
673      * @param   t   the thread that is about to exit.
674      * @param   e   the uncaught exception.
675      */
676     public void uncaughtException(Thread t, Throwable e) {
677         if (parent != null) {
678             parent.uncaughtException(t, e);
679         } else {
680             Thread.UncaughtExceptionHandler ueh =
681                 Thread.getDefaultUncaughtExceptionHandler();
682             if (ueh != null) {
683                 ueh.uncaughtException(t, e);
684             } else if (!(e instanceof ThreadDeath)) {
685                 System.err.print("Exception in thread \""
686                                  + t.getName() + "\" ");
687                 e.printStackTrace(System.err);
688             }
689         }
690     }
691 
692     /**
693      * Does nothing.
694      *
695      * @return false
696      *
697      * @param b ignored
698      *
699      * @deprecated This method was originally intended for controlling suspension
700      *             in low memory conditions. It was never specified.
701      *
702      * @since   1.1
703      */
704     @Deprecated(since="1.2", forRemoval=true)
705     public boolean allowThreadSuspension(boolean b) {
706         return false;
707     }
708 
709     /**
710      * Returns a string representation of this Thread group.
711      *
712      * @return  a string representation of this thread group.
713      */
714     public String toString() {
715         return getClass().getName()
716                 + "[name=" + getName()
717                 + ",maxpri=" + getMaxPriority()
718                 + "]";
719     }
720 
721     /**
722      * Add a strongly reachable subgroup.
723      */
724     private void synchronizedAddStrong(ThreadGroup group) {
725         synchronized (this) {
726             if (groups == null) {
727                 groups = new ThreadGroup[4];
728             } else if (groups.length == ngroups) {
729                 groups = Arrays.copyOf(groups, ngroups + 4);
730             }
731             groups[ngroups++] = group;
732         }
733     }
734 
735     /**
736      * Add a weakly reachable subgroup.
737      */
738     private void synchronizedAddWeak(ThreadGroup group) {
739         synchronized (this) {
740             if (weaks == null) {
741                 @SuppressWarnings({"unchecked", "rawtypes"})
742                 WeakReference<ThreadGroup>[] array = new WeakReference[4];
743                 weaks = array;
744             } else {
745                 // expunge
746                 for (int i = 0; i < nweaks; ) {
747                     ThreadGroup g = weaks[i].get();
748                     if (g == null) {
749                         removeWeak(i);
750                     } else {
751                         i++;
752                     }
753                 }
754                 // expand to make space if needed
755                 if (weaks.length == nweaks) {
756                     weaks = Arrays.copyOf(weaks, nweaks + 4);
757                 }
758             }
759             weaks[nweaks++] = new WeakReference<>(group);
760         }
761     }
762 
763     /**
764      * Remove the weakly reachable group at the given index of the weaks array.
765      */
766     private void removeWeak(int index) {
767         assert Thread.holdsLock(this) && index < nweaks;
768         int last = nweaks - 1;
769         if (index < nweaks)
770             weaks[index] = weaks[last];
771         weaks[last] = null;
772         nweaks--;
773     }
774 
775     /**
776      * Returns a snapshot of the subgroups.
777      */
778     private List<ThreadGroup> synchronizedSubgroups() {
779         synchronized (this) {
780             return subgroups();
781         }
782     }
783 
784     /**
785      * Returns a snapshot of the subgroups.
786      */
787     private List<ThreadGroup> subgroups() {
788         assert Thread.holdsLock(this);
789         List<ThreadGroup> snapshot = new ArrayList<>();
790         for (int i = 0; i < ngroups; i++) {
791             snapshot.add(groups[i]);
792         }
793         for (int i = 0; i < nweaks; ) {
794             ThreadGroup g = weaks[i].get();
795             if (g == null) {
796                 removeWeak(i);
797             } else {
798                 snapshot.add(g);
799                 i++;
800             }
801         }
802         return snapshot;
803     }
804 }