1 /* 2 * Copyright (c) 2003, 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 sun.font; 27 28 import java.awt.GraphicsConfiguration; 29 import java.awt.GraphicsEnvironment; 30 import java.lang.ref.Reference; 31 import java.lang.ref.ReferenceQueue; 32 import java.lang.ref.SoftReference; 33 import java.lang.ref.WeakReference; 34 import java.util.*; 35 36 import sun.java2d.Disposer; 37 import sun.java2d.pipe.BufferedContext; 38 import sun.java2d.pipe.RenderQueue; 39 import sun.java2d.pipe.hw.AccelGraphicsConfig; 40 import jdk.internal.misc.Unsafe; 41 42 /** 43 44 A FontStrike is the keeper of scaled glyph image data which is expensive 45 to compute so needs to be cached. 46 So long as that data may be being used it cannot be invalidated. 47 Yet we also need to limit the amount of native memory and number of 48 strike objects in use. 49 For scaleability and ease of use, a key goal is multi-threaded read 50 access to a strike, so that it may be shared by multiple client objects, 51 potentially executing on different threads, with no special reference 52 counting or "check-out/check-in" requirements which would pass on the 53 burden of keeping track of strike references to the SG2D and other clients. 54 55 A cache of strikes is maintained via Reference objects. 56 This helps in two ways : 57 1. The VM will free references when memory is low or they have not been 58 used in a long time. 59 2. Reference queues provide a way to get notification of this so we can 60 free native memory resources. 61 62 */ 63 64 @SuppressWarnings("removal") 65 public final class StrikeCache { 66 67 static final Unsafe unsafe = Unsafe.getUnsafe(); 68 69 static ReferenceQueue<Object> refQueue = Disposer.getQueue(); 70 71 static ArrayList<GlyphDisposedListener> disposeListeners = new ArrayList<GlyphDisposedListener>(1); 72 73 74 /* Reference objects may have their referents cleared when GC chooses. 75 * During application client start-up there is typically at least one 76 * GC which causes the hotspot VM to clear soft (not just weak) references 77 * Thus not only is there a GC pause, but the work done do rasterise 78 * glyphs that are fairly certain to be needed again almost immediately 79 * is thrown away. So for performance reasons a simple optimisation is to 80 * keep up to 8 strong references to strikes to reduce the chance of 81 * GC'ing strikes that have been used recently. Note that this may not 82 * suffice in Solaris UTF-8 locales where a single composite strike may be 83 * composed of 15 individual strikes, plus the composite strike. 84 * And this assumes the new architecture doesn't maintain strikes for 85 * natively accessed bitmaps. It may be worth "tuning" the number of 86 * strikes kept around for the platform or locale. 87 * Since no attempt is made to ensure uniqueness or ensure synchronized 88 * access there is no guarantee that this cache will ensure that unique 89 * strikes are cached. Every time a strike is looked up it is added 90 * to the current index in this cache. All this cache has to do to be 91 * worthwhile is prevent excessive cache flushing of strikes that are 92 * referenced frequently. The logic that adds references here could be 93 * tweaked to keep only strikes that represent untransformed, screen 94 * sizes as that's the typical performance case. 95 */ 96 static int MINSTRIKES = 8; // can be overridden by property 97 static int recentStrikeIndex = 0; 98 static FontStrike[] recentStrikes; 99 static boolean cacheRefTypeWeak; 100 101 /* 102 * Native sizes and offsets for glyph cache 103 * There are 10 values. 104 */ 105 static int nativeAddressSize; 106 static int glyphInfoSize; 107 static int xAdvanceOffset; 108 static int yAdvanceOffset; 109 static int boundsOffset; 110 static int widthOffset; 111 static int heightOffset; 112 static int rowBytesOffset; 113 static int topLeftXOffset; 114 static int topLeftYOffset; 115 static int pixelDataOffset; 116 static int cacheCellOffset; 117 static int managedOffset; 118 static long invisibleGlyphPtr; 119 120 /* Native method used to return information used for unsafe 121 * access to native data. 122 * return values as follows:- 123 * arr[0] = size of an address/pointer. 124 * arr[1] = size of a GlyphInfo 125 * arr[2] = offset of advanceX 126 * arr[3] = offset of advanceY 127 * arr[4] = offset of width 128 * arr[5] = offset of height 129 * arr[6] = offset of rowBytes 130 * arr[7] = offset of topLeftX 131 * arr[8] = offset of topLeftY 132 * arr[9] = offset of pixel data. 133 * arr[10] = address of a GlyphImageRef representing the invisible glyph 134 */ getGlyphCacheDescription(long[] infoArray)135 static native void getGlyphCacheDescription(long[] infoArray); 136 137 static { 138 139 long[] nativeInfo = new long[13]; 140 getGlyphCacheDescription(nativeInfo); 141 //Can also get address size from Unsafe class :- 142 //nativeAddressSize = unsafe.addressSize(); 143 nativeAddressSize = (int)nativeInfo[0]; 144 glyphInfoSize = (int)nativeInfo[1]; 145 xAdvanceOffset = (int)nativeInfo[2]; 146 yAdvanceOffset = (int)nativeInfo[3]; 147 widthOffset = (int)nativeInfo[4]; 148 heightOffset = (int)nativeInfo[5]; 149 rowBytesOffset = (int)nativeInfo[6]; 150 topLeftXOffset = (int)nativeInfo[7]; 151 topLeftYOffset = (int)nativeInfo[8]; 152 pixelDataOffset = (int)nativeInfo[9]; 153 invisibleGlyphPtr = nativeInfo[10]; 154 cacheCellOffset = (int) nativeInfo[11]; 155 managedOffset = (int) nativeInfo[12]; 156 157 if (nativeAddressSize < 4) { 158 throw new InternalError("Unexpected address size for font data: " + 159 nativeAddressSize); 160 } 161 java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Object>() { public Object run() { String refType = System.getProperty(R, R); cacheRefTypeWeak = refType.equals(R); String minStrikesStr = System.getProperty(R); if (minStrikesStr != null) { try { MINSTRIKES = Integer.parseInt(minStrikesStr); if (MINSTRIKES <= 0) { MINSTRIKES = 1; } } catch (NumberFormatException e) { } } recentStrikes = new FontStrike[MINSTRIKES]; return null; } })162 java.security.AccessController.doPrivileged( 163 new java.security.PrivilegedAction<Object>() { 164 public Object run() { 165 166 /* Allow a client to override the reference type used to 167 * cache strikes. The default is "soft" which hints to keep 168 * the strikes around. This property allows the client to 169 * override this to "weak" which hint to the GC to free 170 * memory more aggressively. 171 */ 172 String refType = 173 System.getProperty("sun.java2d.font.reftype", "soft"); 174 cacheRefTypeWeak = refType.equals("weak"); 175 176 String minStrikesStr = 177 System.getProperty("sun.java2d.font.minstrikes"); 178 if (minStrikesStr != null) { 179 try { 180 MINSTRIKES = Integer.parseInt(minStrikesStr); 181 if (MINSTRIKES <= 0) { 182 MINSTRIKES = 1; 183 } 184 } catch (NumberFormatException e) { 185 } 186 } 187 188 recentStrikes = new FontStrike[MINSTRIKES]; 189 190 return null; 191 } 192 }); 193 } 194 195 refStrike(FontStrike strike)196 static void refStrike(FontStrike strike) { 197 int index = recentStrikeIndex; 198 recentStrikes[index] = strike; 199 index++; 200 if (index == MINSTRIKES) { 201 index = 0; 202 } 203 recentStrikeIndex = index; 204 } 205 doDispose(FontStrikeDisposer disposer)206 private static void doDispose(FontStrikeDisposer disposer) { 207 if (disposer.intGlyphImages != null) { 208 freeCachedIntMemory(disposer.intGlyphImages, 209 disposer.pScalerContext); 210 } else if (disposer.longGlyphImages != null) { 211 freeCachedLongMemory(disposer.longGlyphImages, 212 disposer.pScalerContext); 213 } else if (disposer.segIntGlyphImages != null) { 214 /* NB Now making multiple JNI calls in this case. 215 * But assuming that there's a reasonable amount of locality 216 * rather than sparse references then it should be OK. 217 */ 218 for (int i=0; i<disposer.segIntGlyphImages.length; i++) { 219 if (disposer.segIntGlyphImages[i] != null) { 220 freeCachedIntMemory(disposer.segIntGlyphImages[i], 221 disposer.pScalerContext); 222 /* native will only free the scaler context once */ 223 disposer.pScalerContext = 0L; 224 disposer.segIntGlyphImages[i] = null; 225 } 226 } 227 /* This may appear inefficient but it should only be invoked 228 * for a strike that never was asked to rasterise a glyph. 229 */ 230 if (disposer.pScalerContext != 0L) { 231 freeCachedIntMemory(new int[0], disposer.pScalerContext); 232 } 233 } else if (disposer.segLongGlyphImages != null) { 234 for (int i=0; i<disposer.segLongGlyphImages.length; i++) { 235 if (disposer.segLongGlyphImages[i] != null) { 236 freeCachedLongMemory(disposer.segLongGlyphImages[i], 237 disposer.pScalerContext); 238 disposer.pScalerContext = 0L; 239 disposer.segLongGlyphImages[i] = null; 240 } 241 } 242 if (disposer.pScalerContext != 0L) { 243 freeCachedLongMemory(new long[0], disposer.pScalerContext); 244 } 245 } else if (disposer.pScalerContext != 0L) { 246 /* Rarely a strike may have been created that never cached 247 * any glyphs. In this case we still want to free the scaler 248 * context. 249 */ 250 if (longAddresses()) { 251 freeCachedLongMemory(new long[0], disposer.pScalerContext); 252 } else { 253 freeCachedIntMemory(new int[0], disposer.pScalerContext); 254 } 255 } 256 } 257 longAddresses()258 private static boolean longAddresses() { 259 return nativeAddressSize == 8; 260 } 261 disposeStrike(final FontStrikeDisposer disposer)262 static void disposeStrike(final FontStrikeDisposer disposer) { 263 // we need to execute the strike disposal on the rendering thread 264 // because they may be accessed on that thread at the time of the 265 // disposal (for example, when the accel. cache is invalidated) 266 267 // Whilst this is a bit heavyweight, in most applications 268 // strike disposal is a relatively infrequent operation, so it 269 // doesn't matter. But in some tests that use vast numbers 270 // of strikes, the switching back and forth is measurable. 271 // So the "pollRemove" call is added to batch up the work. 272 // If we are polling we know we've already been called back 273 // and can directly dispose the record. 274 // Also worrisome is the necessity of getting a GC here. 275 276 if (Disposer.pollingQueue) { 277 doDispose(disposer); 278 return; 279 } 280 281 RenderQueue rq = null; 282 GraphicsEnvironment ge = 283 GraphicsEnvironment.getLocalGraphicsEnvironment(); 284 if (!GraphicsEnvironment.isHeadless()) { 285 GraphicsConfiguration gc = 286 ge.getDefaultScreenDevice().getDefaultConfiguration(); 287 if (gc instanceof AccelGraphicsConfig) { 288 AccelGraphicsConfig agc = (AccelGraphicsConfig)gc; 289 BufferedContext bc = agc.getContext(); 290 if (bc != null) { 291 rq = bc.getRenderQueue(); 292 } 293 } 294 } 295 if (rq != null) { 296 rq.lock(); 297 try { 298 rq.flushAndInvokeNow(new Runnable() { 299 public void run() { 300 doDispose(disposer); 301 Disposer.pollRemove(); 302 } 303 }); 304 } finally { 305 rq.unlock(); 306 } 307 } else { 308 doDispose(disposer); 309 } 310 } 311 freeIntPointer(int ptr)312 static native void freeIntPointer(int ptr); freeLongPointer(long ptr)313 static native void freeLongPointer(long ptr); freeIntMemory(int[] glyphPtrs, long pContext)314 private static native void freeIntMemory(int[] glyphPtrs, long pContext); freeLongMemory(long[] glyphPtrs, long pContext)315 private static native void freeLongMemory(long[] glyphPtrs, long pContext); 316 freeCachedIntMemory(int[] glyphPtrs, long pContext)317 private static void freeCachedIntMemory(int[] glyphPtrs, long pContext) { 318 synchronized(disposeListeners) { 319 if (disposeListeners.size() > 0) { 320 ArrayList<Long> gids = null; 321 322 for (int i = 0; i < glyphPtrs.length; i++) { 323 if (glyphPtrs[i] != 0 && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) { 324 325 if (gids == null) { 326 gids = new ArrayList<Long>(); 327 } 328 gids.add((long) glyphPtrs[i]); 329 } 330 } 331 332 if (gids != null) { 333 // Any reference by the disposers to the native glyph ptrs 334 // must be done before this returns. 335 notifyDisposeListeners(gids); 336 } 337 } 338 } 339 340 freeIntMemory(glyphPtrs, pContext); 341 } 342 freeCachedLongMemory(long[] glyphPtrs, long pContext)343 private static void freeCachedLongMemory(long[] glyphPtrs, long pContext) { 344 synchronized(disposeListeners) { 345 if (disposeListeners.size() > 0) { 346 ArrayList<Long> gids = null; 347 348 for (int i=0; i < glyphPtrs.length; i++) { 349 if (glyphPtrs[i] != 0 350 && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) { 351 352 if (gids == null) { 353 gids = new ArrayList<Long>(); 354 } 355 gids.add(glyphPtrs[i]); 356 } 357 } 358 359 if (gids != null) { 360 // Any reference by the disposers to the native glyph ptrs 361 // must be done before this returns. 362 notifyDisposeListeners(gids); 363 } 364 } 365 } 366 367 freeLongMemory(glyphPtrs, pContext); 368 } 369 addGlyphDisposedListener(GlyphDisposedListener listener)370 public static void addGlyphDisposedListener(GlyphDisposedListener listener) { 371 synchronized(disposeListeners) { 372 disposeListeners.add(listener); 373 } 374 } 375 notifyDisposeListeners(ArrayList<Long> glyphs)376 private static void notifyDisposeListeners(ArrayList<Long> glyphs) { 377 for (GlyphDisposedListener listener : disposeListeners) { 378 listener.glyphDisposed(glyphs); 379 } 380 } 381 getStrikeRef(FontStrike strike)382 public static Reference<FontStrike> getStrikeRef(FontStrike strike) { 383 return getStrikeRef(strike, cacheRefTypeWeak); 384 } 385 getStrikeRef(FontStrike strike, boolean weak)386 public static Reference<FontStrike> getStrikeRef(FontStrike strike, boolean weak) { 387 /* Some strikes may have no disposer as there's nothing 388 * for them to free, as they allocated no native resource 389 * eg, if they did not allocate resources because of a problem, 390 * or they never hold native resources. So they create no disposer. 391 * But any strike that reaches here that has a null disposer is 392 * a potential memory leak. 393 */ 394 if (strike.disposer == null) { 395 if (weak) { 396 return new WeakReference<>(strike); 397 } else { 398 return new SoftReference<>(strike); 399 } 400 } 401 402 if (weak) { 403 return new WeakDisposerRef(strike); 404 } else { 405 return new SoftDisposerRef(strike); 406 } 407 } 408 409 static interface DisposableStrike { getDisposer()410 FontStrikeDisposer getDisposer(); 411 } 412 413 static class SoftDisposerRef 414 extends SoftReference<FontStrike> implements DisposableStrike { 415 416 private FontStrikeDisposer disposer; 417 getDisposer()418 public FontStrikeDisposer getDisposer() { 419 return disposer; 420 } 421 422 @SuppressWarnings("unchecked") SoftDisposerRef(FontStrike strike)423 SoftDisposerRef(FontStrike strike) { 424 super(strike, StrikeCache.refQueue); 425 disposer = strike.disposer; 426 Disposer.addReference((Reference<Object>)(Reference)this, disposer); 427 } 428 } 429 430 static class WeakDisposerRef 431 extends WeakReference<FontStrike> implements DisposableStrike { 432 433 private FontStrikeDisposer disposer; 434 getDisposer()435 public FontStrikeDisposer getDisposer() { 436 return disposer; 437 } 438 439 @SuppressWarnings("unchecked") WeakDisposerRef(FontStrike strike)440 WeakDisposerRef(FontStrike strike) { 441 super(strike, StrikeCache.refQueue); 442 disposer = strike.disposer; 443 Disposer.addReference((Reference<Object>)(Reference)this, disposer); 444 } 445 } 446 447 } 448