<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ArrayList#subList這四個坑,一不小心就中招

          共 15119字,需瀏覽 31分鐘

           ·

          2022-06-27 20:50

          Hollis的新書限時折扣中,一本深入講解Java基礎(chǔ)的干貨筆記!

          一、使用不當(dāng)引起內(nèi)存泄露

          先給大家看一段簡單但是比較有意思的代碼

          public class OrderService {
              
              public static void main(String[] args) {
                  OrderService orderService = new OrderService();
                  orderService.process();
              }
              
              public void process() {
                  List<Long> orderIdList = queryOrder();
                  List<List<Long>> allFailedList = new ArrayList<>();
                  for(int i = 0; i < Integer.MAX_VALUE; i++) {
                      System.out.println(i);
                      List<Long> failedList = doProcess(orderIdList);
                      allFailedList.add(failedList);
                  }
              }
              
              private List<Long> doProcess(List<Long> orderIdList) {
                  List<Long> failedList = new ArrayList<>();
                  for (Long orderId : orderIdList) {
                      if (orderId % 2 == 0) {
                          failedList.add(orderId) ;
                      }
                  }
                  // 只取一個失敗的訂單id做分析
                  return failedList.subList(01);
              }
              
              private List<Long> queryOrder() {
                  List<Long> orderIdList = new ArrayList<>();
                  for (int i = 0; i < 1000; i++) {
                      orderIdList.add(RandomUtils.nextLong());
                  }
                  return orderIdList;
              }
          }

          如果你在本地的機器上運行這段代碼,并且打開arthas監(jiān)控內(nèi)存情況:

          Memory                            used        total      max         usage      
          heap                              2742M       3643M      3643M       75.28%     
          ps_eden_space                     11M         462M       468M        2.52%      
          ps_survivor_space                 0K          460288K    460288K     0.00%      
          ps_old_gen                        2730M       2731M      2731M       99.99%     
          nonheap                           28M         28M        -1          97.22%     
          code_cache                        5M          5M         240M        2.19%      
          metaspace                         20M         20M        -1          97.19%     
          compressed_class_space            2M          2M         1024M       0.25%      
          direct                            0K          0K         -           0.00%      
          mapped                            0K          0K         -           0.00% 

          不到3GB的老年代當(dāng)i循環(huán)到大概60萬左右的時候就已經(jīng)打爆了,而我們當(dāng)前堆中的最大的對象是allFailedList最多也是60萬個Long型的List,粗略的計算一下也只有幾十MB,完全不至于打爆內(nèi)存。那我們就有理由懷疑上面的這段代碼產(chǎn)生了內(nèi)存泄露了。

          回到ArrayList#subList的實現(xiàn)代碼:

          public List<E> subList(int fromIndex, int toIndex) {
              subListRangeCheck(fromIndex, toIndex, size);
              return new SubList(this0, fromIndex, toIndex);
          }

          private class SubList extends AbstractList<Eimplements RandomAccess {
              private final AbstractList<E> parent;
              private final int parentOffset;
              private final int offset;
              int size;

              SubList(AbstractList<E> parent,
                      int offset, int fromIndex, int toIndex) {
                this.parent = parent;
                this.parentOffset = fromIndex;
                this.offset = offset + fromIndex;
                this.size = toIndex - fromIndex;
                this.modCount = ArrayList.this.modCount;
              }
          }

          可以看到,每次調(diào)用ArrayList#subList的時候都會生成一個SubList對象,而這個對象的parent屬性值卻持有原ArrayList的引用,這樣一來就說得通了,allFailedList持有歷次調(diào)用queryOrder產(chǎn)生的List對象,這些對象最終都轉(zhuǎn)移到了老年代而得不到釋放。

          二、使用不當(dāng)引起死循環(huán)

          再看一段代碼:

          public class SubListDemo {

              public static void main(String[] args) {
                  List<Long> arrayList = init();
                  List<Long> subList = arrayList.subList(01);
                  for (int i = 0; i < arrayList.size(); i++) {
                      if (arrayList.get(i) % 2 == 0) {
                          subList.add(arrayList.get(i));
                      }
                  }
              }

              private static List<Long> init() {
                  List<Long> arrayList = new ArrayList<>();
                  arrayList.add(RandomUtils.nextLong());
                  arrayList.add(RandomUtils.nextLong());
                  arrayList.add(RandomUtils.nextLong());
                  arrayList.add(RandomUtils.nextLong());
                  arrayList.add(RandomUtils.nextLong());
                  return arrayList;
              }
          }

          如果我說上面的這段代碼是一個死循環(huán),你會感到奇怪么。回到subList的實現(xiàn)

          // AbstractList
          public boolean add(E e) {
              add(size(), e);
              return true;
          }

          然后會調(diào)用到ArrayList的方法

          public void add(int index, E e) {
              rangeCheckForAdd(index);
              checkForComodification();
              parent.add(parentOffset + index, e);
              this.modCount = parent.modCount;
              this.size++;
          }

          可以看到,調(diào)用subListadd其實是在原ArrayList中增加元素,因此原arrayList.size()會一直變大,最終導(dǎo)致死循環(huán)。

          三、無法對subList和原List做結(jié)構(gòu)性修改

          public static void main(String[] args) {
              List<String> listArr = new ArrayList<>();
              listArr.add("Delhi");
              listArr.add("Bangalore");
              listArr.add("New York");
              listArr.add("London");

              List<String> listArrSub = listArr.subList(13);

              System.out.println("List-: " + listArr);
              System.out.println("Sub List-: " + listArrSub);

              //Performing Structural Change in list.
              listArr.add("Mumbai");

              System.out.println("\nAfter Structural Change...\n");

              System.out.println("List-: " + listArr);
              System.out.println("Sub List-: " + listArrSub);
          }

          這段代碼最后會拋出ConcurrentModificationException

          List-: [Delhi, Bangalore, New York, London]
          Sub List-: [Bangalore, New York]

          After Structural Change...

          List-: [Delhi, Bangalore, New York, London, Mumbai]
          Exception in thread "main" java.util.ConcurrentModificationException
              at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
              at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
              at java.util.AbstractList.listIterator(AbstractList.java:299)
              at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
              at java.util.AbstractCollection.toString(AbstractCollection.java:454)
              at java.lang.String.valueOf(String.java:2982)
              at java.lang.StringBuilder.append(StringBuilder.java:131)
              at infosys.Research.main(Research.java:26)

          簡單看下ArrayList的源碼:

          public boolean add(E e) {
              ensureCapacityInternal(size + 1);  // Increments modCount!!
              elementData[size++] = e;
              return true;
          }

          private void ensureCapacityInternal(int minCapacity) {
              ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
          }

          private void ensureExplicitCapacity(int minCapacity) {
              // 注意這行對原list的modCount這個變量做了自增操作
              modCount++;

              // overflow-conscious code
              if (minCapacity - elementData.length > 0)
                  grow(minCapacity);
          }

          要注意,調(diào)用原數(shù)組的add方法時已經(jīng)修改了原數(shù)組的modCount屬性,當(dāng)程序執(zhí)行到打印subList這行代碼時會調(diào)用Sublist#toString方法,到最后會調(diào)用到下面這個私有方法:

          private void checkForComodification() {
              if (ArrayList.this.modCount != this.modCount)
                  throw new ConcurrentModificationException();
          }

          根據(jù)前面分析,原ArrayListmodCount屬性已經(jīng)自增,所以ArrayList.this.modCount != this.modCount執(zhí)行的結(jié)果是true,最終拋出了ConcurrentModificationException異常。

          關(guān)于modCount這個屬性,Oracle的文檔中也有詳細的描述

          The number of times this list has been structurally modified. Structural modifications are those that change the size of the list.

          翻譯過來就是:

          modCount記錄的是List被結(jié)構(gòu)性修改的次數(shù),所謂結(jié)構(gòu)性修改是指能夠改變List大小的操作

          如果提前沒有知識儲備,這類異常是比較難排查的

          四、作為RPC接口入?yún)r序列化失敗

          從上面SubList的定義可以看出來,SubList并沒有實現(xiàn)Serializable接口,因此在一些依賴Java原生序列化協(xié)議的RPC的框架中會序列化失敗,如Dubbo等。

          五、最佳實踐

          subList設(shè)計之初是作為原List的一個視圖,經(jīng)常在只讀的場景下使用,這和大多數(shù)人理解的不太一樣,即便只在只讀的場景下使用,也容易產(chǎn)生內(nèi)存泄露,況且這個視圖的存在還不允許原ListSubList做結(jié)構(gòu)性修改,個人認為subList這個Api的設(shè)計糟糕透了,盡量在代碼中避免直接使用ArrayList#subList,獲取ListsubList有兩條最佳實踐:

          5.1 拷貝到新的ArrayList

          ArrayList myArrayList = new ArrayList();
          ArrayList part1 = new ArrayList(myArrayList.subList(025));
          ArrayList part2 = new ArrayList(myArrayList.subList(2651));

          5.2 使用lambda表達式

          dataList.stream().skip(5).limit(10).collect(Collectors.toList());
          dataList.stream().skip(30).limit(10).collect(Collectors.toList());


          我的新書《深入理解Java核心技術(shù)》已經(jīng)上市了,上市后一直蟬聯(lián)京東暢銷榜中,目前正在6折優(yōu)惠中,想要入手的朋友千萬不要錯過哦~長按二維碼即可購買~


          長按掃碼享受6折優(yōu)惠


          往期推薦

          3000幀動畫圖解MySQL為什么需要binlog、redo log和undo log


          知乎熱議:月薪 2~3W 的碼農(nóng),怎樣度過一天?


          從實現(xiàn)原理講,Nacos 為什么這么強




          有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號


          好文章,我在看??

          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午色婷婷国产无码 | 成人性生活视频 | 欧洲在线,中文字幕 | 夜夜爽久久精品91 | av中文字 |