B02、运行时内存篇-3.2

news2025/1/20 1:48:11

1、堆

1.1、核心概述

        一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。Java 堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。

        堆内存的大小是可以调节的。在《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。堆,是GC ( Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除

1、对象都分配在堆上?

        Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for all class instances and arrays is allocated ) 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。我要说的是:“几乎”所有的对象实例都在这里分配内存。——从实际使用角度看的。

public class SimpleHeap {
    private int id;

    public SimpleHeap(int id) {
        this.id = id;
    }

    public void show() {
        System.out.println("My ID is " + id);
    }

    public static void main(String[] args) {
        SimpleHeap sl = new SimpleHeap(1);
        SimpleHeap s2 = new SimpleHeap(2);
    }
}

2、所有的线程都共享堆?

        所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB)。

1.2、堆的内部结构

现代垃圾收集器大部分都基于分代收集理论设计,堆空间细分为:

Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区

  • Young Generation Space   新生区      Young/New
    • 又被划分为Eden区和Survivor区
  • Tenure generation space  养老区      Old/Tenure
  • Permanent Space          永久区      Perm

Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间

  • Young Generation Space    新生区      Young/New
    • 又被划分为Eden区和Survivor区
  • Tenure generation space   养老区      Old/Tenure
  • Meta Space                元空间      Meta

约定说法:

新生区<=>新生代<=>年轻代

养老区<=>老年区<=>老年代 

永久区<=>永久代

1、年轻代与老年代

        存储在JVM中的Java对象可以被划分为两类:

  • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
  • 另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。

        Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(OldGen)。其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)。

        几乎所有的Java对象都是在Eden区被new出来的。绝大部分的Java对象的销毁都在新生代进行了。(IBM 公司的专门研究表明,新生代中 80% 的对象都是“朝生夕死”的)

1.3、如何设置堆内存大小

        Java堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,大家可以通过选项”-Xmx”和”-Xms”来进行设置。

  • “-Xms”用于表示堆区的起始内存,等价于-XX:InitialHeapSize
  • “-Xmx”则用于表示堆区的最大内存,等价于-XX:MaxHeapSize

        一旦堆区中的内存大小超过“-Xmx”所指定的最大内存时,将会抛出OutOfMemoryError:heap异常。

        通常会将 -Xms 和 -Xmx两个参数配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

  • heap默认最大值计算方式:如果物理内存少于192M,那么heap最大值为物理内存的一半。如果物理内存大于等于1G,那么heap的最大值为物理内存的1/4
  • heap默认最小值计算方式:最少不得少于8M,如果物理内存大于等于1G那么默认值为物理内存的1/64,即1024/64=16M。最小堆内存在jvm启动的时候就会被初始化。

注意:对于32位虚拟机,如果物理内存等于4G,那么堆内存可以达到1G。对于64位虚拟机,如果物理内存为128G,那么heap最多可以达到32G。

1、如何设置新生代与老年代比例?

        这参数正常在开发中是不会调整的。而配置新生代与老年代在堆结构的占比是:

  • 默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
  • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

        也可以使用选项”-Xmn”设置新生代最大内存大小,这个参数一般使用默认值就可以了。

2、如何设置Eden、幸存者区比例?

        在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1:1。当然开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8

3、OOM异常示例

public class OOMTest {
    public static void main(String[] args) {
        ArrayList<Picture> list = new ArrayList<>();
        while(true){
//            try {
//                Thread.sleep(20);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            list.add(new Picture(new Random().nextInt(1024 * 1024)));
        }
    }
}

4、参数设置小结

4.1、-Xms -Xmx

堆空间大小的设置:

  • -Xms:初始内存 (默认为物理内存的1/64);
  • -Xmx:最大内存(默认为物理内存的1/4);
/**
 * -Xms10m -Xmx10m
 */
public class HeapSpaceTest {
    public static void main(String[] args) {
        String str = "hello word";
        List<String> list = new ArrayList<>();
        try {
            while(true){

                str +=  UUID.randomUUID().toString();
                list.add(str);
            }
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(list.size());
        }
    }
}
4.2、-Xmn

        设置新生代的大小。(初始值及最大值),通常默认即可。

4.3、-XX:NewRatio

        配置新生代与老年代在堆结构的占比。赋的值即为老年代的占比,剩下的1给新生代。默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3。当设置-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5。

4.4、-XX:SurvivorRatio

        在HotSpot中,Eden空间和另外两个Survivor空间缺省所占的比例是8:1。开发人员可以通过选项“-XX:SurvivorRatio”调整这个空间比例。比如-XX:SurvivorRatio=8

4.5、-XX:MaxTenuringThreshold

        设置新生代垃圾的最大年龄。超过此值,仍未被回收的话,则进入老年代。默认值为15。当设置-XX:MaxTenuringThreshold=0:表示年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。

        如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。

4.6、-XX:+PrintGCDetails

        用于输出详细的GC处理日志:

        显示如下:

Heap
 PSYoungGen      total 9728K, used 2497K [0x00000000fd580000, 0x00000000fe000000, 0x0000000100000000)
  eden space 8704K, 28% used [0x00000000fd580000,0x00000000fd7f06e8,0x00000000fde00000)
  from space 1024K, 0% used [0x00000000fdf00000,0x00000000fdf00000,0x00000000fe000000)
  to   space 1024K, 0% used [0x00000000fde00000,0x00000000fde00000,0x00000000fdf00000)
 ParOldGen       total 22016K, used 0K [0x00000000f8000000, 0x00000000f9580000, 0x00000000fd580000)
  object space 22016K, 0% used [0x00000000f8000000,0x00000000f8000000,0x00000000f9580000)
 Metaspace       used 3511K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 388K, capacity 390K, committed 512K, reserved 1048576K
4.7、-XX:HandlePromotionFailure

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

  • 如果大于,则此次Minor GC是安全的
  • 如果小于,则虚拟机会查看-XX:HandlePromotionFailure设置值是否允许担保失败。
  • 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

        在JDK 6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,观察OpenJDK中的源码变化,虽然源码中还定义了HandlePromotionFailure参数,但是在代码中已经不会再使用它。JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

4.8、-XX:+PrintFlagsFinal

        查看所有的参数的最终值(可能会存在修改,不再是初始值)。具体查看某个参数的指令:

  • jps:查看当前运行中的进程
  • jinfo -flag SurvivorRatio 进程id

1.4、对象分配口诀

        为新对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还需要考虑GC执行完内存回收后是否会在内存空间中产生内存碎片。

小结:

针对幸存者s0,s1区的总结:复制之后有交换,谁空谁是to(也就是 s1 区)

关于垃圾回收:

  • 频繁在新生区收集
  • 很少在养老区收集
  • 几乎不在永久区/元空间收集

1、对象分配过程剖析(重点理解)

  1. new的对象先放伊甸园区。此区有大小限制。
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC/YGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
  3. 然后将伊甸园中的剩余对象移动到幸存者0区。
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
  5. 如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
  6. 啥时候能去养老区呢?可以设置次数。默认是15次。
    1. 可以设置参数:-XX:MaxTenuringThreshold=<N> 设置对象晋升老年代的年龄阈值。
  7. 在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major GC,进行养老区的内存清理。
  8. 若养老区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常:java.lang.OutOfMemoryError: Java heap space

完成的流程图示说明:

2、分配策略

内存分配策略(或对象提升(promotion)规则):

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

3、内存分配原则(重点记忆)

针对不同年龄段的对象分配原则如下所示:

  • 优先分配到Eden
  • 大对象直接分配到老年代
    • 尽量避免程序中出现过多的大对象
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    • 如果Survivor 区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
  • 空间分配担保
    • -XX:HandlePromotionFailure
/** 测试:大对象直接进入老年代
 * -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
 */
public class YoungOldAreaTest {
    public static void main(String[] args) {
        byte[] buffer = new byte[1024 * 1024 * 20];//20m

    }
}

/**
 * -Xms600m -Xmx600m -XX:SurvivorRatio=8 -XX:+PrintGCDetails
 */
public class HeapInstanceTest {
    byte[] buffer = new byte[new Random().nextInt(1024 * 200)];

    public static void main(String[] args) {
        ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
        while (true) {
            list.add(new HeapInstanceTest());
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.5、解释MinorGC、MajorGC、FullGC

        JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:

  • 一种是部分收集(Partial GC)
  • 一种是整堆收集(Full GC)

        部分收集,不是完整收集整个Java堆的垃圾收集。其中又分为:

  • 新生代收集(Minor GC / Young GC):只是新生代(Eden\S0,S1)的垃圾收集
  • 老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
    • 目前,只有CMS GC会有单独收集老年代的行为。
    • 注意,很多时候Major GC会和Full GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
  • 混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。
    • 目前,只有G1 GC会有这种行为

        整堆收集(Full GC),顾名思义会收集整个java堆和方法区的垃圾收集。

1、MinorGC触发机制

        当年轻代空间不足时,就会触发Minor GC。这里的年轻代满指的是Eden区满,Survivor满不会引发GC(每次 Minor GC 会清理年轻代的内存)。

        因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。

2、MajorGC触发机制

        指发生在老年代的GC,对象从老年代消失时,我们说“Major GC”或“Full GC”发生了。出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。

        也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,则触发Major GC。Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。如果Major GC 后,内存还不足,就报OOM了。

3、FullGC触发机制

触发Full GC 执行的情况有如下五种:

(1)调用System.gc()时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、survivor space0(From Space)区向survivor space1(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

说明:full gc是开发或调优中尽量要避免的。这样暂时时间会短一些。

 4、代码示例

public class OOMTest {
    public static void main(String[] args) {
        String str = "www.baidu.com";
        //将参数调整的小一些,这样问题会出现的比较早。
        // -Xms8m -Xmx8m -XX:+PrintGCDetails
        while(true){
            str += str + new Random().nextInt(88888888) +
                    new Random().nextInt(999999999);
        }
    }
}


/**
 * 测试MinorGC 、 MajorGC、FullGC
 * -Xms10m -Xmx10m -XX:+PrintGCDetails
 */
public class GCTest {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();
            String a = "baidu.com";
            while (true) {
                list.add(a);
                a = a + a;
                i++;
            }
        } catch (Throwable t) {
            t.printStackTrace();
            System.out.println("遍历次数为:" + i);
        }
    }
}

1.6、OOM如何解决(重点关注)

        要解决OOM异常或heap space的异常,一般的手段是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

        如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置

        如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗

1.7、堆空间分代思想

        经研究,不同对象的生命周期不同。70%-99%的对象是临时对象。

  • 新生代:有Eden、两块大小相同的Survivor(又称为from/to,s0/s1)构成,to总为空。
  • 老年代:存放新生代中经历多次GC仍然存活的对象。
JDK7
JDK8

1、为什么要分代,不分代有什么影响

        其实不分代完全可以,分代的唯一理由就是优化GC性能。如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC 的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

1.8、快速分配策略:TLAB

1、为什么有 TLAB(Thread Local Allocation Buffer)?

  • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。

        所以,多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略(TLAB)

2、什么是 TLAB

        从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。据我所知所有OpenJDK衍生出来的JVM都提供了TLAB的设计。

3、TLAB 相关参数设置

        尽管不是所有的对象实例都能够在TLAB中成功分配内存,但JVM确实是将TLAB作为内存分配的首选。在程序中,开发人员可以通过选项“-XX:+/-UseTLAB”设置是否开启TLAB空间。

        默认情况下,TLAB空间的内存非常小,仅占有整个Eden空间的1%,当然我们可以通过选项“-XX:TLABWasteTargetPercent”设置TLAB空间所占用Eden空间的百分比大小。

        一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。

2、方法区

2.1、栈、堆、方法区的关系

对象创建示例:

Person person = new Person();

堆内示意图:

2.2、方法区在哪里?

        《Java虚拟机规范》中明确说明: “尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。” 但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。所以,方法区看作是一块独立于Java 堆的内存空间。

2.3、方法区的理解

        方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。

        方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:

  • java.lang.OutOfMemoryError: PermGen space 
  • java.lang.OutOfMemoryError: Metaspace

        比如加载大量的第三方的jar包;Tomcat部署的工程过多(30-50个);大量动态的生成反射类。关闭JVM就会释放这个区域的内存。

2.4、HotSpot中方法区的演进

        在jdk7及以前,习惯上把方法区,称为永久代。jdk8开始,使用元空间取代了永久代。

        本质上,方法区和永久代并不等价。仅是对hotspot而言的。《Java虚拟机规范》对如何实现方法区,不做统一要求。例如:BEA JRockit/ IBM J9中不存在永久代的概念。现在来看,当年使用永久代,不是好的idea。导致Java程序更容易OOM(超过-XX:MaxPermSize上限)

        而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替。

        元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。此外,永久代、元空间二者并不只是名字变了,内部结构也调整了。根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常。

2.5、方法区常用参数有哪些?

设置方法区内存的大小

        方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。在jdk7及以前:

  • 通过-XX:PermSize来设置永久代初始分配空间。默认值是20.75M
  • -XX:MaxPermSize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
  • 当JVM加载的类信息容量超过了这个值,会报异常OutOfMemoryError:PermGen space 。

        在jdk8以后:

  • 元数据区大小可以使用参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定,替代上述原有的两个参数。
  • 默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize 的值是-1,即没有限制。
  • 与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError: Metaspace
  • -XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。
  • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC ,建议将-XX:MetaspaceSize设置为一个相对较高的值。

        在JDK8 及以上版本中,设定MaxPermSize 参数, JVM在启动时并不会报错,但是会提示:

Java HotSpot 64Bit Server VM warning:
ignoring option MaxPermSize=2560m; support was removed in 8.0 。

代码示例:

/**
 * jdk8中:
 * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 * jdk6中:
 * -XX:PermSize=10m -XX:MaxPermSize=10m
 */
 
public class OOMTest extends ClassLoader {
    public static void main(String[] args) {
        int j = 0;
        try {
            OOMTest test = new OOMTest();
            for (int i = 0; i < 10000; i++) {
                //创建ClassWriter对象,用于生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                //指明版本号,public,类名,包名,父类,接口
                classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                //返回byte[]
                byte[] code = classWriter.toByteArray();
                //类的加载
                test.defineClass("Class" + i, code, 0, code.length);//CLass对象
                j++;
            }
        } finally {
            System.out.println(j);
        }
    }
}

2.6、方法区都存什么?

        《深入理解Java 虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

1、类型信息

        对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息: 

① 、这个类型的完整有效名称(全名=包名.类名)

② 、这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类) 

③、 这个类型的修饰符(public,abstract, final的某个子集) 

④ 、这个类型直接接口的一个有序列表 

2、域(Field)信息

        JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。域的相关信息包括: 域名称、域类型、域修饰符(public, private, protected, static, final, volatile, transient的某个子集)。

3、方法信息

        JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序 :

  • 方法名称 
  • 方法的返回类型(或 void)
  • 方法参数的数量和类型(按顺序)
  • 方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的一个子集)
  • 方法的字节码(bytecodes)、操作数栈、局部变量表及大小 (abstract和native方法除外)
  • 异常表(abstract和native方法除外)
    • 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

4、non-final 的类变量

        静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。类变量被类的所有实例共享,即使没有类实例时你也可以访问它。

public class MethodAreaTest {
    public static void main(String[] args) {
        Order order = null;
        order.hello();
        System.out.println(order.count);
    }
}

class Order{
    public static int count = 1;
    public static void hello(){
        System.out.println("hello!");
    }
}

补充说明:全局常量:static final

        被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。

5、运行时常量池

        运行时常量池(Runtime Constant Pool)是方法区的一部分。常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用这部分内容将在类加载后存放到方法区的运行时常量池中

        运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池。JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。

        运行时常量池,相对于Class文件常量池的另一重要特征是:具备动态性(String.intern())。运行时常量池类似于传统编程语言中的符号表(symbol table),但是它所包含的数据却比符号表要更加丰富一些。

        当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛OutOfMemoryError异常。

5.1、常量池
  • 方法区,内部包含了运行时常量池。
  • 字节码文件,内部包含了常量池。
  • 要弄清楚方法区,需要理解清楚ClassFile,因为加载类的信息都在方法区。
  • 要弄清楚方法区的运行时常量池,需要理解清楚ClassFile中的常量池。

        一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用。

小结:常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。

5.2、为什么需要常量池

        一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。

5.3、常量池有什么

        几种在常量池内存储的数据类型包括:

  1. 数量值
  2. 字符串值
  3. 类引用
  4. 字段引用
  5. 方法引用

例如下面这段代码:

public class MethodAreaTest2 {
    public static void main(String[] args) {
        Object obj = new Object();
    }
}

        其中代码:Object foo = new Object();将会被编译成如下字节码:

0:      new #2               // Class java/lang/Object
1:      dup
2:      invokespecial #3    // Method java/ lang/Object “<init>”( ) V

2.7、方法区使用举例

/**
 * 方法区使用示例
 */
public class MethodAreaDemo {
    public static void main(String[] args) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}

2.8、永久代与元空间

        首先明确,只有HotSpot才有永久代。BEA JRockit、IBM J9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。

1、永久代的变化

jdk1.6及之前:有永久代(permanent generation)

jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中

jdk1.8及之后: 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池仍在堆

2、为什么被元空间替代

        随着Java8 的到来,HotSpot VM 中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域叫做元空间( Metaspace )。

        由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。这项改动是很有必要的,原因有:

1)、为永久代设置空间大小是很难确定的。

        在某些场景下,如果动态加载类过多,容易产生Perm 区的OOM 。比如某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。

        而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。

2)、对永久代进行调优是很困难的。

3、StringTable 为什么要调整

        jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。

        这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

4、静态变量放哪里

/**
 * 静态引用对应的对象实体始终都存在堆空间
 * jdk7:
 * -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * jdk 8:
 * -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];

    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}




/**
 * staticObj、instanceObj、localObj存放在哪里?
 */
public class StaticObjTest {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder();
        ObjectHolder instanceObj = new ObjectHolder();

        void foo() {
            ObjectHolder localObj = new ObjectHolder();
            System.out.println("done");
        }
    }

    private static class ObjectHolder {
    }

    public static void main(String[] args) {
        Test test = new StaticObjTest.Test();
        test.foo();
    }
}

        staticObj随着Test的类型信息存放在方法区,instanceObj随着Test的对象实例存放在Java堆,localObject则是存放在foo()方法栈帧的局部变量表中。

        测试发现:三个对象的数据在内存中的地址都落在Eden区范围内,所以结论:只要是对象实例必然会在Java堆中分配。
         接着,找到了一个引用该staticObj对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面有一个名为staticObj的实例字段:

        从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,《Java虚拟机规范》并未做出规定,这就成了一件允许不同虚拟机自己灵活把握的事情。JDK 7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对象存放在一起,存储于Java堆之中,从我们的实验中也明确验证了这一点

2.9、方法区是否存在GC?回收什么?

        有些人认为方法区(如HotSpot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK 11时期的ZGC收集器就不支持类卸载)。

        一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前Sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

        方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。先来说说方法区内常量池之中主要存放的两大类常量:字面量和符号引用字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

        HotSpot虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收。回收废弃常量与回收Java堆中的对象非常类似。判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

        Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading -XX:+TraceClassUnloading查看类加载和卸载信息

        在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

2.10、内存结构小结

3、直接内存

3.1、概述

        不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。直接内存是在Java堆外的、直接向系统申请的内存区间。来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存;通常,访问直接内存的速度会优于Java堆。即读写性能高。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存(Java的NIO库允许Java程序使用直接内存,用于数据缓冲区)。

3.2、非直接缓冲区vs直接缓冲区

        读写文件,需要与磁盘交互,需要由用户态切换到内核态。在内核态时,需要内存如右图的操作。使用IO,见上图。这里需要两份内存存储重复数据,效率低。

        使用NIO时,如上图。操作系统划出的直接缓存区可以被java代码直接访问,只有一份。NIO适合对大文件的读写操作。

3.3、大小设置方式

        直接内存也是有可能导致OutOfMemoryError异常的。由于直接内存在Java堆外,因此它的大小不会直接受限于-Xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。

        缺点分配回收成本较高不受JVM内存回收管理。直接内存大小可以通过MaxDirectMemorySize设置;如果不指定,默认与堆的最大值-Xmx参数值一致

简单理解就是:java process memory(进程内存) = java heap(堆内存) + native memory(直接内存或本地内存)

3.4、代码示例

/**
 * -Xmx20m -XX:MaxDirectMemorySize=10m
 */
public class DirectMemTest {
    private static final long _1MB = 1024 * 1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafeField.get(null);
        while(true){
            unsafe.allocateMemory(_1MB);
        }
        
    }
}

如果不指定MaxDirectMemorySize,则默认与java堆最大值(-Xmx指定)一致。

4、StringTable

4.1、String 的不可变性

        通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。而字符串常量池中是不会存储相同内容的字符串的。

@Test
public void test1() {
    String s1 = "abc";//字面量的定义方式
       String s2 = "abc";
    s1 = "hello";

    System.out.println(s1 == s2);//比较s1和s2的地址值

    System.out.println(s1);//hello
    System.out.println(s2);//abc

    System.out.println("*****************");

    String s3 = "abc";
    s3 += "def";
    System.out.println(s3);//abcdef
    System.out.println(s2);

    System.out.println("*****************");

    String s4 = "abc";
    String s5 = s4.replace('a', 'm');
    System.out.println(s4);//abc
    System.out.println(s5);//mbc

}


@Test
public void test2(){
    StringTest1 ex = new StringTest1();
    ex.change(ex.str, ex.ch);
    System.out.print(ex.str + " and ");//
    System.out.println(ex.ch);
}
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };

public void change(String str, char ch[]) {
    str = "test ok";
    ch[0] = 'b';
}

4.2、String 的内存分配

整体来说:

  • Java 6及以前,字符串常量池存放在永久代。
  • Java 7 中 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。
  • Java 8 中,字符串常量仍然在堆。

StringTable为什么要调整?

        String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

        在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:

-XX:StringTableSize=99991

举例证明:jdk6

Eception in thread "main" java.lang.OutOfMemoryError: PermGen Space
    at java.lang.String.intern(Native Method)
    at cn.blnp.net.java3.StringTest2.main(StringTest2.java:18)

举例证明:jdk8

Eception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:703)
    at java.util.HashMap.putVal(HashMap.java:662)
    at java.util.HashMap.put(HashMap.java:611)
    at java.util.HashMap.add(HashMap.java:219)
    at cn.blnp.net.java3.StringTest2.main(StringTest2.java:20)

4.3、String 的基本操作

public class StringTest3 {
    @Test
    public void test1(){
        System.out.println();//2320
        System.out.println();//2321
        System.out.println();//2321
        System.out.println("1");//2321
        System.out.println("2");
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");//2330

        System.out.println("1");//2331
        System.out.println("2");//2331
        System.out.println("3");
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
        System.out.println("7");
        System.out.println("8");
        System.out.println("9");
        System.out.println("10");//2331
    }
    @Test
    public void test2(){
        String s1 = "a" + "b" + "c";//常量优化机制,编译的时候就已经是abc
        String s2 = "abc";
        /*
         * 最终.java编译成.class,再执行.class
         * String s1 = "abc";
         * String s2 = "abc"
         */
        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }
 
}

4.4、字符串拼接操作

@Test
public void test3(){
    String s1 = "a";
    String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;//new StringBuilder().append("a").append("b").toString() --> new String("ab")
    System.out.println(s3 == s4);
}
 
@Test
public void test4(){
    final String s1 = "a";
    final String s2 = "b";
    String s3 = "ab";
    String s4 = s1 + s2;
    System.out.println(s3 == s4);
}
 
//体会执行效率:
public void method1(){
    String src = "";
    for(int i = 0;i < 10;i++){
        src = src + "a";//每次循环都会创建一个StringBuilder
    }
    System.out.println(src);
    
}

public void method2(){
    StringBuilder src = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        src.append("a");
        
    }
    System.out.println(src);
}

4.5、new String() 的问题

String的实例化方式: 

  • 方式一:通过字面量定义的方式
  • 方式二:通过new + 构造器的方式

String s = new String("abc");方式创建对象,在内存中创建了几个对象?

        两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"

4.6、intern() 方法

public class StringTest4 {
    public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2);//

        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);//
    }
}

jdk6中的解释:

jdk7中的解释:

@Test
public void test1(){
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);//

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);//
    
}

4.7、G1的String去重操作

        许多大规模的java应用的瓶颈在于内存,测试表明,在这些类型的应用里面,java堆中存活的数据集合差不多25%是String对象。更进一步,这里面差不多一半String对象是重复的,重复的意思是说:string1.equals(string2)为true。堆上存在重复的String对象必然是一种内存的浪费。这个项目将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就能避免浪费内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1568748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch 压测实践总结

背景 搜索、ES运维场景离不开压力测试。 1.宿主机层面变更&#xff1a;参数调优 & 配置调整 & 硬件升级2.集群层面变更&#xff1a;参数调优3.索引层面变更&#xff1a;mapping调整 当然还有使用层面变更&#xff0c;使用API调优&#xff08;不属于该文章的讨论范围…

Python快速入门系列-10(Python进阶与扩展)

第十章:Python进阶与扩展 10.1 Python与其他语言的整合10.1.1 使用Python的C API示例:使用C API创建一个简单的Python扩展10.1.2 使用Cython加速Python代码示例:使用Cython编写一个快速的矩阵乘法函数10.1.3 使用SWIG创建接口示例:使用SWIG为C++类生成Python接口10.2 Pytho…

特征融合篇 | 结合内容引导注意力 DEA-Net 思想 实现双主干特征融合新方法 | IEEE TIP 2024

本篇改进已集成到 YOLOv8-Magic 框架。 摘要—单幅图像去雾是一个具有挑战性的不适定问题,它从观察到的雾化图像中估计潜在的无雾图像。一些现有的基于深度学习的方法致力于通过增加卷积的深度或宽度来改善模型性能。卷积神经网络(CNN)结构的学习能力仍然未被充分探索。本文…

5米分辨率数字高程模型(DEM)的制作

在现代科技的驱动下&#xff0c;地理信息系统&#xff08;GIS&#xff09;和遥感技术已经取得了惊人的进展。其中一项令人瞩目的技术就是5米分辨率数字高程模型&#xff08;DEM&#xff09;的制作&#xff0c;它是基于多颗高分辨率卫星数据为原始数据&#xff0c;借助智能立体模…

QA测试开发工程师面试题满分问答8: mysql数据库的索引定义、用途和使用场景

MySQL数据库索引是一种数据结构&#xff0c;用于提高数据库的查询效率。索引是基于表中的一个或多个列构建的&#xff0c;它们允许数据库系统快速定位和访问表中的特定数据&#xff0c;而无需扫描整个表。 索引的定义 在MySQL中&#xff0c;可以使用CREATE INDEX语句定义索引…

公园景区小红书抖音打造线上流量运营策划方案

【干货资料持续更新&#xff0c;以防走丢】 公园景区小红书抖音打造线上流量运营策划方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 共70页可编辑&#xff08;完整资料包含以下内容&#xff09; 目录 公园的线上运营方案&#xff1a; 一、运营目标 1. 品…

Gradle 创建gradle项目

创建Java项目 创建 Gradle 项目 项目设置 创建SSM项目 与上方步骤一致 添加war插件 添加相关的依赖&#xff1a; plugins {id groovyid war }group com.qaomuu version 1.0-SNAPSHOTrepositories {mavenCentral() }dependencies {implementation org.springframewor…

第十一届蓝桥杯物联网试题(省赛)

对于通信方面&#xff0c;还是终端A、B都保持接收状态&#xff0c;当要发送的数组不为空再发送数据&#xff0c;发送完后立即清除&#xff0c;接收数据的数组不为空则处理&#xff0c;处理完后立即清除&#xff0c;分工明确 继电器不亮一般可能是电压不够 将数据加空格再加\r…

vivado 串行矢量格式 (SVF) 文件编程

串行矢量格式 (SVF) 文件编程 注释 &#xff1a; 串行矢量格式 (SVF) 编程在 Versal ™ 器件上不受支持。 对 FPGA 和配置存储器器件进行编程的另一种方法是通过使用串行矢量格式 (SVF) 文件来执行编程。通过 Vivado Design Suite 和 Vivado Lab Edition 生成的 SVF …

c++-----string类及模拟实现———字符串

这一块更多的是字符串、顺序表和类和对象的结合 在c语言的时候我们已经学习了一些字符串相关的知识&#xff0c;本篇博客会用到相关的函数&#xff1b;c语言 字符串内存函数的介绍 文章目录 目录 文章目录 前言 一、string是什么&#xff1f; 二、sting常用函数 三、string…

聊一聊单点登录

互联网工程师 一、单点登录的概念 单点登录&#xff08;Single Sign-On&#xff0c;简称SSO&#xff09;是一种身份认证和授权技术&#xff0c;旨在解决用户在访问多个应用系统时需要重复登录的问题。该技术允许用户在一个应用系统中完成登录后&#xff0c;就可以访问其他相互信…

javaWeb城市公交查询系统的设计与实现

一、选题背景 随着低碳生活的普及&#xff0c;人们更倾向于低碳环保的出行方式&#xff0c;完善公交系统无疑具有重要意义。公交是居民日常生活中最常使用的交通工具之一&#xff0c;伴随着我国经济繁荣和城市人口增长&#xff0c;出行工具的选择也变得越来越重要。政府在公共…

尚硅谷50道Java面试题笔记 写的不全

b站链接&#xff1a;https://www.bilibili.com/video/BV1Bb411d7SL/?p4&vd_source714a8042f058b82c668750a0930ff9b0 1 mysql使用innodb引擎&#xff0c;请简述mysql索引的最左前缀如何优化orderby语句。 关键点&#xff1a; 如果排序字段不在索引列上&#xff0c;file…

JAVAEE——文件IO之文件操作

文章目录 文件的创建和销毁File概述构造方法常用的方法getAbsolutePath()exists()isDirectory()isFile()createNewFile()delete()deleteOnExit()list()listFiles()mkdir() 文件的创建和销毁 上面我们介绍了文件的读写操作那么文件的创建等的操作如何进行呢&#xff1f;这个操作…

媒体偏见从何而来?--- 美国MRC(媒体评级委员会)为何而生?

每天当我们打开淘宝&#xff0c;京东&#xff0c;步入超市&#xff0c;逛街或者逛展会&#xff0c;各种广告铺天盖地而来。从原来的平面广告&#xff0c;到多媒体广告&#xff0c;到今天融合AR和VR技术的数字广告&#xff0c;还有元宇宙虚拟世界&#xff0c;还有大模型加持的智…

美创科技获浙江省网络空间安全协会多项荣誉认可

4月2日&#xff0c;浙江省网络空间安全协会第二届会员大会第一次会议在杭州隆重召开&#xff0c;近180家会员单位代表、数十位特邀专家、嘉宾莅临现场。浙江省委网信办副主任马晓军出席会议并致辞&#xff0c;本次大会由协会秘书长吴铤主持。 凝心聚力&#xff0c;继往开来&…

Linux系统Docker如何部署Nextcloud结合内网穿透实现公网访问本地资源?

文章目录 1. 安装Docker2. 使用Docker拉取Nextcloud镜像3. 创建并启动Nextcloud容器4. 本地连接测试5. 公网远程访问本地Nextcloud容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署Nextcl…

uniApp使用uview对vuex的二次封装实现全局变量

1、uni-app目根目录新建’/store/index.js’&#xff0c;并复制如下内容到其中 2、uni-app目根目录新建’/store/ u . m i x i n . j s ′ &#xff0c;并复制如下内容到其中&#xff0c;由于 H X 某些版本的限制&#xff0c;我们无法帮您自动引入 " u.mixin.js&#xff0…

指针的偏移遍历数组--指针和数组名的区别

1.指针取地址&#xff1a;可以是数组名&#xff0c;可以是数组首地址&arr[0] 2.指针偏移完后记得回到数组首地址 #include <stdio.h>int main(){int arr[3] {1,2,3};int *p;int i;p arr; // 数组名就是数组的首地址// p &arr[0] 数组的首地址就是首个元素…

【问题处理】银河麒麟操作系统实例分享,鲲鹏服务器GaussDB测试ping延迟过高问题

1.问题环境 系统环境 物理机 网络环境 私有网络 硬件环境 机型 TaiShan 200 (Model 2280) (VD) 处理器 HUAWEI Kunpeng 920 5250 内存 32GB*16 显卡 无 主板型号 BC82AMDDRE 架构 ARM 固件版本 iBMC固件版本 3.03.00.31 (U82) 单板ID 0x00a9 BIOS版本 1.8…