@@ -62,7 +62,7 @@ object ScalaReflection extends ScalaReflection {
6262 */
6363 def dataTypeFor [T : TypeTag ]: DataType = dataTypeFor(localTypeOf[T ])
6464
65- private def dataTypeFor (tpe : `Type`): DataType = ScalaReflectionLock . synchronized {
65+ private def dataTypeFor (tpe : `Type`): DataType = cleanUpReflectionObjects {
6666 tpe match {
6767 case t if t <:< definitions.IntTpe => IntegerType
6868 case t if t <:< definitions.LongTpe => LongType
@@ -92,7 +92,7 @@ object ScalaReflection extends ScalaReflection {
9292 * Array[T]. Special handling is performed for primitive types to map them back to their raw
9393 * JVM form instead of the Scala Array that handles auto boxing.
9494 */
95- private def arrayClassFor (tpe : `Type`): ObjectType = ScalaReflectionLock . synchronized {
95+ private def arrayClassFor (tpe : `Type`): ObjectType = cleanUpReflectionObjects {
9696 val cls = tpe match {
9797 case t if t <:< definitions.IntTpe => classOf [Array [Int ]]
9898 case t if t <:< definitions.LongTpe => classOf [Array [Long ]]
@@ -145,7 +145,7 @@ object ScalaReflection extends ScalaReflection {
145145 private def deserializerFor (
146146 tpe : `Type`,
147147 path : Option [Expression ],
148- walkedTypePath : Seq [String ]): Expression = ScalaReflectionLock . synchronized {
148+ walkedTypePath : Seq [String ]): Expression = cleanUpReflectionObjects {
149149
150150 /** Returns the current path with a sub-field extracted. */
151151 def addToPath (part : String , dataType : DataType , walkedTypePath : Seq [String ]): Expression = {
@@ -452,7 +452,7 @@ object ScalaReflection extends ScalaReflection {
452452 inputObject : Expression ,
453453 tpe : `Type`,
454454 walkedTypePath : Seq [String ],
455- seenTypeSet : Set [`Type`] = Set .empty): Expression = ScalaReflectionLock . synchronized {
455+ seenTypeSet : Set [`Type`] = Set .empty): Expression = cleanUpReflectionObjects {
456456
457457 def toCatalystArray (input : Expression , elementType : `Type`): Expression = {
458458 dataTypeFor(elementType) match {
@@ -638,7 +638,7 @@ object ScalaReflection extends ScalaReflection {
638638 * Returns true if the given type is option of product type, e.g. `Option[Tuple2]`. Note that,
639639 * we also treat [[DefinedByConstructorParams ]] as product type.
640640 */
641- def optionOfProductType (tpe : `Type`): Boolean = ScalaReflectionLock . synchronized {
641+ def optionOfProductType (tpe : `Type`): Boolean = cleanUpReflectionObjects {
642642 tpe match {
643643 case t if t <:< localTypeOf[Option [_]] =>
644644 val TypeRef (_, _, Seq (optType)) = t
@@ -700,7 +700,7 @@ object ScalaReflection extends ScalaReflection {
700700 def schemaFor [T : TypeTag ]: Schema = schemaFor(localTypeOf[T ])
701701
702702 /** Returns a catalyst DataType and its nullability for the given Scala Type using reflection. */
703- def schemaFor (tpe : `Type`): Schema = ScalaReflectionLock . synchronized {
703+ def schemaFor (tpe : `Type`): Schema = cleanUpReflectionObjects {
704704 tpe match {
705705 case t if t.typeSymbol.annotations.exists(_.tpe =:= typeOf[SQLUserDefinedType ]) =>
706706 val udt = getClassFromType(t).getAnnotation(classOf [SQLUserDefinedType ]).udt().newInstance()
@@ -766,7 +766,7 @@ object ScalaReflection extends ScalaReflection {
766766 /**
767767 * Whether the fields of the given type is defined entirely by its constructor parameters.
768768 */
769- def definedByConstructorParams (tpe : Type ): Boolean = {
769+ def definedByConstructorParams (tpe : Type ): Boolean = cleanUpReflectionObjects {
770770 tpe <:< localTypeOf[Product ] || tpe <:< localTypeOf[DefinedByConstructorParams ]
771771 }
772772
@@ -795,6 +795,20 @@ trait ScalaReflection {
795795 // Since the map values can be mutable, we explicitly import scala.collection.Map at here.
796796 import scala .collection .Map
797797
798+ /**
799+ * Any codes calling `scala.reflect.api.Types.TypeApi.<:<` should be wrapped by this method to
800+ * clean up the Scala reflection garbage automatically. Otherwise, it will leak some objects to
801+ * `scala.reflect.runtime.JavaUniverse.undoLog`.
802+ *
803+ * This method will also wrap `func` with `ScalaReflectionLock.synchronized` so the caller doesn't
804+ * need to call it again.
805+ *
806+ * @see https://github.com/scala/bug/issues/8302
807+ */
808+ def cleanUpReflectionObjects [T ](func : => T ): T = ScalaReflectionLock .synchronized {
809+ universe.asInstanceOf [scala.reflect.runtime.JavaUniverse ].undoLog.undo(func)
810+ }
811+
798812 /**
799813 * Return the Scala Type for `T` in the current classloader mirror.
800814 *
0 commit comments