Dela via


Övergång från Java 8 till Java 11

Det finns ingen lösning som passar alla för att överföra kod från Java 8 till Java 11. För ett icke-trivialt program kan det vara mycket arbete att flytta från Java 8 till Java 11. Potentiella problem är borttagna API:er, inaktuella paket, användning av internt API, ändringar av klassinläsare och ändringar i skräpinsamling.

I allmänhet är metoderna att försöka köra på Java 11 utan att kompilera om eller kompilera med JDK 11 först. Om målet är att få igång ett program så snabbt som möjligt är det ofta den bästa metoden att bara försöka köra på Java 11. För ett bibliotek är målet att publicera en artefakt som kompileras och testas med JDK 11.

Att flytta till Java 11 är värt besväret. Nya funktioner har lagts till och förbättringar har gjorts sedan Java 8. Dessa funktioner och förbättringar förbättrar start, prestanda, minnesanvändning och ger bättre integrering med containrar. Och det finns tillägg och ändringar i API:et som förbättrar utvecklarproduktiviteten.

Det här dokumentet berör verktyg för att inspektera kod. Den omfattar även problem som du kan stöta på och rekommendationer för att lösa dem. Du bör också läsa andra guider, till exempel Oracle JDK Migration Guide. Hur du gör befintlig kod modulär beskrivs inte här.

Verktygslådan

Java 11 har två verktyg, jdeprscan och jdeps, som är användbara för att sniffa upp potentiella problem. Dessa verktyg kan köras mot befintliga klass- eller jar-filer. Du kan utvärdera övergången utan att behöva kompilera om.

jdeprscan söker efter användning av inaktuellt eller borttaget API. Användning av inaktuellt API är inte ett blockerande problem, men är något att titta på. Finns det en uppdaterad jar-fil? Behöver du logga ett problem för att åtgärda användningen av inaktuellt API? Användning av borttaget API är ett blockerande problem som måste åtgärdas innan du försöker köra på Java 11.

jdeps, som är en beroendeanalysator för Java-klass. När det används med --jdk-internals alternativet anger jdeps vilken klass som är beroende av vilket internt API. Du kan fortsätta att använda internt API i Java 11, men att ersätta användningen bör vara en prioritet. OpenJDK wiki-sidan Java Dependency Analysis Tool har rekommenderat ersättningar för vissa vanliga interna JDK-API:er.

Det finns jdeps - och jdeprscan-plugin-program för både Gradle och Maven. Vi rekommenderar att du lägger till dessa verktyg i dina byggskript.

Java-kompilatorn, javac, är ett annat verktyg i verktygslådan. Varningarna och felen som du får från jdeprscan och jdeps kommer från kompilatorn. Fördelen med att använda jdeprscan och jdeps är att du kan köra dessa verktyg över befintliga jar-filer och klassfiler, inklusive bibliotek från tredje part.

Vad jdeprscan och jdeps inte kan göra är att varna om användningen av reflektion för att få åtkomst till inkapslat API. Reflekterande åtkomst kontrolleras vid körning. I slutändan måste du köra koden på Java 11 för att veta med säkerhet.

Använda jdeprscan

Det enklaste sättet att använda jdeprscan är att ge den en jar-fil från en befintlig version. Du kan också ge den en katalog, till exempel utdatakatalogen för kompilatorn eller ett enskilt klassnamn. Använd alternativet --release 11 för att få den mest fullständiga listan över inaktuella API:er. Om du vill prioritera vilket inaktuellt API du ska fokusera på, justera tillbaka inställningen till --release 8. API som är inaktuellt i Java 8 kommer sannolikt att tas bort tidigare än API:et som har blivit inaktuellt på senare tid.

jdeprscan --release 11 my-application.jar

Verktyget jdeprscan genererar ett felmeddelande om det har problem med att lösa en beroende klass. Till exempel error: cannot find class org/apache/logging/log4j/Logger. Vi rekommenderar att du lägger till beroende klasser i --class-path eller använder programklasssökvägen, men verktyget fortsätter genomsökningen utan den. Argumentet är --class-path. Inga andra varianter av argumentet class-path fungerar.

jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar
error: cannot find class sun/misc/BASE64Encoder
class com/company/Util uses deprecated method java/lang/Double::<init>(D)V

Den här utdatan anger att com.company.Util klassen anropar en inaktuell konstruktor för java.lang.Double klassen. Javadoc rekommenderar API att använda i stället för föråldrat API. Ingen mängd arbete löser problemet eftersom det är API:et error: cannot find class sun/misc/BASE64Encoder som har tagits bort. Sedan Java 8, java.util.Base64 ska användas.

Kör jdeprscan --release 11 --list för att få en uppfattning om vilket API som har varit inaktuellt sedan Java 8. Om du vill hämta en lista över API:et som har tagits bort kör du jdeprscan --release 11 --list --for-removal.

Använda jdeps

Använd jdeps med --jdk-internals alternativet för att hitta beroenden i JDK:s interna API. Kommandoradsalternativet --multi-release 11 behövs för det här exemplet eftersom log4j-core-2.13.0.jar är en jar-fil med flera versioner. Utan det här alternativet klagar jdeps om det hittar en jar-fil med flera versioner. Alternativet anger vilken version av klassfiler som ska inspekteras.

jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar
Util.class -> JDK removed internal API
Util.class -> jdk.base
Util.class -> jdk.unsupported
   com.company.Util        -> sun.misc.BASE64Encoder        JDK internal API (JDK removed internal API)
   com.company.Util        -> sun.misc.Unsafe               JDK internal API (jdk.unsupported)
   com.company.Util        -> sun.nio.ch.Util               JDK internal API (java.base)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.misc.Unsafe                          See http://openjdk.java.net/jeps/260   

Utdata ger några goda råd om hur du eliminerar användningen av JDK:s interna API! Om möjligt föreslås ersättnings-API:et. Namnet på modulen där paketet kapslas in anges i parenteserna. Modulnamnet kan användas med --add-exports eller --add-opens om det är nödvändigt att uttryckligen bryta inkapslingen.

Användningen av sun.misc.BASE64Encoder eller sun.misc.BASE64Decoder resulterar i en java.lang.NoClassDefFoundError i Java 11. Kod som använder dessa API:er måste ändras för att använda java.util.Base64.

Försök att eliminera användningen av alla API:er som kommer från modulen jdk.unsupported. API från den här modulen refererar till JDK Enhancement Proposal (JEP) 260 som en föreslagen ersättning. I ett nötskal säger JEP 260 att användningen av internt API kommer att stödjas tills ersättnings-API är tillgängligt. Även om din kod kan använda JDK:s interna API fortsätter den att köras, åtminstone ett tag. Ta en titt på JEP 260 eftersom det pekar på ersättningar för vissa interna API: er. variabelhandtag kan användas i stället för vissa sun.misc.Unsafe API, till exempel.

jdeps kan göra mer än att bara söka efter användning av JDK internals. Det är ett användbart verktyg för att analysera beroenden och generera en modulinformationsfil. Mer information finns i dokumentationen .

Att använda javac

Kompilering med JDK 11 kräver uppdateringar för att skapa skript, verktyg, testramverk och bibliotek som ingår. Använd alternativet -Xlint:unchecked för javac för att få information om användningen av JDK:s interna API och andra varningar. Det kan också vara nödvändigt att använda --add-opens eller --add-reads exponera inkapslade paket för kompilatorn (se JEP 261).

Bibliotek kan överväga att paketera som en jar-fil med flera versioner. Med jar-filer med flera versioner kan du stödja både Java 8- och Java 11-körningar från samma jar-fil. De lägger till komplexitet i bygget. Hur du skapar jars för flera versioner ligger utanför omfånget för det här dokumentet.

Körs på Java 11

De flesta program bör köras på Java 11 utan ändringar. Det första du ska prova är att köra på Java 11 utan att kompilera om koden. Poängen med att bara köra är att se vilka varningar och fel som genereras vid körning. Den här metoden får ett resultat som...
applikation för att köra på Java 11 snabbare genom att fokusera på det nödvändigaste arbetet.

De flesta av de problem du kan stöta på kan lösas utan att behöva kompilera om koden. Om ett problem måste åtgärdas i koden gör du korrigeringen men fortsätter att kompilera med JDK 8. Om möjligt kan du arbeta med att få programmet att köras med java version 11 innan du kompilerar med JDK 11.

Kontrollera kommandoradsalternativ

Innan du kör java 11 gör du en snabb genomsökning av kommandoradsalternativen. Alternativ som har tagits bort gör att den virtuella Java-datorn (JVM) avslutas. Den här kontrollen är särskilt viktig om du använder GC-loggningsalternativ eftersom de har ändrats drastiskt från Java 8. JaCoLine-verktyget är bra att använda för att identifiera problem med kommandoradsalternativen.

Kontrollera bibliotek från tredje part

En potentiell källa till problem är bibliotek från tredje part som du inte kontrollerar. Du kan proaktivt uppdatera bibliotek från tredje part till nyare versioner. Eller så kan du se vad som inte går att köra programmet och bara uppdatera de bibliotek som behövs. Problemet med att uppdatera alla bibliotek till en ny version är att det gör det svårare att hitta rotorsaken om det finns ett fel i programmet. Inträffade felet på grund av ett uppdaterat bibliotek? Eller orsakades felet av någon förändring i körmiljön? Problemet med att bara uppdatera det som krävs är att det kan ta flera iterationer att lösa.

Rekommendationen här är att göra så få ändringar som möjligt och att uppdatera bibliotek från tredje part som en separat insats. Om du uppdaterar ett bibliotek från tredje part vill du oftast ha den senaste och bästa versionen som är kompatibel med Java 11. Beroende på hur långt bakom den aktuella versionen är kanske du vill använda en mer försiktig metod och uppgradera till den första Java 9+-kompatibla versionen.

Förutom att titta på releasenoter kan du använda jdeps och jdeprscan för att utvärdera jar-filen. Dessutom har OpenJDK-kvalitetsgruppen en wiki-sida för kvalitetsuppsökande som visar status för testning av många FOSS-projekt (Free Open Source Software) mot versioner av OpenJDK.

Ange skräpsamling explicit

Parallell skräpinsamlare (Parallell GC) är standard-GC i Java 8. Om programmet använder standardvärdet bör GC anges uttryckligen med kommandoradsalternativet -XX:+UseParallelGC. Standardinställningen ändrades i Java 9 till Garbage First-skräpinsamlaren (G1GC). För att göra en rättvis jämförelse av ett program som körs på Java 8 jämfört med Java 11 måste GC-inställningarna vara desamma. Experimentering med GC-inställningarna bör skjutas upp tills programmet har verifierats på Java 11.

Ange uttryckligen standardalternativ

Om du kör på den virtuella HotSpot-datorn dumpar inställningen kommandoradsalternativet -XX:+PrintCommandLineFlags värdena för de alternativ som angetts av den virtuella datorn, särskilt de standardvärden som anges av GC. Kör med den här flaggan på Java 8 och använd de utskrivna alternativen när du kör på Java 11. För det mesta är standardvärdena desamma från 8 till 11. Men att använda inställningarna från 8 garanterar paritet.

Vi rekommenderar att du anger kommandoradsalternativet --illegal-access=warn . I Java 11 resulterar reflektion för åtkomst till JDK-internt API i en varning om felaktig reflekterande åtkomst. Som standard utfärdas varningen endast för den första olagliga åtkomsten. Inställningen --illegal-access=warn kommer att orsaka en varning vid varje otillåten reflektiv åtkomst. Du hittar fler fall om olaglig åtkomst med alternativet inställt på att varna. Men du får också många redundanta varningar.
När programmet körs på Java 11 anger du --illegal-access=deny för att efterlikna det framtida beteendet för Java-körningen. Från och med Java 16 är --illegal-access=denystandardvärdet .

Varningar för ClassLoader

I Java 8 kan du omvandla systemklassinläsaren till en URLClassLoader. Detta görs vanligtvis av program och bibliotek som vill injicera klasser i klassvägen under körning. Klassinläsningshierarkin har ändrats i Java 11. Systemklassinläsaren (även kallad programklassinläsaren) är nu en intern klass. Casting till en URLClassLoader kommer att utlösa en ClassCastException vid körning. Java 11 har inte något API:er för att dynamiskt utöka klassvägen vid körning, men det kan göras genom reflektion, med uppenbara varningar för användningen av inre API:er.

I Java 11 läser startklassinläsaren bara in kärnmoduler. Om du skapar en klassladdare med en null-förälder kan det hända att den inte hittar alla plattformsklasser. I Java 11 ska du använda ClassLoader.getPlatformClassLoader() i stället för null som föräldraklassladdare i sådana fall.

Ändringar av nationella data

Standardkällan för nationella data i Java 11 ändrades med JEP 252 till Unicode Consortiums Common Locale Data Repository. Detta kan påverka den lokaliserade formateringen. Ange systemegenskapen java.locale.providers=COMPAT,SPI så att den återgår till java 8-språkbeteendet om det behövs.

Potentiella problem

Här är några av de vanliga problem som du kan stöta på. Följ länkarna för mer information om dessa problem.

Okända alternativ

Om ett kommandoradsalternativ har tagits bort skrivs programmet ut Unrecognized option: eller Unrecognized VM option följs av namnet på det felaktiga alternativet. Ett okänt alternativ gör att den virtuella datorn avslutas. Alternativ som har blivit inaktuella men inte tagits bort kommer att skapa en VM-varning.

I allmänhet har alternativ som har tagits bort ingen ersättning och det enda sättet är att ta bort alternativet från kommandoraden. Undantaget är alternativ för loggning av skräpinsamling. GC-loggning omimplementerades i Java 9 för att använda det enhetliga JVM-loggningsramverket. Se "Tabell 2-2 Mappa loggningsflaggor för äldre skräpinsamling till Xlog-konfigurationen" i avsnittet Aktivera loggning med JVM Unified Logging Framework i Java SE 11 Tools-referensen.

VM-varningar

Användning av inaktuella alternativ ger en varning. Ett alternativ är inaktuellt när det har ersatts eller inte längre är användbart. Precis som med borttagna alternativ bör dessa alternativ tas bort från kommandoraden. Varningen VM Warning: Option <option> was deprecated innebär att alternativet fortfarande stöds, men att stödet kan tas bort i framtiden. Ett alternativ som inte längre stöds och som genererar varningen VM Warning: Ignoring option. Alternativ som inte längre stöds har ingen effekt på körningen.

I utforskaren för vm-alternativ på webbsidan finns en fullständig lista över alternativ som har lagts till i eller tagits bort från Java sedan JDK 7.

Fel: Det gick inte att skapa den virtuella Java-datorn

Det här felmeddelandet skrivs ut när JVM påträffar ett okänt alternativ.

VARNING! En otillåten reflektiv åtkomstoperation har inträffat

När Java-kod använder reflektion för att få åtkomst till JDK-internt API utfärdar körningen en varning om otillåten reflekterande åtkomst.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int)
WARNING: Please consider reporting this to the maintainers of com.company.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Det innebär att en modul inte har exporterat det paket som nås via reflection. Paketet är inkapslat i modulen och är i princip internt API. Varningen kan ignoreras som ett första försök att komma igång på Java 11. Java 11-körningen tillåter reflekterande åtkomst så att äldre kod kan fortsätta att fungera.

Om du vill åtgärda den här varningen letar du efter uppdaterad kod som inte använder det interna API:et. Om problemet inte kan lösas med uppdaterad kod kan antingen kommandoradsalternativet --add-exports--add-opens eller användas för att öppna åtkomsten till paketet. De här alternativen ger åtkomst till oexporterade typer av en modul från en annan modul.

Med --add-exports alternativet kan målmodulen komma åt de offentliga typerna av det namngivna paketet i källmodulen. Ibland används setAccessible(true) kod för att komma åt icke-offentliga medlemmar och API. Detta kallas djup reflektion. I det här fallet kan du använda --add-opens för att ge din kod åtkomst till icke-offentliga medlemmar i ett paket. Om du är osäker på om du vill använda --add-exports eller --add-opens börjar du med --add-exports.

Alternativen --add-exports eller --add-opens bör betraktas som en lösning, inte som en långsiktig lösning. Med de här alternativen bryts inkapslingen av modulsystemet, vilket är avsett att hindra JDK-internt API från att användas. Om det interna API:et tas bort eller ändras misslyckas programmet. Reflekterande åtkomst nekas i Java 16, förutom där åtkomst aktiveras av kommandoradsalternativ som --add-opens. Om du vill efterlikna det framtida beteendet anger du --illegal-access=deny på kommandoraden.

Varningen i exemplet ovan utfärdas eftersom sun.nio.ch paketet inte exporteras av modulen java.base . Med andra ord finns det inget exports sun.nio.ch; i filen för module-info.java modulen java.base. Detta kan lösas med --add-exports=java.base/sun.nio.ch=ALL-UNNAMED. Klasser som inte definieras i en modul tillhör implicit den namnlösa modulen, bokstavligen med namnet ALL-UNNAMED.

java.lang.reflect.InaccessibleObjectException

Det här undantaget anger att du försöker anropa setAccessible(true) ett fält eller en metod för en inkapslad klass. Du kan också få en varning om otillåten reflekterande åtkomst. Använd alternativet --add-opens för att ge din kod åtkomst till icke-offentliga medlemmar i ett paket. Undantagsmeddelandet kommer att informera dig om att modulen "inte öppnar" paketet för den modul som försöker anropa setAccessible. Om modulen är "namnlös modul" använder du UNNAMED-MODULE som målmodul i alternativet --add-opens .

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: 
module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6

$ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Main

java.lang.NoClassDefFoundError

NoClassDefFoundError orsakas troligen av ett delat paket eller genom att referera till borttagna moduler.

NoClassDefFoundError orsakas av split-packages

Ett delat paket är när ett paket hittas i mer än ett bibliotek. Symptomet för ett problem med delade paket är att en klass som du vet är på klasssökvägen inte hittas.

Det här problemet uppstår bara när du använder modulsökvägen. Java-modulsystemet optimerar klasssökning genom att begränsa ett paket till en namngiven modul. Körtiden ger företräde åt modulsökvägen framför klassökvägen vid en klassökning. Om ett paket delas mellan en modul och klasssökvägen används endast modulen för att göra klasssökningen. Detta kan leda till NoClassDefFound fel.

Ett enkelt sätt att söka efter ett delat paket är att ansluta modulsökvägen och klasssökvägen till jdeps och använda sökvägen till programklassfilerna <som sökväg>. Om det finns ett delat paket skriver jdeps ut en varning: Warning: split package: <package-name> <module-path> <split-path>.

Det här problemet kan lösas genom att använda --patch-module <module-name>=<path>[,<path>] för att lägga till split-paketet i den namngivna modulen.

NoClassDefFoundError orsakas av java-EE- eller CORBA-moduler

Om programmet körs på Java 8 men genererar en java.lang.NoClassDefFoundError eller en java.lang.ClassNotFoundException, är det troligt att programmet använder ett paket från Java EE- eller CORBA-modulerna. Modulerna är inaktuella i Java 9 och har tagits bort i Java 11.

Lös problemet genom att lägga till ett körningsberoende i projektet.

Modulen har tagits bort Berört paket Föreslaget beroende
Java API för XML Web Services (JAX-WS) java.xml.ws JAX WS RI Runtime
Java-arkitektur för XML-bindning (JAXB) java.xml.bind JAXB Runtime
JavaBeans Activation Framework (JAV) java.activation Aktiveringsramverk för JavaBeans (TM)
Vanliga anteckningar java.xml.ws.annotation Javax-antecknings-API
CORBA (Common Object Request Broker-arkitektur) java.corba GlassFish CORBA ORB
Java Transaction API (JTA) java.transaction Java-transaktions-API

-Xbootclasspath/p är inte längre ett alternativ som stöds

Stöd för -Xbootclasspath/p har tagits bort. Använd --patch-module i stället. Alternativet --patch-module beskrivs i JEP 261. Leta efter avsnittet med etiketten "Patching module content". --patch-module kan användas med javac och med java för att åsidosätta eller utöka klasserna i en modul.

Vad --patch-module i själva verket gör är att infoga korrigeringsmodulen i modulsystemets klasssökning. Modulsystemet hämtar klassen från korrigeringsmodulen först. Det här är samma effekt som i väntan på startklassökvägen i Java 8.

ClassVersionError stöds inte

Det här undantaget innebär att du försöker köra kod som kompilerats med en senare version av Java på en tidigare version av Java. Du kör till exempel på Java 11 med en jar-fil som kompilerats med JDK 13.

Java-version Klassfilformatversion
8 52
9 53
10 54
11 55
12 56
tretton 57

Nästa steg

När programmet körs på Java 11 bör du överväga att flytta bibliotek från klasssökvägen och till modulsökvägen. Leta efter uppdaterade versioner av biblioteken som programmet är beroende av. Välj modulära bibliotek om det är tillgängligt. Använd modulsökvägen så mycket som möjligt, även om du inte planerar att använda moduler i ditt program. Att använda modulsökvägen har bättre prestanda för klassinläsning än vad klasssökvägen gör.