JVM高级特性与实践(四):内存分配 与 回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:分配内存给对象、回收分配给对象的内存。

关于第二点的回收内存,在之前的博文中已经讲解过虚拟机中的垃圾收集体系以及运作原理,而此篇文章就来探讨学习有关分配内存给对象,相关知识点如下:

  • 对象内存分配、回收解析
  • Minor GC 与 Full GC概念
  • 五大策略解析
  • 策略应用到代码实践原理解析

JVM高级特性与实践(一):Java内存区域 与 内存溢出异常
JVM高级特性与实践(二):对象存活判定算法(引用) 与 回收
JVM高级特性与实践(三):垃圾收集算法 与 垃圾收集器实现
JVM高级特性与实践(五):实例探究Class类文件 及 常量池
JVM高级特性与实践(六):Class类文件的结构(访问标志,索引、字段表、方法表、属性表集合)
JVM高级特性与实践(七):九大类字节码指令集(实例探究 )
JVM高级特性与实践(八):虚拟机的类加载机制
JVM高级特性与实践(九):类加载器 与 双亲委派模式(自定义类加载器源码探究ClassLoader)
JVM高级特性与实践(十):虚拟机字节码执行引擎(栈帧结构)
JVM高级特性与实践(十一):方法调用 与 字节码解释执行引擎(实例解析)
JVM高级特性与实践(十二):Java内存模型 与 高效并发时的内外存交互(volatile变量规则)
JVM高级特性与实践(十三):线程实现 与 Java线程调度
JVM高级特性与实践(十四):线程安全 与 锁优化


一. 内存分配 和 回收策略

1. 准备知识

(1)概述

首先来初步了解一下有关对象内存分配的概念,往大方向讲,它就是在上分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存有关的参数设置。

(2)Java堆内存

此点在上篇博文已经详细讲过,可后续讲解大量涉及到Java堆内存中的Eden区Survivor区等类似概念及分配,所以在此复习,查看以下图解了解即可:

这里写图片描述

(3)重要概念需知

下面将会讲解几条最普遍的内存分配规则,并通过代码去验证这些规则。以下的讲解会涉及到两个重要的概念,需提前了解:

  • 新生代GC(Minor GC): 指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭(即生命周期特别短)的特征,所以MinorGC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 Parallel Scavenge
    收集器的收集策略里就有直接进行Major GC的策略选择过程)。 老年代GC 的速度一般比 新生代GC慢10倍以上。

(4)代码实战设置环境条件

注意:下面代码测试是在Client模式虚拟机运行,未收工指定收集器组合,也就是说验证的是 Serial/ Serial Old收集器(ParNew / Serial Old收集器组合的规则也基本一致)下的内存分配和回收策略。以下代码测试都将加上了以下参数:

-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

以上设置意味着将Java堆内存大小限制设置为20M,由于新生代和老年代各占一半,所以新生代占10M内存。Eden区Survivor区的比例是8,在新生代中由一块Eden区和两块大小相等的Survivor区组成,所以Eden区内存为8M,每个Survivor区大小为1M。



2. 五大策略

2.1 对象优先在 Eden 分配

(1)策略解析

大多数情况下,对象在新生代Eden区 中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次新生代GC(Minor GC)。

(2)代码实践与日志展示

【新生代 Minor GC】

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  */
public static void testAllocation() {
    byte[] allocation1, allocation2, allocation3, allocation4;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[4 * _1MB];  // 出现一次Minor GC
 }

运行结果:

[GC [DefNew: 6487K->152K(9216K), 0.0040116 secs] 6487K->6296K(19456K), 0.0040436 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
        Heap 
         def new generation   total 9216K, used 4576K [0x32750000, 0x33150000, 0x33150000) 
          eden space 8192K,  54% used [0x32750000, 0x32ba1fa8, 0x32f50000) 
          from space 1024K,  14% used [0x33050000, 0x33076150, 0x33150000) 
          to   space 1024K,   0% used [0x32f50000, 0x32f50000, 0x33050000) 
         tenured generation   total 10240K, used 6144K [0x33150000, 0x33b50000, 0x33b50000) 
           the space 10240K,  60% used [0x33150000, 0x33750030, 0x33750200, 0x33b50000) 
         compacting perm gen  total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000) 
           the space 12288K,   3% used [0x33b50000, 0x33bae2c0, 0x33bae400, 0x34750000) 
            ro space 10240K,  55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000) 
            rw space 12288K,  55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000) 

(3)结果分析

testAllocation() 方法中,尝试分配3个2MB大小和1个4MB大小的对象。从输出结果中可以发现“eden space 8192K、from space 1024K、to space 1024K”的信息,新生代总可用空间为9216KB(Eden区 + 1个 Survivor区的总容量)。执行testAllocation() 方法中的分配 allocation4 对象的语句会发生一次 Minor GC,这次GC的结果是新生代6651KB变为148KB,而总内存占用量则几乎没有减少,因为allocation1 、allocation2 、allocation3 三个对象都是存活的,虚拟机几乎没有找到可回收的对象。

这次GC发生的原因给allocation4 分配内存时,发现Eden区 已经被占用了6MB,剩余空间已不足以分配allocation4 所需的4MB内存,因此发生了 Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入 Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。

此次GC结束后,4MB的 allocation4 对象顺利分配在 Eden中,因此程序执行完后的结果是 Eden区占用4MB(被allocation4占用 ),Survivor空间处于空闲状态,老年代被占用6MB(被allocation1、allocation2、allocation3占用)。



2.2 大对象直接进入老年代

(1)大对象

大对象”是代表需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(例如如下代码中的byte[]数组)。

(2)策略解析

大对象对虚拟机的内存分配就是一个坏消息(拓展一下:对Java虚拟机而言,比遇到一个大对象更坏的情况时遇到一群“朝生夕灭”的“短命大对象”,编写程序时应当避免此现象产生),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。

(3)测试环境设置

大体的新生代老年代内存大小设置同以上一样,只是这里多设置了一个限制:虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置的对象直接在老年代分配。目的是为了避免在Eden区及两个Survivor区之间发生大量的内存复制(注意:新生代采用复制算法)。

(4)代码实践与日志展示

【大对象直接进入老年代】

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * -XX:PretenureSizeThreshold=3145728
 */
public static void testPretenureSizeThreshold() {
    byte[] allocation;
    allocation = new byte[4 * _1MB];  //直接分配在老年代中
}

运行结果:

 Heap 
def new generation   total 9216K, used 507K [0x32750000, 0x33150000, 0x33150000) 
 eden space 8192K,   6% used [0x32750000, 0x327cef38, 0x32f50000) 
 from space 1024K,   0% used [0x32f50000, 0x32f50000, 0x33050000) 
 to   space 1024K,   0% used [0x33050000, 0x33050000, 0x33150000) 
tenured generation   total 10240K, used 4096K [0x33150000, 0x33b50000, 0x33b50000) 
  the space 10240K,  40% used [0x33150000, 0x33550010, 0x33550200, 0x33b50000) 
compacting perm gen  total 12288K, used 376K [0x33b50000, 0x34750000, 0x37b50000) 
  the space 12288K,   3% used [0x33b50000, 0x33bae3b8, 0x33bae400, 0x34750000) 
   ro space 10240K,  55% used [0x37b50000, 0x380d1140, 0x380d1200, 0x38550000) 
   rw space 12288K,  55% used [0x38550000, 0x38bf44c8, 0x38bf4600, 0x39150000) 

(5)结果分析

执行完testPretenureSizeThreshold() 方法后,查看打印日志的“the space 10240K, 40% used”,可以发现Eden空间几乎没有被使用,而老年代的10MB空间被使用了40%,也就是4MB的allocation 对象直接被分配到老年代中,因为PretenureSizeThreshold被设置为3M(就是3145728,此参数不能像 -Xmx之类的参数那样写成3MB),因此超过3MB的对象都会直接在老年代进行分配。



2.3 长期存活的对象将进入老年代

(1)对象年龄(Age)计数器

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别对象应放在新生代还是老年代。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。

(2)策略解析

如果对象在Eden出生并经过第一次 Minor GC后仍然存活,并且能被Survivor 容纳的话,将被移动到 Survivor空间中,并且对象年龄设为1。对象在Survivor 区 每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会晋升到老年代中。

(3)测试环境设置

大体的新生代老年代内存大小设置都是一样,这里多出现了一种参数:-XX:MaxTenuringThreshold,可通过它来设置对象晋升老年代的年龄阀值。

(4)代码实践与日志展示

【长期存活的对象进入老年代】

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
 * -XX:+PrintTenuringDistribution
 */
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
    byte[] allocation1, allocation2, allocation3;
    allocation1 = new byte[_1MB / 4];  // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
    allocation2 = new byte[4 * _1MB];
    allocation3 = new byte[4 * _1MB];
    allocation3 = null;
    allocation3 = new byte[4 * _1MB];
}

以 MaxTenuringThreshold=1 参数来运行的结果:

[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 1) 
- age   1:     418144 bytes,     418144 total 
: 4695K->408K(9216K), 0.0054252 secs] 4695K->4504K(19456K), 0.0054708 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]  
[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 1) 
- age   1:        136 bytes,        136 total 
: 4668K->0K(9216K), 0.0013601 secs] 8764K->4504K(19456K), 0.0013867 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
Heap 
 def new generation   total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000) 
  eden space 8192K,  52% used [0x32750000, 0x32b78fe0, 0x32f50000) 
  //☆☆☆
  from space 1024K,   0% used [0x32f50000, 0x32f50088, 0x33050000) 
  to   space 1024K,   0% used [0x33050000, 0x33050000, 0x33150000) 
 tenured generation   total 10240K, used 4504K [0x33150000, 0x33b50000, 0x33b50000) 
   //☆☆☆
   the space 10240K,  43% used [0x33150000, 0x335b60a0, 0x335b6200, 0x33b50000) 
 compacting perm gen  total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000) 
   the space 12288K,   3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000) 

以 MaxTenuringThreshold=15 参数来运行的结果:

  ......
  from space 1024K,   39% used [0x32f50000, 0x32f50088, 0x33050000) 
  ......
   the space 10240K,  40% used [0x33150000, 0x335b60a0, 0x335b6200, 0x33b50000) 
  ......

(5)结果分析

以上分别将参数 -XX:MaxTenuringThreshold设置成1 和15来进行测试代码中的 testTenuringThreshold() 方法,此方法中的allocation1 对象需要256KB内存,Survivor空间可以容纳。

  • MaxTenuringThreshold = 1 时,allocation1 对象在第二次GC 发送时进入老年代,新生代已使用的内存GC 后非常干净地变成 0KB。
  • MaxTenuringThreshold = 15 时,在第二次GC后,allocation1 对象还留在新生代 Survivor空间,此时新生代仍然有404KB 被占用。


2.4 动态对象年龄判断

(1)策略解析

为了能够更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到MaxTenuringThreshold规定值才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到参数的规定值。

(2)测试环境设置

大体的新生代老年代内存大小设置都是一样,这里将参数-XX:MaxTenuringThreshold(可通过它来设置对象晋升老年代的年龄阀值)设置为15。

(3)代码实践与日志展示

【动态对象年龄判断】

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
 * -XX:+PrintTenuringDistribution
 */
@SuppressWarnings("unused")
public static void testTenuringThreshold2() {
    byte[] allocation1, allocation2, allocation3, allocation4;
    allocation1 = new byte[_1MB / 4];   
    // allocation1+allocation2大于survivo空间一半
    allocation2 = new byte[_1MB / 4];  
    allocation3 = new byte[4 * _1MB];
    allocation4 = new byte[4 * _1MB];
    allocation4 = null;
    allocation4 = new byte[4 * _1MB];
}

运行结果:

[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 1 (max 15) 
- age   1:     680304 bytes,     680304 total 
: 4951K->664K(9216K), 0.0033210 secs] 4951K->4760K(19456K), 0.0033442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
[GC [DefNew 
Desired survivor size 524288 bytes, new threshold 15 (max 15) 
- age   1:        136 bytes,        136 total 
: 4924K->0K(9216K), 0.0011772 secs] 9020K->4760K(19456K), 0.0011987 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
Heap 
 def new generation   total 9216K, used 4260K [0x32750000, 0x33150000, 0x33150000) 
  eden space 8192K,  52% used [0x32750000, 0x32b78fe0, 0x32f50000) 
  from space 1024K,   0% used [0x32f50000, 0x32f50088, 0x33050000) 
  to   space 1024K,   0% used [0x33050000, 0x33050000, 0x33150000) 
 tenured generation   total 10240K, used 4760K [0x33150000, 0x33b50000, 0x33b50000) 
   the space 10240K,  46% used [0x33150000, 0x335f60b0, 0x335f6200, 0x33b50000) 
 compacting perm gen  total 12288K, used 377K [0x33b50000, 0x34750000, 0x37b50000) 
   the space 12288K,   3% used [0x33b50000, 0x33bae5c0, 0x33bae600, 0x34750000) 

(4)结果分析

查看日志可知,执行完代码后,结果中的 Survivor空间占用仍为 0%,而老年代比预期增加了 6%,也就是说:allocation1 、allocation2 对象都直接进入老年代,而没有 等到15岁的临界年龄。因为这两个对象加起来已达了512KB,并且它们是同年的,满足同年对象达到Survivor 空间的一半规则(只需注释掉其中一个对象的new操作,就会发现另外一个就不会晋升到老年代中)



2.5 空间分配担保

(1)策略解析

在发生 Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间。

  • 如果以上条件成立,那么 Minor GC可确保时安全的。
  • 若不成立,则虚拟机会查看HandlePromotionFailure参数设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。
    • 如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;
    • 如果小于或者HandlePromotionFailure参数设置不允许“冒险”,此时改为进行一次 Full GC。

(2)“冒险”概念解析

上小点中提到了“冒险”,来解释其内涵:在前面提过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个 Survivor空间作为轮换备份,因此当出现大量对象在 Minor GC 后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把 Survivor无法容纳的对象直接进入老年代

与生活中的“贷款”场景类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行 Full GC 来让老年代腾出更多空间。

(3)测试环境设置

大体的新生代老年代内存大小设置都是一样,新加了一个参数:-XX:HandlePromotionFailure,用来设置是否允许担保失败。

(4)代码实践与日志展示

【空间分配担保】

private static final int _1MB = 1024 * 1024;

/**
 * VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
 */
@SuppressWarnings("unused")
public static void testHandlePromotion() {
    byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation1 = null;
    allocation4 = new byte[2 * _1MB];
    allocation5 = new byte[2 * _1MB];
    allocation6 = new byte[2 * _1MB];
    allocation4 = null;
    allocation5 = null;
    allocation6 = null;
    allocation7 = new byte[2 * _1MB];
}

HandlePromotionFailure = false参数来运行的结果:

 [GC [DefNew: 6487K->152K(9216K), 0.0040346 secs] 6487K->4248K(19456K), 0.0040639 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
[GC [DefNew: 6546K->6546K(9216K), 0.0004896 secs] 10642K->4248K(19456K), 0.0005141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

HandlePromotionFailure = true参数来运行的结果:

 [GC [DefNew: 6487K->152K(9216K), 0.0040346 secs] 6487K->4248K(19456K), 0.0040639 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]  
[GC [DefNew: 6546K->152K(9216K), 0.0004896 secs] 10642K->4248K(19456K), 0.0006143 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

(5)结果分析

从日志可看出,设置HandlePromotionFailure 参数不同的值,影响到虚拟机的空间分配担保原则,当参数为true时,即允许担保失败,会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,来决定后续是 Minor GC 还是 Full GC。




二. 小结

此篇博文通过代码实践的方式验证了Java虚拟机中自动内存分配及回收的主要五大规则。学到这里可以发现,前面几篇博文中讲解的垃圾收集算法、各个收集器的知识等都是一层层的铺垫,显现在真正的实践当中。因此,学习虚拟机内存知识,如果要到实践调优阶段,那么必须了解每个具体收集器的行为、优势和劣势、调节参数,这些知识点都是环环相扣,缺一不可,攀上JVM山峰这条路,还是得扎扎实实一步一步地爬,与之共勉~


若有错误,欢迎指教~

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 深入理解Java虚拟机是一本介绍JVM高级特性和最佳实践的书籍。它涵盖了JVM的内存模型、垃圾回收机制、类加载器、字节码执行引擎等方面的知识,并提供了一些优化JVM性能的实践建议。这本书对于想要深入了解JVM内部工作原理的Java开发人员来说是一本非常有价值的参考书。 ### 回答2: Java虚拟机(JVM)是Java语言的核心组件,它负责将Java代码翻译成操作系统能够识别和执行的指令。尽管JVMJava语言的核心组件之一,但是它的高级特性与最佳实践通常被Java开发者忽视,因此深入理解JVM高级特性与最佳实践对于Java开发者来说是非常重要的。 在深入理解JVM高级特性和最佳实践之前,我们首先需要了解JVM运行Java代码的过程。Java代码在运行时需要进行编译,这个过程可以分为以下几个步骤: 1. 编写Java源代码 2. 通过Java编译器将Java源代码编译成字节码文件 3. JVM将字节码文件解析成操作系统能够识别和执行的指令 4. 操作系统执行指令 JVM在解析字节码文件时会采用以下两种方式: 1. 解释执行 2. 即时编译 在解释执行过程中,JVM根据字节码文件中的指令一条一条地执行代码。这种方式的优点是JVM不需要提前编译代码,因此可以在不同的硬件和操作系统上运行相同的代码。但是,解释执行的速度比较慢,因此不适用于需要高性能的应用程序。 在即时编译过程中,JVM在运行时将字节码文件转换为机器码执行。这种方式的优点在于可以提高应用程序的执行速度,但是需要在应用程序运行时进行编译,因此可能会造成一些性能损失。 除了解释执行和即时编译之外,JVM还具有以下几个高级特性: 1. 垃圾回收 2. 类加载器 3. 多线程编程 垃圾回收JVM的一项重要功能,它可以自动回收应用程序中不再使用的内存。JVM的垃圾回收机制可以减少Java开发者手动管理内存的工作量,因此提高了开发效率。但是,垃圾回收机制也会对应用程序的性能造成影响。 类加载器是JVM中的另一个重要组件,它可以加载应用程序中使用的类。JVM中的类加载器会根据应用程序中的类的依赖关系加载类,这样可以使应用程序更加健壮和安全。 多线程编程是JVM中的另一个高级特性。多线程编程可以减少应用程序的执行时间,并且可以使应用程序更加可靠和可伸缩。JVM通过提供线程安全的对象和锁来支持多线程编程。 除了了解JVM高级特性之外,Java开发者还需要了解一些最佳实践。以下是一些Java开发者应该遵循的最佳实践: 1. 在开发和测试过程中使用合适的JVM参数 2. 使用合适的垃圾回收机制 3. 配置合适的内存大小 4. 优化多线程编程 5. 进行代码优化 熟练掌握JVM高级特性和最佳实践对于Java开发者来说是非常重要的,因为这些知识可以帮助Java开发者编写更高效、更安全和更可靠的代码。 ### 回答3: Java虚拟机(JVM)是Java程序的运行环境,也是Java语言的核心。深入理解Java虚拟机(JVM)的高级特性与最佳实践,对于Java开发人员来说,是非常必要的。 首先,Java虚拟机有很多高级特性,这些特性包括JVM调优、内存管理、垃圾回收、类加载机制、线程管理、JNI等等。这些都是Java开发人员必须熟悉的内容。例如,我们需要学会如何通过调整JVM参数来优化Java应用程序的性能,如何管理JVM的内存,避免内存泄漏和OOM(Out of Memory)等问题。同时,我们也需要了解JVM中的垃圾回收机制,以及如何通过调整垃圾回收器的参数来实现更好的性能。 其次,最佳实践也非常重要。我们需要遵循规范的编程习惯,在代码编写中避免一些常见的问题,如死锁、并发争用、线程安全等等。同时,我们还需要遵循一些最佳实践,如尽量使用不可变对象、使用线程安全的集合、优化代码避免频繁的内存分配和垃圾回收等等。 除此以外,我们还需要深入了解Java虚拟机内部的工作原理,包括类文件的结构、类加载机制、字节码的执行过程等等。只有这样,我们才能更好地利用JVM特性来实现更好的性能和可靠性,同时还能更好地调试应用程序,解决出现的问题。 总之,深入理解Java虚拟机的高级特性和最佳实践,对于Java开发人员来说是非常重要的,只有这样才能够写出高质量、高性能的Java应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值