从 Java 8 升级到 Java 17 全过程,避坑!

1 篇文章 0 订阅

 

  • 编译相关

    • JEP 320

    • 使用了 sun.misc.* 下的包

    • lombok 使用了 com.sun.tools.javac.* 下的包

    • kotlin 版本限制

    • 废弃依赖分析

  • 参数迁移

    • 什么是 Unified Logging

    • 输出什么信息(selectors)

    • 第二部分:输出到哪里(output)

    • 第三部分:日志 decorators

    • GC 参数迁移

  • 运行相关

    • 反射+私有 API 调用之伤

    • 关于 GC 算法的选择

    • G1 参数调整


最近在做 Java8 到 Java17 的迁移工作,前期做了一些准备,但是在升级过程还是有些问题,太emo了,一些信息记录如下,分为几个部分:

  • 编译相关

  • 参数迁移相关

  • 运行相关

前人栽树后人乘凉,有需要升级的可以参考一下,避免踩坑。。。

编译相关

JEP 320

在 Java11 中引入了一个提案 JEP 320: Remove the Java EE and CORBA Modules (openjdk.org/jeps/320) 提案,移除了 Java EE and CORBA 的模块,如果项目中用到需要手动引入。比如代码中用到了 javax.annotation.* 下的包:

public abstract class FridayAgent 
    @PreDestroy
    public void destroy() {
        agentClient.close();
    }
}    

在编译时会找不到相关的类。这是因为 Java EE 已经在 Java 9 中被标记为 deprecated,Java 11 中被正式移除,可以手动引入 javax 的包:

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

使用了 sun.misc.* 下的包

比如 sun.misc.BASE64Encoder,这个简单,替换一下工具类即可。

[ERROR]   symbol:   class BASE64Encoder
[ERROR]   location: package sun.misc

netty 低版本使用了 sun.misc.*,编译错误信息如下

Caused by: java.lang.NoClassDefFoundError: Could not initialize class io.netty.util.internal.PlatformDependent0
        at io.netty.util.internal.PlatformDependent.getSystemClassLoader(PlatformDependent.java:694) ~[netty-all-4.0.42.Final.jar!/:4.0.42.Final]

对应的源码如下:

/**
 * The {@link PlatformDependent} operations which requires access to {@code sun.misc.*}.
 */
final class PlatformDependent0 {
}

https://github.com/netty/netty/issues/6855

lombok 使用了 com.sun.tools.javac.* 下的包

错误信息如下:

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.2:compile (default-compile) on project encloud-common: Fatal error compiling: java.lang.ExceptionInInitializerError: Unable to make field private com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs accessible: module jdk.compiler does not "opens com.sun.tools.javac.processing" to unnamed module

如果你的项目中使用 lombok,而且是低版本的话,就会出现,lombok 的原理是在编译期做一些手脚,用到了 com.sun.tools.javac 下的文件,升级到最新版可以解决。ps,个人很不喜欢 lombok, 调试的时候代码和 class 对不上真的很恶心。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
   <!-- <version>1.16.4</version>-->
    <version>1.18.24</version>
</dependency>

kotlin 版本限制

我们后端在很多年前就 all-in Kotlin,Kotlin 的升级也是我们的重中之重。

[ERROR] Failed to execute goal org.jetbrains.kotlin:kotlin-maven-plugin:1.2.71:compile (compile) on project encloud-core: Compilation failure [ERROR] Unknown JVM target version: 17 [ERROR] Supported versions: 1.6, 1.8

Kotlin 在 1.6.0 版本开始支持 Java17 的字节码,低于 1.6.0 的编译会直接报错

废弃依赖分析

可以用 jdeps --jdk-internals --multi-release 17 --class-path . encloud-api.jar 来做项目的依赖分析

图片

这样你就可以知道哪些库需要做升级了。

参数迁移

什么是 Unified Logging

在 Java 领域,有广为人知的日志框架,slf4j、log4j 等,这些框架提供了统一的编程接口,让用户可以通过简单的配置实现日志输出的个性化配置,比如日志 tag、级别(info、debug 等)、上下文(线程 id、行号、时间等),在 JVM 内部之前一直缺乏这样的规范,于是出来了 Unified Logging,实现了日志格式的大一统,这就是我们接下来要介绍的重点 Unified Logging

我们接触最多的是 gc 的日志,在 java8 中,我们配置 gc 日志的参数是 -Xloggc:/tmp/gc.log。在 JVM 中除了 GC,还有大量的其它相关的日志,比如线程、os 等,在新的 Unified Logging 日志中,日志输出的方式变更为了 java -Xlog:xxx,GC 不再特殊只是做为日志的一种存在形式。

java -Xlog -version

输出结果如下:

图片

可以看到日志输出里,不仅有 GC 相关的日志,还有 os 线程相关的信息。事实上 java 的日志的生产者有非常多部分,比如 thread、class load、unload、safepoint、cds 等。

图片

归根到底,日志打印,需要回答清楚三个问题:

  • what:要输出什么信息(tag),以什么日志级别输出(level)

  • where:输出到哪里(console 还是 file)

  • decorators:日志如何

输出什么信息(selectors)

首先来看 what 的部分,如何指定要输出哪些信息,这个在 JVM 内部被称之为 selectors。

JVM 采用的是 <tag-set>=<level>的形式来表示 selectors,默认情况下,tag 为all,表示所有的 tag,level 为 INFOjava -Xlog -version 等价于下面的形式

java -Xlog:all=info -version

如果我们想输出tag 为 gc,日志级别为 debug 的日志,可以用 java -Xlog:gc=debug 的形式:

$ java -Xlog:gc=debug -version
[0.023s][info][gc] Using G1
[0.023s][debug][gc] ConcGCThreads: 3 offset 22
[0.023s][debug][gc] ParallelGCThreads: 10
[0.024s][debug][gc] Initialize mark stack with 4096 chunks, maximum 524288

这样就输出了 tag 为 gc,级别为 debug 的日志信息。

不过这里有一个比较坑的点是,这里的 tag 匹配规则是精确匹配,如果某条日志的 tag 是 gc,metaspace,通过上面的规则是匹配不到的,我们可以手动指定的方式来输出。

$ java -Xlog:gc+metaspace -version

[0.022s][info][gc,metaspace] CDS archive(s) mapped at: ... size 12443648.
[0.022s][info][gc,metaspace] Compressed class space mapped at: reserved size:...
[0.022s][info][gc,metaspace] Narrow klass base:..., Narrow 
klass shift: 0, Narrow klass range: 0x100000000

这里的 selector 也是可以进行组合的,不同的 selector 之间用逗号分隔即可。比如同时输出 gc 和 gc+metaspace 这两类 tag 的日志,就可以这么写:

$ java -Xlog:gc=debug,gc+metaspace -version

[0.020s][info][gc] Using G1
[0.020s][debug][gc] ConcGCThreads: 3 offset 22
[0.020s][debug][gc] ParallelGCThreads: 10
[0.020s][debug][gc] Initialize mark stack with 4096 chunks, maximum 524288
[0.022s][info ][gc,metaspace] CDS archive(s) mapped at:
[0.022s][info ][gc,metaspace] Compressed class space mapped at:
[0.022s][info ][gc,metaspace] Narrow klass base: 0x0000000800000000

当然这么搞是很麻烦的,JVM 提供了通配符 * 来解决精确匹配的问题,比如我们想要所有 tag 为 gc 的日志,可以这么写:

$ java -Xlog:gc*=debug -version

[0.024s][debug][gc,heap] Minimum heap 8388608
[0.024s][info ][gc     ] Using G1
[0.024s][debug][gc,heap,coops] Heap address: 0x0000000707400000
[0.024s][debug][gc           ] ConcGCThreads: 3 offset 22
[0.024s][debug][gc           ] ParallelGCThreads: 10
[0.024s][debug][gc           ] Initialize mark stack with 4096 chunks
[0.024s][debug][gc,ergo,heap ] Expand the heap. requested expansion amount:
[0.025s][debug][gc,heap,region] Activate regions [0, 125)[0.025s][debug][gc,ihop       ] Target occupancy update: old: 0B, new: 262144000B
[0.025s][debug][gc,ergo,refine] Initial Refinement Zones: green: 2560
[0.026s][debug][gc,task       ] G1 Service Thread 
[0.026s][debug][gc,task       ] G1 Service Thread (Periodic GC Task) (register)
[0.026s][info ][gc,init       ] Version: 17.0.3+7 (release)
...

如果只想要 INFO 级别的日志,则可以省略 level 的设置,使用 java -Xlog:gc* -version 即可。

如果想知道有哪些个性化的 tag 可以选择,可以用 java -Xlog:help 来找到所有可用的 tag。

阶段性小结

图片

第二部分:输出到哪里(output)

默认情况下,日志会输出到 stdout,jvm 支持以下三种输出方式:

  • stdout

  • stderr

  • file

一般而言我们会把日志输出到文件中,方便后续进一步分析

-Xlog:all=debug:file=/path_to_logs/app.log

还可以指定日志切割的大小和方式

-Xlog:gc*:file=/path_to_logs/app.log:filesize=104857600,filecount=5

第三部分:日志 decorators

每条日志除了正常的信息以外,还有不少日志相关的上下文信息,在 jvm 中被称为 decorators,有下面这些可选项。

图片

比如可以用 java -Xlog:all=debug:stdout:level,tags,time,uptime,pid -version 选项来打印日志。

[2022-06-15T19:54:01.529+0800][0.001s][5235][info ][os,thread] Thread attached
[2022-06-15T19:54:01.529+0800][0.001s][5235][debug][os,thread] Thread 5237 stack...
[2022-06-15T19:54:01.529+0800][0.001s][5235][debug][perf,datacreation] 

Unified Logging 小结

输出格式如下:

-Xlog:[selectors]:[output]:[decorators][:output-options]
  • selectors 是多个 tag 和 level 的组合,起到了 what(过滤器)的作用,格式为 tag1[+tag2...][*][=level][,...]

  • decorators 是日志相关的描述信息,也可以理解为上下文

  • output 是输出相关的选项,一般我们会配置为输出到文件,按文件大小切割

这里补充一个知识点,就是默认值:

  • tag:all

  • level:info

  • output:stdout

  • decorators: uptime, level, tags

GC 参数迁移

可以看到 GC 相关的参数都已经收拢到 Xlog 下,以前的很多 Java8 下的参数已经被移除或者标记为过期。

比如 PrintGCDetails 已经被 -Xlog:gc* 取代:

java -XX:+PrintGCDetails -version

[0.001s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.

常见的标记为废弃的参数还有 -XX:+PrintGC 和  -Xloggc:<filepath>,迁移前后的参数如下:

旧参数新参数
-XX:+PrintGCDetails-Xlog:gc*
-XX:+PrintGC-Xlog:gc
-Xloggc:<filepath>-Xlog:gc:file=<filepath>

除此之外,大量的 GC 的参数被移除,比如常用的参数  -XX:+PrintTenuringDistribution,Java17 会拒绝启动

java -XX:+PrintTenuringDistribution -version
Unrecognized VM option 'PrintTenuringDistribution'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

更详细的移除的参数如下

CMSDumpAtPromotionFailure,
CMSPrintEdenSurvivorChunks,
GlLogLevel,
G1PrintHeapRegions, 
G1PrintRegionLivenessInfo, 
G1SummarizeConcMark,
G1SummarizeRSetStats,
G1TraceConcRefinement,
G1TraceEagerReclaimHumongousObjects, 
G1TraceStringSymbolTableScrubbing,
GCLogFileSize, NumberofGCLogFiles, 
PrintAdaptiveSizePolicy,
PrintclassHistogramAfterFullGC,
PrintClassHistogramBeforeFullGC,
PrintCMSInitiationStatistics
PrintCMSStatistics, 
PrintFLSCensus,
PrintFLSStatistics,
PrintGCApplicationConcurrentTime
PrintGCApplicationStoppedTime,
PrintGCCause,
PrintGCDateStamps, 
PrintGCID,
PrintGCTaskTimeStamps,
PrintGCTimeStamps,
PrintHeapAtGC,
PrintHeapAtGCExtended, 
PrintJNIGCStalls,
PrintOldPLAB
PrintParallel0ldGCPhaseTimes, 
PrintPLAB,
PrintPromotionFailure,
PrintReferenceGC, 
PrintStringDeduplicationStatistics, 
PrintTaskqueue,
PrintTenuringDistribution, 
PrintTerminationStats,
PrintTLAB,
TraceDynamicGCThreads,
TraceMetadataHumongousAllocation,
UseGCLogFileRotation,
VerifySilently

这些移除的参数大部分都能在新的日志体系下找到对应的参数,比如 PrintHeapAtGC 这个参数可以用 -Xlog:gc+heap=debug 来替代

$ java -Xlog:gc+heap=debug -cp . G1GCDemo01

[0.004s][debug][gc,heap] Minimum heap 8388608  Initial heap 268435456  Maximum heap 
hello, g1gc!
[12.263s][debug][gc,heap] GC(0) Heap before GC invocations=0 (full 0):
[12.265s][debug][gc,heap] GC(0)  garbage-first heap 
[12.265s][debug][gc,heap] GC(0)   region size 2048K, 1 young (2048K)
[12.265s][debug][gc,heap] GC(0)  Metaspace       used 3678K
[12.265s][debug][gc,heap] GC(0)   class space    used 300K
[12.280s][debug][gc,heap] GC(0) Uncommittable regions after shrink: 124

虽然理解起来不太直观,不过要记住 -XX:+PrintGCApplicationStoppedTime 和 -XX+PrintGCApplicationConcurrentTime 这两个参数一起被 -Xlog:safepoint 取代。

还有一个常见的参数 -XX:+PrintAdaptiveSizePolicy 被 -Xlog:gc+ergo*=trace 取代,

[0.122s][debug][gc, ergo, refine] Initial Refinement Zones: green: 23, yellow:
69, red: 115, min yellow size: 46
[0.142s ][debug][gc, ergo, heap ] Expand the heap. requested expansion amount: 268435456B expansion amount: 268435456B
[2.475s][trace][gc, ergo, cset] GC(0) Start choosing CSet. pending cards: 0 predicted base time: 10.00ms remaining time:
190.00ms target pause time: 200.00ms
[2.476s][trace][gc, ergo, cset ] GC(9) Add young regions to CSet. eden: 24 regions, survivors: 0 regions, predicted young
region time: 367.19ms, target pause time: 200.00ms
[2.476s ][debug][gc, ergo, cset ] GC(0) Finish choosing CSet. old: 0 regions, predicted old region time: 0.00ms, time
remaining: 0.00
[2.826s][debug][gc, ergo] GC(0) Running G1 Clear Card Table Task using 1 workers for 1 units of work for 24 regions.
[2.827s][debug][gc, ergo] GC (0) Running G1 Free Collection Set using 1 workers for collection set length 24
[2.828s][trace][gc, ergo, refine] GC(0) Updating Refinement Zones: update rs time: 0.004ms, update rs buffers: 0, update rs
goal time: 19.999ms
[2.829s][debug][gc, ergo, refine] GC(0) Updated Refinement Zones: green: 23, yellow: 69, red: 115
[3.045s][trace][gc, ergo, set ] GC(1) Start choosing CSet. pending cards: 5898 predicted base time: 26.69ms remaining
time: 173.31ms target pause time: 200.00ms
[3.045s][trace][gc, ergo, cset ] GC(1) Add young regions to Set. eden: 9 regions, survivors: 3 regions, predicted young
region time: 457.38ms, target pause time: 200.00ms
[3.045s][debug](gc, ergo, set ] GC(1) Finish choosing CSet. old: @ regions, predicted old region time: 0.00ms, time
remaining: 0.00
[3.090s ][debug][gc, ergo
] GC (1) Running G1 Clear Card Table Task using 1 workers for 1 units of work for 12 regions.
[3.091s][debug][gc, ergo
GC (1) Running G1 Free Collection Set using 1 workers for collection set length 12
[3.093s][trace][gc, ergo, refine] GC(1) Updating Refinement Zones: update rs time: 2.510ms, update rs buffers: 25, update rs
goal time: 19.999ms
[3.093s ][debug][gc, ergo, refine] GC(1) Updated Refinement Zones: green: 25, yellow: 75, red: 125

看一下这部分的源码的变迁,就可以知道确实是如此了,在 Java8 中,PSYoungGen::resize_spaces代码如下:

图片

在 Java17 中,这部分日志打印被 gc+ergo 的标签日志取代:

图片

还有一个分代 GC 中非常有用的参数 -XX:+PrintTenuringDistribution,现在被 gc+age=trace 取代

完整的参数变迁对应表如下:

图片

图片

图片

图片

举例

-XX:+PrintGCDetails                           \  // gc*
-XX:+PrintGCApplicationStoppedTime            \  // safepoint
-XX:+PrintGCApplicationConcurrentTime         \  // safepoint 
-XX:+PrintGCCause                             \  // 默认会输出
-XX:+PrintGCID                                \  // 默认会输出
-XX:+PrintTenuringDistribution                \  // gc+age*=trace
-XX:+PrintGCDateStamps                        \  // :time,tags,level
-XX:+UseGCLogFileRotation                     \  // :filecount=5,filesize=10M 
-XX:NumberOfGCLogFiles=5                      \  // :filecount=5,filesize=10M 
-XX:GCLogFileSize=10M                         \  // :filecount=5,filesize=10M 
-Xloggc:/var/log/`date +%FT%H-%M-%S`-gc.log   \  // -Xlog::file=/var/log/%t-gc.log 

变迁后:

-Xlog:
  gc*, 
  safepoint, 
  gc+heap=debug, 
  gc+ergo*=trace, 
  gc+age*=trace, 
  :file=/var/log/%t-gc.log 
  :time,tags,level 
  :filecount=5,filesize=10M 

推荐的配置

-Xlog:
  // selections
    codecache+sweep*=trace,
    class+unload,                      // TraceClassUnloading
    class+load,                        // TraceClassLoading
    os+thread,
    safepoint,                        // TraceSafepoint
    gc*,                              // PrintGCDetails
    gc+stringdedup=debug,             // PrintStringDeduplicationStatistics
    gc+ergo*=trace,
    gc+age=trace,                     // PrintTenuringDistribution
    gc+phases=trace,
    gc+humongous=trace,
    jit+compilation=debug
// output
:file=/path_to_logs/app.log   
// decorators               
:level,tags,time,uptime,pid
// output-options                
:filesize=104857600,filecount=5

运行相关

反射+私有 API 调用之伤

在 Java8 中,没有人能阻止你访问特定的包,比如 sun.misc,对反射也没有限制,只要 setAccessible(true) 就可以了。Java9 模块化以后,一切都变了,只能通过 --add-exports 和 --add-opens 来打破模块封装

  • --add-opens 导出特定的包

  • --add-opens 允许模块中特定包的类路径深度反射访问

比如:

--add-opens java.base/java.lang=ALL-UNNAMED 
--add-opens java.base/java.io=ALL-UNNAMED 
--add-opens java.base/java.math=ALL-UNNAMED 
--add-opens java.base/java.net=ALL-UNNAMED 
--add-opens java.base/java.nio=ALL-UNNAMED 
--add-opens java.base/java.security=ALL-UNNAMED 
--add-opens java.base/java.text=ALL-UNNAMED 
--add-opens java.base/java.time=ALL-UNNAMED 
--add-opens java.base/java.util=ALL-UNNAMED 
--add-opens java.base/jdk.internal.access=ALL-UNNAMED 
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED

关于 GC 算法的选择

CMS 正式退出历史舞台,G1 正式接棒,ZGC 蓄势待发。在GC 算法的选择上,目前来看 G1 还是最佳的选择,ZGC 因为有内存占用被 OS 标记过高(三倍共享内存)虚高的问题,进程可能被 OOM-killer 杀掉。

ZGC 三倍 RES 内存

ZGC 底层用到了一个称之为染色指针的技术,使用三个视图(Marked0、Marked1 和 Remapped)来映射到同一块共享内存区域,原理如下:

##include <iostream>
##include <sys/mman.h>
##include <sys/stat.h>
##include <fcntl.h>
##include <unistd.h>
##include <cstdio>
##include <cstdlib>

int main() {
    // shm_open()函数用来打开或者创建一个共享内存区,两个进程可以通过给shm_open()函数传递相同的名字以达到操作同一共享内存的目的
    int fd = ::shm_open("/test", O_RDWR | O_CREAT | O_EXCL, 0600);
    if (fd < 0) {
        shm_unlink("/test");
        perror("shm open failed");
        return 0;
    }

    size_t size = 1 * 1024 * 1024 * 1024;
    // 创建一个共享内存后,默认大小为0,所以需要设置共享内存大小。ftruncate()函数可用来调整文件或者共享内存的大小
    ::ftruncate(fd, size);
    int prot = PROT_READ | PROT_WRITE;
    // 创建共享内存后,需要将共享内存映射到调用进程的地址空间,可通过mmap()函数来完成
    uint32_t *p1 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    uint32_t *p2 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    uint32_t *p3 = (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));
    ::close(fd);
    *p1 = 0xcafebabe;
    ::printf("Address of addr1: %p, value is 0x%x\n", p1, *p1);
    ::printf("Address of addr2: %p, value is 0x%x\n", p2, *p2);
    ::printf("Address of addr3: %p, value is 0x%x\n", p3, *p3);
    ::getchar();
    *p2 = 0xcafebaba;
    ::printf("Address of addr1: %p, value is 0x%x\n", p1, *p1);
    ::printf("Address of addr2: %p, value is 0x%x\n", p2, *p2);
    ::printf("Address of addr3: %p, value is 0x%x\n", p3, *p3);
    ::getchar();
    munmap(p1, size);
    munmap(p2, size);
    munmap(p3, size);
    shm_unlink("/test");
    std::cout << "hello" << std::endl;
}

你可以想象 p1、p2、p3 这三块内存区域就是 ZGC 中三种视图。

但是在 linux 统计中,虽然是共享内存,但是依然会统计三次,比如 RES。

同一个应用,使用 G1 RES 显示占用 2G,ZGC 则显示占用 6G

java -XX:+AlwaysPreTouch -Xms2G -Xmx2G -XX:+UseZGC MyTest
java -XX:+AlwaysPreTouch -Xms2G -Xmx2G -XX:+UseG1GC MyTest

图片

接下面我们讨论的都是 G1 相关的。

G1 参数调整

不要配置新生代的大小

这个在《JVM G1 源码分析和调优》一书里有详细的介绍,有两个主要的原因:

  • G1对内存的管理是不连续的,重新分配一个分区代价很低

  • G1 的需要根据目标停顿时间动态调整搜集的分区的个数,如果不能调整新生代的大小,那么 G1 可能不能满足停顿时间的要求

诸如 -Xmn, -XX:NewSize, -XX:MaxNewSize, -XX:SurvivorRatio 都不要在 G1 中出现,只需要控制最大、最小堆和目标暂停时间即可

调整 -XX:InitiatingHeapOccupancyPercent 到合适的值

IHOP 默认值为 45,这个值是启动并发标记的先决条件,只有当老年代内存栈总空间的 45% 之后才会启动并发标记任务。

增加这个值:导致并发标记可能花费更多的时间,同时导致 YGC 和 Mixed-GC 收集时的分区数变少,可以根据整体应用占用的平均内存来设置。


                
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这本书的内容是帮你全面了解java虚拟机,本书第1版两年内印刷近10次,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的改进:根据最新的JDK1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的修正;等等。 第2版不仅技术更新、内容更丰富,而且实战性更强。全书共分为五大部分,围绕内存管理、执行子系统、程序编译与优化、高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理。第一部分从宏观的角度介绍了整个Java技术体系、Java和JVM的发展历程、模块化,以及JDK的编译,这对理解本书后面内容有重要帮助。第二部分讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因;常见的垃圾收集算法以及垃圾收集器的特点和工作原理;常见虚拟机监控与故障处理工具的原理和使用方法。第三部分分析了虚拟机的执行子系统,包括类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎。第四部分讲解了程序的编译与代码的优化,阐述了泛型、自动装箱拆箱、条件编译等语法糖的原理;讲解了虚拟机的热点探测方法、HotSpot的即时编译器、编译触发条件,以及如何从虚拟机外部观察和分析JIT编译的数据和结果;第五部分探讨了Java实现高效并发的原理,包括JVM内存模型的结构和操作;原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。 前言 第一部分 走近Java 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 Java虚拟机发展史 1.4.1 Sun Classic Exact VM 1.4.2 Sun HotSpot VM 1.4.3 Sun Mobile-Embedded VM Meta-Circular VM 1.4.4 BEA JRockit IBM J9 VM 1.4.5 Azul VM BEA Liquid VM 1.4.6 Apache Harmony Google Android Dalvik VM 1.4.7 Microsoft JVM及其他 1.5 展望Java技术的未来 1.5.1 模块化 1.5.2 混合语言 1.5.3 多核并行 1.5.4 进一步丰富语法 1.5.5 64位虚拟机 1.6 实战:自己编译JDK 1.6.1 获取JDK源码 1.6.2 系统需求 1.6.3 构建编译环境 1.6.4 进行编译 1.6.5 在IDE工具中进行源码调试 1.7 本章小结 第二部分 自动内存管理机制 第2章 Java内存区域与内存溢出异常 2.1 概述 2.2 运行时数据区域 2.2.1 程序计数器 2.2.2 Java虚拟机栈 2.2.3 本地方法栈 2.2.4 Java堆 2.2.5 方法区 2.2.6 运行时常量池 2.2.7 直接内存 2.3 HotSpot虚拟机对象探秘 2.3.1 对象的创建 2.3.2 对象的内存布局 2.3.3 对象的访问定位 2.4 实战:OutOfMemoryError异常 2.4.1 Java堆溢出 2.4.2 虚拟机栈和本地方法栈溢出 2.4.3 方法区和运行时常量池溢出 2.4.4 本机直接内存溢出 2.5 本章小结 第3章 垃圾收集器与内存分配策略 3.1 概述 3.2 对象已死吗 3.2.1 引用计数算法 3.2.2 可达性分析算法 3.2.3 再谈引用 3.2.4 生存还是死亡 3.2.5 回收方法区 3.3 垃圾收集算法 3.3.1 标记-清除算法 3.3.2 复制算法 3.3.3 标记-整理算法 3.3.4 分代收集算法 3.4 HotSpot的算法实现 3.4.1 枚举根节点 3.4.2 安全点 3.4.3 安全区域 3.5 垃圾收集器 3.5.1 Serial收集器 3.5.2 ParNew收集器 3.5.3 Parallel Scavenge收集器 3.5.4 Serial Old收集器 3.5.5 Parallel Old收集器 3.5.6 CMS收集器 3.5.7 G1收集器 3.5.8 理解GC日志 3.5.9 垃圾收集器参数总结 3.6 内存分配与回收策略 3.6.1 对象优先在Eden分配 3.6.2 大对象直接进入老年代 3.6.3 长期存活的对象将进入老年代 3.6.4 动态对象年龄判定 3.6.5 空间分配担保 3.7 本章小结 第4章 虚拟机性能监控与故障处理工具 4.1 概述 4.2 JDK的命令行工具 4.2.1 jps:虚拟机进程状况工具 4.2.2 jstat:虚拟机统计信息监视工具 4.2.3 jinfo:Java配置信息工具 4.2.4 jmap:Java内存映像工具 4.2.5 jhat:虚拟机堆转储快照分析工具 4.2.6 jstack:Java堆栈跟踪工具 4.2.7 HSDIS:JIT生成代码反汇编 4.3 JDK的可视化工具 4.3.1 JConsole:Java监视与管理控制台 4.3.2 VisualVM:多合一故障处理工具 4.4 本章小结 第5章 调优案例分析与实战 5.1 概述 5.2 案例分析 5.2.1 高性能硬件上的程序部署策略 5.2.2 集群间同步导致的内存溢出 5.2.3 堆外内存导致的溢出错误 5.2.4 外部命令导致系统缓慢 5.2.5 服务器JVM进程崩溃 5.2.6 不恰当数据结构导致内存占用过大 5.2.7 由Windows虚拟内存导致的长时间停顿 5.3 实战:Eclipse运行速度调优 5.3.1 调优前的程序运行状态 5.3.2 升级JDK 1.6的性能变化及兼容问题 5.3.3 编译时间和类加载时间的优化 5.3.4 调整内存设置控制垃圾收集频率 5.3.5 选择收集器降低延迟 5.4 本章小结 第三部分 虚拟机执行子系统 第6章 类文件结构 6.1 概述 6.2 无关性的基石 6.3 Class类文件的结构 6.3.1 魔数与Class文件的版本 6.3.2 常量池 6.3.3 访问标志 6.3.4 类索引、父类索引与接口索引集合 6.3.5 字段表集合 6.3.6 方法表集合 6.3.7 属性表集合 6.4 字节码指令简介 6.4.1 字节码与数据类型 6.4.2 加载和存储指令 6.4.3 运算指令 6.4.4 类型转换指令 6.4.5 对象创建与访问指令 6.4.6 操作数栈管理指令 6.4.7 控制转移指令 6.4.8 方法调用和返回指令 6.4.9 异常处理指令 6.4.10 同步指令 6.5 公有设计和私有实现 6.6 Class文件结构的发展 6.7 本章小结 第7章 虚拟机类加载机制 7.1 概述 7.2 类加载的时机 7.3 类加载的过程 7.3.1 加载 7.3.2 验证 7.3.3 准备 7.3.4 解析 7.3.5 初始化 7.4 类加载器 7.4.1 类与类加载器 7.4.2 双亲委派模型 7.4.3 破坏双亲委派模型 7.5 本章小结 第8章 虚拟机字节码执行引擎 8.1 概述 8.2 运行时栈帧结构 8.2.1 局部变量表 8.2.2 操作数栈 8.2.3 动态连接 8.2.4 方法返回地址 8.2.5 附加信息 8.3 方法调用 8.3.1 解析 8.3.2 分派 8.3.3 动态类型语言支持 8.4 基于栈的字节码解释执行引擎 8.4.1 解释执行 8.4.2 基于栈的指令集与基于寄存器的指令集 8.4.3 基于栈的解释器执行过程 8.5 本章小结 第9章 类加载及执行子系统的案例与实战 9.1 概述 9.2 案例分析 9.2.1 Tomcat:正统的类加载器架构 9.2.2 OSGi:灵活的类加载器架构 9.2.3 字节码生成技术与动态代理的实现 9.2.4 Retrotranslator:跨越JDK版本 9.3 实战:自己动手实现远程执行功能 9.3.1 目标 9.3.2 思路 9.3.3 实现 9.3.4 验证 9.4 本章小结 第四部分 程序编译与代码优化 第10章 早期(编译期)优化 10.1 概述 10.2 Javac编译器 10.2.1 Javac的源码与调试 10.2.2 解析与填充符号表 10.2.3 注解处理器 10.2.4 语义分析与字节码生成 10.3 Java语法糖的味道 10.3.1 泛型与类型擦除 10.3.2 自动装箱、拆箱与遍历循环 10.3.3 条件编译 10.4 实战:插入式注解处理器 10.4.1 实战目标 10.4.2 代码实现 10.4.3 运行与测试 10.4.4 其他应用案例 10.5 本章小结 第11章 晚期(运行期)优化 11.1 概述 11.2 HotSpot虚拟机内的即时编译器 11.2.1 解释器与编译器 11.2.2 编译对象与触发条件 11.2.3 编译过程 11.2.4 查看及分析即时编译结果 11.3 编译优化技术 11.3.1 优化技术概览 11.3.2 公共子表达式消除 11.3.3 数组边界检查消除 11.3.4 方法内联 11.3.5 逃逸分析 11.4 Java与CC++的编译器对比 11.5 本章小结 第五部分 高效并发 第12章 Java内存模型与线程 12.1 概述 12.2 硬件的效率与一致性 12.3 Java内存模型 12.3.1 主内存与工作内存 12.3.2 内存间交互操作 12.3.3 对于volatile型变量的特殊规则 12.3.4 对于long和double型变量的特殊规则 12.3.5 原子性、可见性与有序性 12.3.6 先行发生原则 12.4 Java与线程 12.4.1 线程的实现 12.4.2 Java线程调度 12.4.3 状态转换 12.5 本章小结 第13章 线程安全与锁优化 13.1 概述 13.2 线程安全 13.2.1 Java语言中的线程安全 13.2.2 线程安全的实现方法 13.3 锁优化 13.3.1 自旋锁与自适应自旋 13.3.2 锁消除 13.3.3 锁粗化 13.3.4 轻量级锁 13.3.5 偏向锁 13.4 本章小结 附  录 附录A 编译Windows版的OpenJDK 附录B 虚拟机字节码指令表 附录C HotSpot虚拟机主要参数表 附录D 对象查询语言(OQL)简介 附录E JDK历史版本轨迹
资源介绍 本次分享的资源是一个基于Spring Boot开发的课程作业管理系统,这是一个完整且实用的毕业设计项目,旨在帮助教育机构或教师实现对学生作业的高效、便捷管理。该项目不仅满足了基本的作业管理需求,还具备高度的可扩展性和可定制性,为二次开发提供了广阔的空间。 该项目采用了Spring Boot框架,使得开发过程更加快速、简洁,同时保证了系统的稳定性和安全性。在功能设计上,系统涵盖了作业的发布、提交、批改、查看等功能,以及学生信息管理、教师角色管理等基础模块,实现了作业管理的全流程覆盖。此外,系统还提供了友好的用户界面,使得教师和学生能够轻松上手,快速适应。 值得一提的是,该项目还充分考虑了二次开发的需求。代码中包含了详细的注释和文档,方便开发者快速理解项目结构和逻辑。同时,系统采用了模块化的设计思路,各个功能模块之间耦合度低,易于进行功能扩展和定制。开发者可以根据实际需求,对系统进行个性化的改造和升级,以满足不同场景下的作业管理需求。 总的来说,这个基于Spring Boot开发的课程作业管理系统是一个功能完善、易于扩展的毕业设计项目。它不仅能够为教育机构或教师提供高效的作业管理工具,还能够作为二次开发的起点,为开发者提供广阔的发挥空间。无论是作为毕业设计的参考项目,还是作为实际应用的解决方案,该项目都具有很高的价值和实用性。
毕业设计开题报告 计算机科学与技术 基于JAVA的俄罗斯方块游戏设计与实现 1. 综述本课题国内外研究动态,说明选题的依据和意义 本课题国内外动态: 学校现已开设的课程有C语言、VB、C++,自己本身自学了Java,就目前了解,可用J ave,VB和C++编写俄罗斯方块程序。 (1)VB的优点 VB是完全中文化的环境使用,语句生成器和快速提示帮助使用户不必记忆成千上万的 属性和方法,在较短的时间内就能开发出功能强大的应用程序。Internet应用程序的开 发功能更加强大和容易,支持动态HTML技术的应用程序。应用程序安装向导能帮助用户 自动生成具有一定功能的应用程序,加快了程序的开发速度。 (2)C++的优点 C++是对C语言的扩充,扩充的绝大部分来自著名语言中的最佳特性:从SIMULA 67中吸取了类,从ALGOL 68中吸取了运算符一名多用、引用和在分程序中任何地方说明变量,综合了Ada的类属和 Clu的模块特点,从BCPL中吸取异常处理,从BCPL中吸取了用//表示注释。 (3)Java的优点 Java是定义位于网络计算的计算机语言,它几乎所有的特点也是围绕着这一中心展开 的并为之服务的,这些特点使得Java语言特别适全于用来开发网络上的应用程序;另外 ,作为一种面世较晚的语言,Java也集中体现和充分利用了当代软件技术新成果,如面 向对象、多线程等,这些也都在它的特点中有所反映。 1.开台无关性 如前所述,Java语言独特的运行机制使得它具有良好的二进制级的可移植性,利用J ava,开发人员可以编写出与具体平台无关、普遍适用的应用程序,大大降低了开发、维 护和管理的开销。 2.面向对象 Java是面向对象的编程语言。面向对象技术较好地适应了当今软件开发过程中新出现 的种种传统面向过程语言所不能处理的问题,包括软件开发的规模扩大、升级加快、维 护量增大经及开发分工日趋细化、专业化和标准化等,是一种迅速成熟、推广的软件开 发方法。面向对象技术的核心是以更接近人类思维的方式建立计算机逻辑模型,它利用 类和对象的机制将数据与其上的操作封装在一起,并通过统一的接口与外界交互,使反 映现实世界实体的各个类在程序中能够独立、自治、继承;这种方法非常有利于提高程 序的可维护性和可重用性,大大提高了开发效率和程序的可管理性,使得面向过程语言 难于操纵的大规模软件可以很方便的创建、使用和维护。 3.安全稳定 对网络上应用程序的另一个需求是较高的安全可靠性。用户通过网络获取并在本地运 行的应用程序必须是可依赖的,不会充当病毒或其他恶意操作的传播者而攻击用户本地 的资源;同时它还应该是稳定的,轻易不会产生死机等错误,使得用户乐于使用。 4.支持多线程 多线程是当今软件技术的又一重要成果,已成功应用在操作系统、应用开发等多个领 域。多程序技术允许同一个程序有两个执行线索,即同时做两件事情,满足了一些复杂 软件的需求。Java不但内置多线程功能,而且提供语言级的多线程支持,即定义了一些 用于建立、管理多线程的类和方法,使得开发具有多线程功能的程序变得简单、容易和 有效。 5.简单易学 如前所述,衍生自C++的Java语言,出于安全稳定性的考虑,去除了C++中不容不得易 理解和掌握的部分,如最典型的指针操作等,降低了学习的难度;同时 Java还有一个特 点就是它的基本语法部分与C语言几乎一模一样。这样,无论是学过Java再学C,还是已 经掌握了C语言再业学Java,都会感到易于入门。 选题的依据和意义: 俄罗斯方块是一款风靡全球的电视游戏机和掌上游戏机游戏,它曾经造成的轰动与造 成的经济价值可以说是游戏史上的一件大事。这款游戏最初是由苏联的游戏制作人 Alex Pajitnov 制作的,它看似简单但却变化无穷,令人上瘾。相信大多数用户都还记得为它痴迷得茶 不思饭不想的那个俄罗斯方块时代。究其历史,俄罗斯方块最早还是出现在 PC 机上,而中国的用户都是通过红白机了解、喜欢上它的。现在远航游戏中心又将重新掀 起这股让人沉迷的俄罗斯方块风潮。对一般用户来说,它的规则简单,容易上手,且游 戏过程变化无穷,而在 "远航游戏中心俄罗斯方块"中,更有一些远航游戏中心网络游戏所独有的魅力――有单机 作战与两人在线对战两种模式,用户可任选一种进行游戏。网络模式还增加了积分制, 使用户既能感受到游戏中的乐趣,也给用户提供了一个展现自己高超技艺的场所。有研 究者发现玩俄罗斯方块游戏有助于防止创伤后应激障碍的发生,可能是这个游戏能够对 大脑储存视觉记忆的功能产生干扰,从而保护病人免受创伤后应激反应的影响 。在了解自己对各个语言编程能力了解后,经过再三比较了三种语言后,决定采用Java 语言编写俄罗斯方块。 二、研究的基本内容,拟解决的主要问题: 研究的基本内容: 1.学习
1 开题报告 计算机科学与技术 基于 JAVA 的俄罗斯方块游戏设计与实现 一、 综述本课题国内外研究动态,说明选题的依据和意义 本课题国内外动态: 学校现已开设的课程有 C 语言、VB、C++,自己本身自学了 Java,就目前了解,可用 Jave,VB 和 C++编写俄罗斯方块程序。 (1)VB 的优点 VB 是完全中文化的环境使用,语句生成器和快速提示帮助使用户不必记忆成千上万的属性和 方法,在较短的时间内就能开发出功能强大的应用程序。Internet 应用程序的开发功能更加强大 和容易,支持动态 HTML 技术的应用程序。应用程序安装向导能帮助用户自动生成具有一定功能的 应用程序,加快了程序的开发速度。 (2)C++的优点 C++是对 C 语言的扩充,扩充的绝大部分来自著名语言中的最佳特性:从 SIMULA 67 中吸取了 类,从 ALGOL 68 中吸取了运算符一名多用、引用和在分程序中任何地方说明变量,综合了 Ada 的 类属和 Clu 的模块特点,从 BCPL 中吸取异常处理,从 BCPL 中吸取了用//表示注释。 (3)Java 的优点 Java 是定义位于网络计算的计算机语言,它几乎所有的特点也是围绕着这一中心展开的并为 之服务的,这些特点使得 Java 语言特别适全于用来开发网络上的应用程序;另外,作为一种面世 较晚的语言,Java 也集中体现和充分利用了当代软件技术新成果,如面向对象、多线程等,这些 也都在它的特点中有所反映。 1.开台无关性 如前所述,Java 语言独特的运行机制使得它具有良好的二进制级的可移植性,利用 Java,开 发人员可以编写出与具体平台无关、普遍适用的应用程序,大大降低了开发、维护和管理的开销。 2.面向对象 Java 是面向对象的编程语言。面向对象技术较好地适应了当今软件开发过程中新出现的种种 传统面向过程语言所不能处理的问题,包括软件开发的规模扩大、升级加快、维护量增大经及开发 分工日趋细化、专业化和标准化等,是一种迅速成熟、推广的软件开发方法。面向对象技术的核心 2 是以更接近人类思维的方式建立计算机逻辑模型,它利用类和对象的机制将数据与其上的操作封装 在一起,并通过统一的接口与外界交互,使反映现实世界实体的各个类在程序中能够独立、自治、 继承;这种方法非常有利于提高程序的可维护性和可重用性,大大提高了开发效率和程序的可管理 性,使得面向过程语言难于操纵的大规模软件可以很方便的创建、使用和维护。 3.安全稳定 对网络上应用程序的另一个需求是较高的安全可靠性。用户通过网络获取并在本地运行的应用 程序必须是可依赖的,不会充当病毒或其他恶意操作的传播者而攻击用户本地的资源;同时它还应 该是稳定的,轻易不会产生死机等错误,使得用户乐于使用。 4.支持多线程 多线程是当今软件技术的又一重要成果,已成功应用在操作系统、应用开发等多个领域。多程 序技术允许同一个程序有两个执行线索,即同时做两件事情,满足了一些复杂软件的需求。Java 不但内置多线程功能,而且提供语言级的多线程支持,即定义了一些用于建立、管理多线程的类和 方法,使得开发具有多线程功能的程序变得简单、容易和有效。 5.简单易学 如前所述,衍生自 C++的 Java 语言,出于安全稳定性的考虑,去除了 C++中不容不得易理解和 掌握的部分,如最典型的指针操作等,降低了学习的难度;同时 Java 还有一个特点就是它的基本 语法部分与 C 语言几乎一模一样。这样,无论是学过 Java 再学 C,还是已经掌握了 C 语言再业学 Java,都会感到易于入门。 选题的依据和意义: 俄罗斯方块是一款风靡全球的电视游戏机和掌上游戏机游戏,它曾经造成的轰动与造成的经济 价值可以说是游戏史上的一件大事。这款游戏最初是由苏联的游戏制作人 Alex Pajitnov 制作的, 它看似简单但却变化无穷,令人上瘾。相信大多数用户都还记得为它痴迷得茶不思饭不想的那个俄 罗斯方块时代。究其历史,俄罗斯方块最早还是出现在 PC 机上,而中国的用户都是通过红白机了 解、喜欢上它的。现在远航游戏中心又将重新掀起这股让人沉迷的俄罗斯方块风潮。对一般用户来 说,它的规则简单,容易上手,且游戏过程变化无穷,而在 "远航游戏中心俄罗斯方块"中,更 有一些远航游戏中心网络游戏所独有的魅力――有单机作战与两人在线对战两种模式,用户可任选 一种进行游戏。网络模式还增加了积分制,使用户既能感受到游戏中的乐趣,也给用户提供了一个 展现自己高超技艺的场所。有研究者发现玩俄罗斯方块游戏有助于防止创伤后应激障碍的发生,可 能是这个游戏能够对大脑储存视觉记忆的功能产生干扰,从而保护病人免受创伤后应激反应的影 3 响 。在了解自己对各个语言编程能力了解后,经过再三比较了三种语言后,决定采用
WebSocket协议是一种在单个TCP连接上进行全双工通信的协议。在WebSocket协议之前,浏览器与服务器之间的通信通常都是基于HTTP请求和响应的,也就是说,客户端向服务器发送请求,服务器返回响应,然后连接就被断开了。这种方式不够实时、效率也不高。 WebSocket协议通过在HTTP协议升级时使用"Upgrade"头来实现握手。握手成功后,连接就从HTTP协议升级到了WebSocket协议,从而在单个TCP连接上实现了全双工通信。 在Java中,可以使用Java API for WebSocket(JSR 356)来实现WebSocket协议。下面是基于Java实现WebSocket协议的步骤: 1. 创建自己的Endpoint类,继承javax.websocket.Endpoint类,实现onOpen、onMessage、onError和onClose方法。 2. 在onOpen方法中,创建Session对象,将它加入到某个集合中,以便在之后向客户端发送消息时使用。 3. 在onMessage方法中,处理客户端发送的消息,并向所有客户端发送消息。 4. 在onError方法中,处理异常。 5. 在onClose方法中,将Session从集合中移除。 6. 创建ServerEndpointConfig对象,并将Endpoint类和URI绑定在一起。 7. 创建WebSocket服务器,将ServerEndpointConfig对象传递给它。 8. 启动WebSocket服务器,等待客户端连接。 9. 客户端连接成功后,服务器会自动调用Endpoint的onOpen方法,表示连接已建立。 10. 客户端发送消息时,服务器会自动调用Endpoint的onMessage方法,处理消息。 11. 客户端关闭连接时,服务器会自动调用Endpoint的onClose方法,清除Session对象。 12. 服务器发生异常时,会自动调用Endpoint的onError方法,处理异常。 以上是基于Java实现WebSocket协议的大致步骤。需要注意的是,WebSocket协议中的数据是以帧的形式进行传输的,而不是HTTP协议中的请求和响应。因此,在编写WebSocket应用程序时,需要特别注意帧的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值