热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从Java8升级到Java17踩坑全过程,建议收藏!

更多内容关注微信公众号:fullstack888最近在做Java8到Java17的迁移工作,前期做了一些准备,但是在升级过程还是有些问题&
更多内容关注微信公众号:fullstack888

最近在做 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 @PreDestroypublic void destroy() {agentClient.close();}
}

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

javax.annotationjavax.annotation-api1.3.2

使用了 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.PlatformDependent0at 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 对不上真的很恶心。

org.projectlomboklombok1.18.24

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 来做项目的依赖分析

c5c57964639e8f5d0584306e24a2aafe.jpeg
图片

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

*参数迁移*

什么是 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

输出结果如下:

0852685856b6197a01b0a767ba636695.jpeg
图片

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

230ba56f34ff1543ce6ddee5c92fa8c2.jpeg
图片

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

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

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

  • decorators:日志如何

输出什么信息(selectors)

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

JVM 采用的是 =的形式来表示 selectors,默认情况下,tag 为all,表示所有的 tag,level 为 INFO,java -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。

阶段性小结

07a8a96808c0afdea4c51df7aa9df5a1.jpeg
图片

第二部分:输出到哪里(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,有下面这些可选项。

7b78911ad26b9fff92c2aaa2c7224969.png
图片

比如可以用 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:,迁移前后的参数如下:

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

除此之外,大量的 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代码如下:

f8880583f48b0c1e8e067332cfef9aa8.jpeg
图片

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

2007bd7ffebfe67dfe8ff3d4d5342d38.jpeg
图片

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

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

7d027e44abc23d2b0352747cb341a915.png
图片
f15647f4d8768b02f4db64114caf8181.png
图片
5402df10a4cefc1142762edcb63a4012.png
图片
547d0f357c006e2aee1e4c649a19e46a.png
图片
举例

-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:// selectionscodecache+sweep*=trace,class+unload,                      // TraceClassUnloadingclass+load,                        // TraceClassLoadingos+thread,safepoint,                        // TraceSafepointgc*,                              // PrintGCDetailsgc+stringdedup=debug,             // PrintStringDeduplicationStatisticsgc+ergo*=trace,gc+age=trace,                     // PrintTenuringDistributiongc+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 
##include 
##include 
##include 
##include 
##include 
##include int main() {// shm_open()函数用来打开或者创建一个共享内存区&#xff0c;两个进程可以通过给shm_open()函数传递相同的名字以达到操作同一共享内存的目的int fd &#61; ::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 &#61; 1 * 1024 * 1024 * 1024;// 创建一个共享内存后&#xff0c;默认大小为0&#xff0c;所以需要设置共享内存大小。ftruncate()函数可用来调整文件或者共享内存的大小::ftruncate(fd, size);int prot &#61; PROT_READ | PROT_WRITE;// 创建共享内存后&#xff0c;需要将共享内存映射到调用进程的地址空间&#xff0c;可通过mmap()函数来完成uint32_t *p1 &#61; (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));uint32_t *p2 &#61; (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));uint32_t *p3 &#61; (uint32_t *) (mmap(nullptr, size, prot, MAP_SHARED, fd, 0));::close(fd);*p1 &#61; 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 &#61; 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 统计中&#xff0c;虽然是共享内存&#xff0c;但是依然会统计三次&#xff0c;比如 RES。

同一个应用&#xff0c;使用 G1 RES 显示占用 2G&#xff0c;ZGC 则显示占用 6G

java -XX:&#43;AlwaysPreTouch -Xms2G -Xmx2G -XX:&#43;UseZGC MyTest
java -XX:&#43;AlwaysPreTouch -Xms2G -Xmx2G -XX:&#43;UseG1GC MyTest
4abd632d8e7c061aa8cdffccf439c095.jpeg

图片

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

G1 参数调整

不要配置新生代的大小

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

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

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

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

调整 -XX:InitiatingHeapOccupancyPercent 到合适的值

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

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

- END -

往期回顾

◆分库分表后路由策略设计

◆中国移动磐⾈DevOps 平台实践

◆双活数据中心建设要点

◆中国工商银行基于 eBPF 技术的云原生可观测图谱探索与实践

◆轻松搞定亿级用户中心系统架构

◆面试官&#xff1a;你工作中做过 JVM 调优吗&#xff1f;怎么做的&#xff1f;

9853cd07c2067a6a7a431e8bab8ce242.png

技术交流&#xff0c;请加微信&#xff1a; jiagou6688 &#xff0c;备注&#xff1a;Java&#xff0c;拉你进架构群


推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 本文介绍了Sencha Touch的学习使用心得,主要包括搭建项目框架的过程。作者强调了使用MVC模式的重要性,并提供了一个干净的引用示例。文章还介绍了Index.html页面的作用,以及如何通过链接样式表来改变全局风格。 ... [详细]
  • node.jsrequire和ES6导入导出的区别原 ... [详细]
author-avatar
伪祢添睬_362
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有