转:http://hllvm.group.iteye.com/group/topic/27945
(截取部分)
1、-XX:+DisableExplicitGC 与 NIO的direct memory
很多人都见过JVM调优建议里使用这个参数,对吧?但是为什么要用它,什么时候应该用而什么时候用了会掉坑里呢?
首先要了解的是这个参数的作用。在Oracle/Sun JDK这个具体实现上,System.gc()的默认效果是引发一次stop-the-world的full GC,对整个GC堆做收集。有几个参数可以改变默认行为,之前发过一帖简单描述过,这里就不重复了。关键点是,用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC(但是“函数调用”本身的开销还是存在的哦~)。
为啥要用这个参数呢?最主要的原因是为了防止某些手贱的同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧。有些应用程序 本来可能正常跑一天也不会出一次full GC,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停。也有些时候这些调用是在某些库或框架里写的,改不了它们的代码但又不想 被这些调用干扰也会用这参数。
OK。看起来这参数应该总是开着嘛。有啥坑呢?
其中一种情况是下述三个条件同时满足时会发生的:
1、应用本身在GC堆内的对象行为良好,正常情况下很久都不发生full GC;
2、应用大量使用了NIO的direct memory,经常、反复的申请DirectByteBuffer
3、使用了-XX:+DisableExplicitGC
能观察到的现象是:
- java.lang.OutOfMemoryError: Direct buffer memory
- at java.nio.Bits.reserveMemory(Bits.java:633)
- at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
- at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
- ...
做个简单的例子来演示这现象:
- import java.nio.*;
- public class DisableExplicitGCDemo {
- public static void main(String[] args) {
- for (int i = 0; i < 100000; i++) {
- ByteBuffer.allocateDirect(128);
- }
- System.out.println("Done");
- }
- }
然后编译、运行之:
- $ java -version
- java version "1.6.0_25"
- Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
- Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
- $ javac DisableExplicitGCDemo.java
- $ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
- Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
- at java.nio.Bits.reserveMemory(Bits.java:633)
- at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
- at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
- at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)
- $ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo
- [GC 10996K->10480K(120704K), 0.0433980 secs]
- [Full GC 10480K->10415K(120704K), 0.0359420 secs]
- Done
可以看到,同样的程序,不带-XX:+DisableExplicitGC时能正常完成运行,而带上这个参数后却出现了OOM。
例子里用-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空间的限额,以便问题更容易展现出来。不用这个参数就得多跑一会儿了。
在这个例子里,main()里的循环不断申请DirectByteBuffer但并没有引用、使用它们,所以这些DirectByteBuffer应该刚创建出来就已经满足被GC的条件,等下次GC运行的时候就应该可以被回收。
实际上却没这么简单。DirectByteBuffer是种典型的“冰山”对象,也就是说它的Java对象虽然很小很无辜,但它背后却会关联着一 定量的native memory资源,而这些资源并不在GC的控制之下,需要自己注意控制好。对JVM如何使用native memory不熟悉的同学可以参考去年JavaOne上IBM的一个演讲,“Where Does All the Native Memory Go”。
Oracle/Sun JDK的实现里,DirectByteBuffer有几处值得注意的地方。
1、DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的。
2、sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些。对PhantomReference不熟悉的同学请参考Bob Lee最近几年在JavaOne上做的演讲,"The Ghost in the Virtual Machine: A Reference to References"。今年的JavaOne上他也讲了同一个主题,内容比前几年的稍微更新了些。PPT可以从链接里的页面下载到。
- /**
- * General-purpose phantom-reference-based cleaners.
- *
- * <p> Cleaners are a lightweight and more robust alternative to finalization.
- * They are lightweight because they are not created by the VM and thus do not
- * require a JNI upcall to be created, and because their cleanup code is
- * invoked directly by the reference-handler thread rather than by the
- * finalizer thread. They are more robust because they use phantom references,
- * the weakest type of reference object, thereby avoiding the nasty ordering
- * problems inherent to finalization.
- *
- * <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary
- * cleanup code. Some time after the GC detects that a cleaner's referent has
- * become phantom-reachable, the reference-handler thread will run the cleaner.
- * Cleaners may also be invoked directly; they are thread safe and ensure that
- * they run their thunks at most once.
- *
- * <p> Cleaners are not a replacement for finalization. They should be used
- * only when the cleanup code is extremely simple and straightforward.
- * Nontrivial cleaners are inadvisable since they risk blocking the
- * reference-handler thread and delaying further cleanup and finalization.
- *
- *
- * @author Mark Reinhold
- * @version %I%, %E%
- */
重点是这两句:"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner."
Oracle/Sun JDK 6中的HotSpot VM只会在old gen GC(full GC/major GC或者concurrent GC都算)的时候才会对old gen中的对象做reference processing,而在young GC/minor GC时只会对young gen里的对象做reference processing。
(死在young gen中的DirectByteBuffer对象会在young GC时被处理的例子,请参考这里:https://gist.github.com/1614952)
也就是说,做full GC的话会对old gen做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。而如果很长一段时间里没做过GC或者只做了 young GC的话则不会在old gen触发Cleaner的工作,那么就可能让本来已经死了的、但已经晋升到old gen的DirectByteBuffer关联的native memory得不到及时释放。
3、为DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory:
- // These methods should be called whenever direct memory is allocated or
- // freed. They allow the user to control the amount of direct memory
- // which a process may access. All sizes are specified in bytes.
- static void reserveMemory(long size) {
- synchronized (Bits.class) {
- if (!memoryLimitSet && VM.isBooted()) {
- maxMemory = VM.maxDirectMemory();
- memoryLimitSet = true;
- }
- if (size <= maxMemory - reservedMemory) {
- reservedMemory += size;
- return;
- }
- }
- System.gc();
- try {
- Thread.sleep(100);
- } catch (InterruptedException x) {
- // Restore interrupt status
- Thread.currentThread().interrupt();
- }
- synchronized (Bits.class) {
- if (reservedMemory + size > maxMemory)
- throw new OutOfMemoryError("Direct buffer memory");
- reservedMemory += size;
- }
- }
这几个实现特征使得Oracle/Sun JDK 6依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。如果打开了 -XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM,也就是上面的例子演示的情况。我们这边在实际生产环境中确实遇到过这样的问题。
教训是:如果你在使用Oracle/Sun JDK 6,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。如果担心System.gc()调用造成full GC频繁,可以尝试下面提到 -XX:+ExplicitGCInvokesConcurrent 参数
======================================================================
2、-XX:+DisableExplicitGC 与 Remote Method Invocation (RMI) 与 -Dsun.rmi.dgc.{server|client}.gcInterval=
看了上一个例子有没有觉得-XX:+DisableExplicitGC参数用起来很危险?那干脆完全不要用这个参数吧。又有什么坑呢?
前段时间有个应用的开发来抱怨,说某次升级JDK之前那应用的GC状况都很好,很长时间都不会发生full GC,但升级后发现每一小时左右就会发生一次。经过对比发现,升级的同时也吧启动参数改了,把原本有的-XX:+DisableExplicitGC给去掉了。
观察到的日志有明显特征。一位同事表示:
who call system.gc :
sun.misc.GC$Daemon.run(GC.java:92)
预发机没什么流量,也会每一小时一次Full GC
频率正好是一小时一次
- 2011-09-23T10:49:38.071+0800: 327692.227: [Full GC (System) 327692.227: [CMS: 75793K->75759K(2097152K), 0.6923690 secs] 430298K->75759K(3984640K), [CMS Perm : 104136K->104124K(173932K)], 0.6925570 secs]
实际上这里在做的是分布式GC。Sun JDK的分布式GC是用纯Java实现的,为RMI服务。
RMI DGC相关参数的介绍文档:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html
(后面回头再补…先睡觉去了)
资料:
jGuru: Distributed Garbage Collection
http://mail.openjdk.java.net/pipermail/hotspot-dev/2011-December/004909.html
each node to recognize that some objects are unreachable so it can
notify a remote node (or nodes) that some remote references to them do
not exist any more. The remote node might then be able to reclaim
objects that are only remotely reachable. (Or this is how I understood
it at least.)
RMI used to call System.gc() once a minute (!!!) but after some
encouragement from yours truly they changed the default to once an hour
(this is configurable using a property). Note that a STW Full GC is not
really required as long as references are processed. So, in CMS (and
G1), a concurrent cycle is fine which is why we recommend to use
-XX:+ExplicitGCInvokesConcurrent in this case.
I had been warned by the RMI folks against totally disabling those
System.gc()'s (e.g., using -XX:+DisableExplicitGC) given that if Full
GCs / concurrent cycles do not otherwise happen at a reasonable
frequency then remote nodes might experience memory leaks since they
will consider that some otherwise unreachable remote references are
still live. I have no idea how severe such memory leaks would be. I
guess they'd be very application-dependent.
An additional thought that just occurred to me: instead of calling
System.gc() every hour what RMI should really be doing is calling
System.gc() every hour provided no old gen GC has taken place during the
last hour. This would be relatively easy to implement by accessing the
old GC counter through the GC MXBeans.
Tony
再加俩链接:http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-January/004929.html
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-January/004946.html
《Java Performance》的303和411页正好也提到了-XX:+DisableExplicitGC与RMI之间的干扰的事情,有兴趣可以读一下,虽然只有一小段。
======================================================================
3、-XX:+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
- product(bool, ExplicitGCInvokesConcurrent, false, \
- "A System.gc() request invokes a concurrent collection;" \
- " (effective only when UseConcMarkSweepGC)") \
- \
- product(bool, ExplicitGCInvokesConcurrentAndUnloadsClasses, false, \
- "A System.gc() request invokes a concurrent collection and " \
- "also unloads classes during such a concurrent gc cycle " \
- "(effective only when UseConcMarkSweepGC)") \
跟上面的第一个例子的-XX:+DisableExplicitGC一样,这两个参数也是用来改变System.gc()的默认行为用的;不同的 是这两个参数只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()还是会触发GC的,只不过不是触发一个 完全stop-the-world的full GC,而是一次并发GC周期。
CMS GC周期中也会做reference processing。所以如果用这两个参数的其中一个,而不是用-XX:+DisableExplicitGC的话,就避开了由full GC带来的长GC pause,同时NIO direct memory的OOM也不会那么容易发生。
做了个跟第一个例子类似的例子,在这里:https://gist.github.com/1344251
《Java Performance》的303页有讲到这俩参数。
相关bug:6919638 CMS: ExplicitGCInvokesConcurrent misinteracts with gc locker
<< JDK6u23修复了这个问题
======================================================================
4、-XX:+GCLockerInvokesConcurrent
- product(bool, GCLockerInvokesConcurrent, false, \
- "The exit of a JNI CS necessitating a scavenge also" \
- " kicks off a bkgrd concurrent collection") \
(内容回头补…)
======================================================================
5、MaxDirectMemorySize 与 NIO direct memory 的默认上限
-XX:MaxDirectMemorySize 是用来配置NIO direct memory上限用的VM参数。
- product(intx, MaxDirectMemorySize, -1, \
- "Maximum total size of NIO direct-buffer allocations") \
但如果不配置它的话,direct memory默认最多能申请多少内存呢?这个参数默认值是-1,显然不是一个“有效值”。所以真正的默认值肯定是从别的地方来的。
在Sun JDK 6和OpenJDK 6里,有这样一段代码,sun.misc.VM:
- // A user-settable upper limit on the maximum amount of allocatable direct
- // buffer memory. This value may be changed during VM initialization if
- // "java" is launched with "-XX:MaxDirectMemorySize=<size>".
- //
- // The initial value of this field is arbitrary; during JRE initialization
- // it will be reset to the value specified on the command line, if any,
- // otherwise to Runtime.getRuntime().maxMemory().
- //
- private static long directMemory = 64 * 1024 * 1024;
- // If this method is invoked during VM initialization, it initializes the
- // maximum amount of allocatable direct buffer memory (in bytes) from the
- // system property sun.nio.MaxDirectMemorySize. The system property will
- // be removed when it is accessed.
- //
- // If this method is invoked after the VM is booted, it returns the
- // maximum amount of allocatable direct buffer memory.
- //
- public static long maxDirectMemory() {
- if (booted)
- return directMemory;
- Properties p = System.getProperties();
- String s = (String)p.remove("sun.nio.MaxDirectMemorySize");
- System.setProperties(p);
- if (s != null) {
- if (s.equals("-1")) {
- // -XX:MaxDirectMemorySize not given, take default
- directMemory = Runtime.getRuntime().maxMemory();
- } else {
- long l = Long.parseLong(s);
- if (l > -1)
- directMemory = l;
- }
- }
- return directMemory;
- }
(代码里原本的注释有个写错的地方,上面有修正)
当MaxDirectMemorySize参数没被显式设置时它的值就是-1,在Java类库初始化时maxDirectMemory()被java.lang.System的静态构造器调用,走的路径就是这条:
- if (s.equals("-1")) {
- // -XX:MaxDirectMemorySize not given, take default
- directMemory = Runtime.getRuntime().maxMemory();
- }
而Runtime.maxMemory()在HotSpot VM里的实现是:
- JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))
- JVMWrapper("JVM_MaxMemory");
- size_t n = Universe::heap()->max_capacity();
- return convert_size_t_to_jlong(n);
- JVM_END
这个max_capacity()实际返回的是 -Xmx减去一个survivor space的预留大小(G1除外)。
结论:MaxDirectMemorySize没显式配置的时候,NIO direct memory可申请的空间的上限就是-Xmx减去一个survivor space的预留大小。
大家感兴趣的话可以试试在不同的-Xmx的条件下不设置MaxDirectMemorySize,并且调用一下sun.misc.VM.maxDirectMemory()看得到的值的相关性。
该行为在JDK7里没变,虽然具体实现的代码有些变化。请参考http://hg.openjdk.java.net/jdk7/jdk7/jdk/rev/b444f86c4abe
======================================================================
6、-verbose:gc 与 -XX:+PrintGCDetails
经常能看到在推荐的标准参数里这两个参数一起出现。实际上它们有啥关系?
在Oracle/Sun JDK 6里,"java"这个启动程序遇到"-verbosegc"会将其转换为"-verbose:gc",将启动参数传给HotSpot VM后,HotSpot VM遇到"-verbose:gc"则会当作"-XX:+PrintGC"来处理。
也就是说 -verbosegc、-verbose:gc、-XX:+PrintGC 三者的作用是完全一样的。
而当HotSpot VM遇到 -XX:+PrintGCDetails 参数时,会顺带把 -XX:+PrintGC 给设置上。
也就是说 -XX:+PrintGCDetails 包含 -XX:+PrintGC,进而也就包含 -verbose:gc。
既然 -verbose:gc 都被包含了,何必在命令行参数里显式设置它呢?
======================================================================
7、-XX:+UseFastEmptyMethods 与 -XX:+UseFastAccessorMethods
虽然不常见,但偶尔也会见到推荐的标准参数上有这俩的身影。
empty method顾名思义就是空方法,也就是方法体只包含一条return指令、返回值类型为void的Java方法。
accessor method在这里则有很具体的定义:
- bool methodOopDesc::is_accessor() const {
- if (code_size() != 5) return false;
- if (size_of_parameters() != 1) return false;
- if (java_code_at(0) != Bytecodes::_aload_0 ) return false;
- if (java_code_at(1) != Bytecodes::_getfield) return false;
- if (java_code_at(4) != Bytecodes::_areturn &&
- java_code_at(4) != Bytecodes::_ireturn ) return false;
- return true;
- }
如果从Java源码的角度来理解,accessor method就是形如这样的:
- public class Foo {
- private int value;
- public int getValue() {
- return this.value;
- }
- }
关键点是:
1、必须是成员方法;静态方法不行
2、返回值类型必须是引用类型或者int,其它都不算
3、方法体的代码必须满足aload_0; getfield #index; areturn或ireturn这样的模式。
留意:方法名是什么都没关系,是不是get、is、has开头都不重要。
那么这俩有啥问题?
取自JDK 6 update 27:
- product(bool, UseFastEmptyMethods, true, \
- "Use fast method entry code for empty methods") \
- \
- product(bool, UseFastAccessorMethods, true, \
- "Use fast method entry code for accessor methods") \
看到这俩参数的默认值都是true了么?也就是说,在Oracle/Sun JDK 6上设置这参数其实也是没意义的,跟默认一样,一直到最新的JDK 6 update 29都是如此。
不过在Oracle/Sun JDK 7里,情况有变化。
Bug ID: 6385687 UseFastEmptyMethods/UseFastAccessorMethods considered harmful
在上述bug对应的代码变更后,这俩参数的默认值改为了false。
本来想多写点这块的…算,还是长话短说。
Oracle JDK 7里的HotSpot VM已经开始有比较好的多层编译(tiered compilation)支持,可以预见在不久的将来该模式将成为HotSpot VM默认的执行模式。当前该模式尚未默认开启;可以通过 -XX:+TieredCompilation 来开启。
有趣的是,在使用多层编译模式时,如果UseFastAccessorMethods/UseFastEmptyMethods是开着的,有些多态方法调用点的性能反而会显著下降。所以,为了适应多层编译模式,JDK 7里这两个参数的默认值就被改为false了。
在邮件列表上有过相关讨论:review for 6385687: UseFastEmptyMethods/UseFastAccessorMethods considered harmful
======================================================================
相关推荐
3. **JVM调优“标准参数”的陷阱**:R大的文章详细介绍了在不同JDK版本下JVM调优过程中可能遇到的一些陷阱。尽管该文章最初是在JDK 6时撰写的,但是其中提到的很多原则仍然适用,并且随着JDK版本的更新,这些原则也...
5. **性能调优**:涵盖JVM性能监控工具的使用,如JConsole、VisualVM等,以及如何通过调整JVM参数(如-Xms, -Xmx, -XX:NewRatio等)来优化内存分配和垃圾收集策略。 6. **线程分析**:讲解如何理解和诊断线程问题,...
10. 描述如何优化Java服务器的性能,包括JVM调优和数据库优化。 通过深入理解和实践这些Java陷阱,不仅可以避免在编程中犯错,也能在面试中展现出专业技能,为你的职业生涯加分。不断学习和探索,使你在Java的世界...
7. **JVM调优**:理解JVM内存模型,如堆内存、栈内存、方法区等,以及如何进行性能优化,包括堆大小设置、类加载机制等。 8. **IO/NIO/BIO**:理解三种I/O模型的特点,尤其是NIO(非阻塞I/O)在高性能服务器编程中...
4. **JVM调优** - 面试陷阱:面试官可能会要求解释JVM的内存模型,或者如何设置JVM参数进行性能优化。 - 解析:Java虚拟机有堆内存(包括新生代、老年代)、方法区、栈等区域。理解各区域的作用,如如何设置-Xms、...
性能调优工具如JVM参数调优、垃圾回收机制、以及运行期剖析接口等,都是为了更好地管理JVM性能而设计的工具和方法。在Java代码运行过程中,内存泄漏是常见的性能瓶颈之一,因此理解和分析内存泄漏的原因和症状是进行...
对于堆内存的设置原则和JVM参数调优也是内存泄漏分析中不可或缺的内容。 书中还讨论了并发和多线程编程中经常遇到的问题。比如,在什么情况下需要加锁、如何正确地加锁、多线程编程中常见的错误、线程池的使用和...
4. **JVM优化**:熟悉JVM参数调优,如-Xms、-Xmx、-XX:NewRatio等。理解类加载器的双亲委派模型。 5. **集合框架**:深入理解ArrayList、LinkedList、HashSet、HashMap等集合类的内部实现,包括它们的时间复杂度和...
另一个焦点是JVM参数调优,例如堆大小设置、新生代与老年代的比例、垃圾收集器的选择等。理解这些参数如何影响JVM的行为并合理配置,可以显著提升应用的性能和稳定性。 此外,书中还可能涉及并发和多线程性能优化,...
5. **JVM优化**:了解JVM的运行机制,包括类加载、内存模型、垃圾收集器和性能调优,这些都是面试中常见的问题。例如,理解堆内存的分代模型,知道如何分析和调整JVM参数以优化应用性能。 6. **集合框架**:深入...
《JAVA解惑》可能包含如何进行性能分析、调优JVM参数,以及如何利用并发和多线程提升程序运行速度的策略。此外,书中还可能涉及测试与调试技术,如单元测试、集成测试以及如何使用调试工具有效地定位和修复问题。 ...
6. Java性能调优,涉及JVM参数设置、算法优化、数据结构选择等。 7. Java安全相关知识,例如Java安全模型、加密解密技术、网络安全等。 8. Java框架和设计模式的运用,如Spring、Hibernate、MVC模式、单例模式等。 9...
- **JVM调优**:JVM内存模型、GC算法、性能监控工具。 4. **设计模式** - **工厂模式**:单例模式、工厂方法模式、抽象工厂模式。 - **结构模式**:适配器模式、代理模式、装饰者模式等。 - **行为模式**:观察...
垃圾回收(GC)是Java虚拟机...这涉及到对JVM内存分配参数的理解、垃圾回收日志的分析以及对垃圾回收器特性的深入掌握。最终,正确地理解和使用垃圾回收器可以帮助开发者构建更加稳定、高效和可扩展的Java应用程序。
8. **性能调优**:JVM参数调整、代码优化、数据库查询优化等,以提升应用程序的性能。 9. **网络编程**:Socket编程、HTTP协议处理、WebSocket等网络通信相关的问题。 10. **数据结构与算法**:在Java中实现数据...
- **3.5.3 特殊场合下JVM参数调优** - 针对不同的应用场景调整JVM参数,如使用G1垃圾回收器。 - **3.5.4 Java完全垃圾回收** - 使用`System.gc()`强制触发垃圾回收,但不推荐频繁使用。 - **3.5.5 top陷阱:** - ...
10. **JVM优化**:深入理解JVM的工作原理并进行调优是提高Java应用性能的关键,文档可能会涵盖类加载、内存配置、JVM参数调整等方面。 11. **单元测试和持续集成**:JUnit和Maven等工具是Java开发中的标准配置,...
3. **JVM优化**:了解JVM的工作原理和调优技巧对于提升应用性能至关重要。书中会讲解如何配置JVM参数,以及分析和解决性能瓶颈。 4. **异常处理**:良好的异常处理策略能增强程序的健壮性。书中会讨论何时抛出异常...
14. **JVM优化**:探讨虚拟机参数设置,内存模型,垃圾回收机制,以提升程序运行效率。 15. **持续集成与版本控制**:介绍Git等版本控制系统和持续集成工具的使用,提高团队协作效率。 通过学习《Java开发手册...