Java-pdf无限压缩方案-优化内存问题

1 篇文章 0 订阅
1 篇文章 0 订阅

背景

因项目需求,项目中需要提供pdf压缩功能。将某一页压缩至1M大小。
场景的Java的pdf处理方案就是itext pdfbox 以及 apose

方案一:itext压缩(不推荐)

代码
 /**
   * @param src  源文件
   * @param dest 目标文件
   * @throws IOException
   * @throws DocumentException
   */
  public static void compressPdf(String src, String dest, float factor)
      throws PdfCompressException {
    log.info("use radio {} compress file:{}>>>{}", factor, src, dest);
    // 读取pdf文件
    PdfReader reader = null;
    PdfStamper stamper = null;
    ByteArrayOutputStream imgBytes = null;
    try {
      reader = new PdfReader(src);
      int n = reader.getXrefSize();
      PdfObject object;
      PRStream stream;
      // Look for image and manipulate image stream
      for (int i = 0; i < n; i++) {
        object = reader.getPdfObject(i);
        if (object == null || !object.isStream()) {
          continue;
        }
        stream = (PRStream) object;
        PdfObject pdfSubByte = stream.get(PdfName.SUBTYPE);
        if (pdfSubByte != null && pdfSubByte.toString().equals(PdfName.IMAGE.toString())) {
          PdfImageObject image = new PdfImageObject(stream);
          BufferedImage bi = image.getBufferedImage();
          if (bi == null) {
            continue;
          }
          int width = bi.getWidth();
          int height = bi.getHeight();
          AffineTransform at = AffineTransform.getScaleInstance(1, 1);
          if ((int) (width * factor) > 0 && (int) (bi.getHeight() * factor) > 0) {
            width = (int) (width * factor);
            height = (int) (bi.getHeight() * factor);
            at = AffineTransform.getScaleInstance(factor, factor);
          }
          BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
          Graphics2D g = img.createGraphics();
          g.drawRenderedImage(bi, at);
          imgBytes = new ByteArrayOutputStream();
          ImageIO.write(img, "JPG", imgBytes);
          stream.clear();
          stream.setData(imgBytes.toByteArray(), false, PRStream.BEST_COMPRESSION);
          stream.put(PdfName.TYPE, PdfName.XOBJECT);
          stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
          stream.put(PdfName.FILTER, PdfName.DCTDECODE);
          stream.put(PdfName.WIDTH, new PdfNumber(width));
          stream.put(PdfName.HEIGHT, new PdfNumber(height));
          stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
          stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
        }
      }
      stamper = new PdfStamper(reader, new FileOutputStream(dest));
    } catch (Exception e) {
      log.error("pdf compress error:{}>>>{}", src, dest);
      log.error("pdf compress error:", e);
      throw new PdfCompressException(e.getMessage());
    } finally {
      if (imgBytes != null) {
        try {
          imgBytes.close();
        } catch (IOException e) {
          log.error("imgBytes close failed when compress pdf:", e);
        }
      }
      if (stamper != null) {
        try {
          stamper.close();
        } catch (Exception e) {
          log.error("stamper close failed when compress pdf:", e);
        }
      }
      if (reader != null) {
        reader.close();
      }
    }
  }
方案描述
	提供一个压缩方法,先尝试 1倍压缩判断是否小于1M,然后0.9,0.8...0.1,直至factor<=0.1或者压缩后文件小于等于1M才停止压缩。
方案问题
该方案相当于至针对pdf当中的图片进行压缩,之前遇见一个10M的非图片pdf,就是一个表格,但是无论如何都压不下来。
后面采用wps和apose以及在线的pdf压缩工具同样处理不了。
压缩效率低,内存消耗巨大。

方案二:pdfbox方案(强烈不推荐)

代码
  public static void image2Pdf(String inputFile, String pdfFile) throws Image2PdfException {
    log.info("convert image 2 pdf :{}>>>{}", inputFile, pdfFile);
    Document doc = null;
    ByteArrayOutputStream outStream = null;
    PdfWriter pdfWriter = null;
    FileInputStream fi = null;
    try {
      File file = new File(inputFile);
      doc = new Document(PageSize.A4, 20, 20, 20, 20);
      pdfWriter = PdfWriter.getInstance(doc, new FileOutputStream(pdfFile));
      doc.open();
      doc.newPage();
      Image image;
      if (file.getName().toLowerCase().endsWith("jpg") || file.getName().toLowerCase()
          .endsWith("jpeg")) {
        java.awt.Image awtImage = Toolkit.getDefaultToolkit().createImage(file.getAbsolutePath());
        image = Image.getInstance(awtImage, null);
      } else {
        image = Image.getInstance(file.getAbsolutePath());
      }
      float height = image.getHeight();
      float width = image.getWidth();
      if (width > height) {
        fi = new FileInputStream(file);
        BufferedImage src = ImageIO.read(fi);
        BufferedImage des1 = RotateImage.rotate(src, 90);
        String type = file.getName().substring(file.getName().lastIndexOf(".") + 1).toLowerCase();
        outStream = new ByteArrayOutputStream();
        ImageIO.write(des1, type, outStream);
        image = Image.getInstance(outStream.toByteArray());
        height = image.getHeight();
        width = image.getWidth();
      }
      int percent = getPercent(height, width);
      image.setAlignment(Image.MIDDLE);
      image.scalePercent(percent);
      float x = (PageSize.A4.getWidth() - image.getScaledWidth()) / 2;
      float y = (PageSize.A4.getHeight() - image.getScaledHeight()) / 2;
      image.setAbsolutePosition(x, y);
      doc.add(image);
    } catch (Exception e) {
      log.error("image 2 pdf failed:{}>>>{}", inputFile, pdfFile);
      log.error("exception info:", e);
      throw new Image2PdfException(e.getMessage());
    } finally {
      if (doc != null) {
        try {
          doc.close();
        } catch (Exception e) {
          log.info("空文档:", e);
        }
      }
      if (pdfWriter != null) {
        pdfWriter.close();
      }
      if (outStream != null) {
        try {
          outStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (fi != null) {
        try {
          fi.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }

  /**
   * 等比压缩,获取压缩百分比
   *
   * @param height 图片的高度
   * @param weight 图片的宽度
   * @return 压缩百分比
   */
  private static int getPercent(float height, float weight) {
    float percent = 0.0F;
    if (height > weight) {
      percent = (PageSize.A4.getHeight() - 120) / height * 100;
    } else {
      percent = (PageSize.A4.getWidth() - 120) / weight * 100;
    }
    return Math.round(percent);
  }

  public static void pdf2ImagePdf(String source, String targetPdf, int dpi)
      throws Pdf2ImageException, PdfSplitException, Image2PdfException {
    String imagePath = source.substring(0, source.lastIndexOf("."));
    File imageDir = YhPdfUtil.pdf2Images(source, imagePath, dpi);
    File[] files = imageDir.listFiles();
    if (files == null || files.length == 0) {
      throw new Pdf2ImageException("no image found,may pdf 2 image failed");
    } else {
      if (files.length == 1) {
        log.info("pdf just one img ,just convert");
        YhPdfUtil.image2Pdf(files[0].getAbsolutePath(), targetPdf);
      } else {
        log.info("so much images,convert every img and merge all...");
        String tmpPdfDir =
            source.replace("\\", "/").substring(0, source.lastIndexOf(".")) + "-pdf-" + System
                .currentTimeMillis() + "/";
        File fpd = new File(tmpPdfDir);
        if (!fpd.exists()) {
          fpd.mkdirs();
        }
        for (int k = 0; k < files.length; k++) {
          String fn =
              files[k].getName().substring(0, files[k].getName().lastIndexOf(".")) + k + ".pdf";
          String tmpPdf = tmpPdfDir + fn;
          YhPdfUtil.image2Pdf(files[k].getAbsolutePath(), tmpPdf);
        }
        File[] tps = fpd.listFiles();
        if (tps == null || tps.length == 0) {
          throw new Image2PdfException("no pdf found,may image 2 pdf failed");
        } else {
          List<String> tst = new ArrayList<>();
          for (int l = 0; l < tps.length; l++) {
            tst.add(tps[l].getAbsolutePath());
          }
          tst.sort(Comparator.comparing(t -> t));
          YhPdfUtil.mergePdf(tst, targetPdf);
          try {
            FileUtils.deleteDirectory(imageDir);
            FileUtils.deleteDirectory(fpd);
          } catch (IOException e) {
            log.error("pdf转纯图pdf后,删除临时文件失败:", e);
          }
        }
      }
    }
  }

  public static void pdf2ImagePdfWithMax(String source, String targetPdf, long size)
      throws Pdf2ImageException, PdfSplitException, Image2PdfException, IOException {
    int dpi;
    File sourceFile = new File(source);
    if (sourceFile.length() <= size) {
      log.info("sourceFile's length:{}>size:{},just copy", sourceFile.length(), size);
      FileUtils.copyFile(sourceFile, new File(targetPdf));
    } else {
      long c = size * 1000 / sourceFile.length();
      c = c > 1000 ? 1000 : c;
      for (dpi = Integer.parseInt(String.valueOf(c)); dpi > 1; dpi = dpi / 2) {
        pdf2ImagePdf(source, targetPdf, dpi);
        File file = new File(targetPdf);
        if (file.length() > size) {
          continue;
        } else {
          break;
        }
      }
    }
  }
    /**
   * 合并pdf
   *
   * @param fileList   本地文件列表 ["D:/opt/aaa.pdf","D:/opt/bbb.pdf"]
   * @param newPdfPath 合并文件的保存路径 "D:/opt/ccc.pdf"
   * @return boolean
   * @throws
   * @version V1.0.0
   * @date 2021/11/4 10:00
   */
  public static boolean mergePdf(List<String> fileList, String newPdfPath) {
    Document document = null;
    FileOutputStream fo = null;
    PdfCopy copy = null;
    PdfReader rr = null;
    try {
      fo = new FileOutputStream(newPdfPath);
      rr = new PdfReader(fileList.get(0));
      document = new Document(rr.getPageSize(1));
      copy = new PdfCopy(document, fo);
      copy.setFullCompression();
      document.open();
      for (int i = 0; i < fileList.size(); i++) {
        PdfReader reader = new PdfReader(fileList.get(i));
        try {
          int n = reader.getNumberOfPages();
          for (int j = 1; j <= n; j++) {
            document.newPage();
            PdfImportedPage page = copy.getImportedPage(reader, j);
            copy.addPage(page);
          }
        } finally {
          reader.close();
        }
      }
      return true;
    } catch (IOException | DocumentException e) {
      log.error("pdf合并失败:", e);
      return false;
    } finally {
      if (rr != null) {
        rr.close();
      }
      if (copy != null) {
        copy.close();
      }
      if (document != null) {
        document.close();
      }
      if (fo != null) {
        try {
          fo.close();
        } catch (Exception e) {
          log.error("Io关闭异常:", e);
        }
      }
    }
  }
方案描述
该方案是通过pdfbox按某个dpi将pdf拆分成图片,然后在将拆出来的pdf通过itext合成为pdf.如果合并的pdf大于体积,则按更小的dpi再来一遍。
问题
其实该方案流程上没有问题,但是在性能上会存在非常大的漏洞及消耗-内存泄漏问题。pdfbox会缓存大量的pdf元数据(字体,字典)等信息
且无法被GC,或者说,在Gc之前,Java服务进程已经被服务器杀死了。刚开始还以为是版本问题,我看最新版本对内存做了优化,但是在升级
最新版本之后,内存增长虽然好了些,但是在有限的内存下。依旧无法会因内存泄漏问题导致服务宕机。

方案三:采用apose将pdf转为图片(不推荐)

代码
  public static File pdf2Images(String pdfPath, String imageDirPath, int dpi)
      throws Pdf2ImageException, PdfSplitException {
    imageDirPath = imageDirPath.replace("\\", "/");
    if (!imageDirPath.endsWith("/")) {
      imageDirPath = imageDirPath + "/";
    }
    File file = new File(pdfPath);
    File imageDir = new File(imageDirPath);
    if (!imageDir.exists()) {
      imageDir.mkdirs();
    }
    com.aspose.pdf.Document pdDocument;
    try {
      pdDocument = new com.aspose.pdf.Document(pdfPath);
      FileOutputStream fileOutputStream = null;
      int pages = pdDocument.getPages().size();
      if (pages == 1) {
        try {
          Resolution resolution = new Resolution(dpi);
          JpegDevice jpegDevice = new JpegDevice(resolution);
          String tmpImage = imageDirPath + file.getName().substring(0, file.getName().lastIndexOf(".")) +
              "-" + System.currentTimeMillis() + ".png";
          log.info("pdf just one page,use dpi {} pdf file 2 image:{}>>>{}", dpi, pdfPath, tmpImage);
          fileOutputStream = new FileOutputStream(new File(tmpImage));
          jpegDevice.process(pdDocument.getPages().get_Item(1), fileOutputStream);
          fileOutputStream.flush();
        } finally {
          pdDocument.close();
          if (fileOutputStream != null) {
            fileOutputStream.close();
          }
        }
      } else {
        log.info("the pdf so many pages, split every page before convert...");
        String tmpPdfPath =
            pdfPath.replace("\\", "/").substring(0, pdfPath.lastIndexOf(".")) + "-pdf-" + System
                .currentTimeMillis() + "/";
        File tmpPdfDir = splitPerPagePdf(pdfPath, tmpPdfPath);
        File[] files = tmpPdfDir.listFiles();
        if (files == null || files.length == 0) {
          throw new PdfSplitException("pdf split failed, no result fle found");
        } else {
          List<File> pdfs = new ArrayList<File>(Arrays.asList(files));
          pdfs.sort(Comparator.comparing(file1 -> file.getName()));
          for (int k = 0; k < pdfs.size(); k++) {
            pdf2Images(pdfs.get(k).getAbsolutePath(), imageDirPath, dpi);
          }
          FileUtils.deleteDirectory(new File(tmpPdfPath));
        }
      }
      return imageDir;
    } catch (IOException e) {
      log.error("pdf转图片失败:{}", e);
      throw new Pdf2ImageException(pdfPath);
    }
  }

public static void image2Pdf(String inputFile, String pdfFile) throws Image2PdfException {
    log.info("convert image 2 pdf :{}>>>{}", inputFile, pdfFile);
    Document doc = null;
    ByteArrayOutputStream outStream = null;
    PdfWriter pdfWriter = null;
    FileInputStream fi = null;
    try {
      File file = new File(inputFile);
      doc = new Document(PageSize.A4, 20, 20, 20, 20);
      pdfWriter = PdfWriter.getInstance(doc, new FileOutputStream(pdfFile));
      doc.open();
      doc.newPage();
      Image image;
      if (file.getName().toLowerCase().endsWith("jpg") || file.getName().toLowerCase()
          .endsWith("jpeg")) {
        java.awt.Image awtImage = Toolkit.getDefaultToolkit().createImage(file.getAbsolutePath());
        image = Image.getInstance(awtImage, null);
      } else {
        image = Image.getInstance(file.getAbsolutePath());
      }
      float height = image.getHeight();
      float width = image.getWidth();
      if (width > height) {
        fi = new FileInputStream(file);
        BufferedImage src = ImageIO.read(fi);
        BufferedImage des1 = RotateImage.rotate(src, 90);
        String type = file.getName().substring(file.getName().lastIndexOf(".") + 1).toLowerCase();
        outStream = new ByteArrayOutputStream();
        ImageIO.write(des1, type, outStream);
        image = Image.getInstance(outStream.toByteArray());
        height = image.getHeight();
        width = image.getWidth();
      }
      int percent = getPercent(height, width);
      image.setAlignment(Image.MIDDLE);
      image.scalePercent(percent);
      float x = (PageSize.A4.getWidth() - image.getScaledWidth()) / 2;
      float y = (PageSize.A4.getHeight() - image.getScaledHeight()) / 2;
      image.setAbsolutePosition(x, y);
      doc.add(image);
    } catch (Exception e) {
      log.error("image 2 pdf failed:{}>>>{}", inputFile, pdfFile);
      log.error("exception info:", e);
      throw new Image2PdfException(e.getMessage());
    } finally {
      if (doc != null) {
        try {
          doc.close();
        } catch (Exception e) {
          log.info("空文档:", e);
        }
      }
      if (pdfWriter != null) {
        pdfWriter.close();
      }
      if (outStream != null) {
        try {
          outStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (fi != null) {
        try {
          fi.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

 /**
   * 合并pdf
   *
   * @param fileList   本地文件列表 ["D:/opt/aaa.pdf","D:/opt/bbb.pdf"]
   * @param newPdfPath 合并文件的保存路径 "D:/opt/ccc.pdf"
   * @return boolean
   * @throws
   * @version V1.0.0
   * @date 2021/11/4 10:00
   */
  public static boolean mergePdf(List<String> fileList, String newPdfPath) {
    Document document = null;
    FileOutputStream fo = null;
    PdfCopy copy = null;
    PdfReader rr = null;
    try {
      fo = new FileOutputStream(newPdfPath);
      rr = new PdfReader(fileList.get(0));
      document = new Document(rr.getPageSize(1));
      copy = new PdfCopy(document, fo);
      copy.setFullCompression();
      document.open();
      for (int i = 0; i < fileList.size(); i++) {
        PdfReader reader = new PdfReader(fileList.get(i));
        try {
          int n = reader.getNumberOfPages();
          for (int j = 1; j <= n; j++) {
            document.newPage();
            PdfImportedPage page = copy.getImportedPage(reader, j);
            copy.addPage(page);
          }
        } finally {
          reader.close();
        }
      }
      return true;
    } catch (IOException | DocumentException e) {
      log.error("pdf合并失败:", e);
      return false;
    } finally {
      if (rr != null) {
        rr.close();
      }
      if (copy != null) {
        copy.close();
      }
      if (document != null) {
        document.close();
      }
      if (fo != null) {
        try {
          fo.close();
        } catch (Exception e) {
          log.error("Io关闭异常:", e);
        }
      }
    }
  }

    public static void compress(String source, String target,int qa) {
        new com.aspose.pdf.Document doc = new new com.aspose.pdf.Document(source);
        //设置压缩属性
        OptimizationOptions opt = new OptimizationOptions();
        //删除PDF不必要的对象
        opt.setRemoveUnusedObjects(true);
        //链接重复流
        opt.setLinkDuplcateStreams(false);
        //删除未使用的流
        opt.setRemoveUnusedStreams(false);
        //删除不必要的字体
        opt.setUnembedFonts(true);
        //压缩PDF中的图片
        opt.setCompressImages(true);
        //图片压缩比, 0 到100可选,越低压缩比越大
        opt.setImageQuality(qa);
        doc.optimizeResources(opt);
        //优化web的PDF文档
        doc.optimize();
        doc.save(target);
    }

方案描述
流程是 pdf转图片->图片转pdf->合并->循环压缩至指定大小,该方案解决了pdfbox内存泄漏问题
问题
虽然解决的pdfbox内存泄漏问题,但是内存占用依旧非常严重。几个文件转换,内存飙升4个G。对服务而言,
还是比较危险的,在内存宽裕的情况下,采用这套方案可以,但是在内存禁止的情况下,不建议如此去做。

方案四:ghostscript+ImageMagick(推荐,最终方案)

代码:

 private static String command = "";
 private static final String cmdExpress = "%s -density 150 -quality %s -limit memory 10mb -limit map 10mb %s %s";

 private static String gsCommand = "";

 static {
   String os = System.getProperty("os.name");
   if (os != null && os.toLowerCase().contains("window")) {
     command = "magick";
     gsCommand = "gswin32c";
   } else if (os != null && os.toLowerCase().contains("ubuntu")) {
     command = "sudo convert";
     gsCommand = "sudo gs";
   } else {
     command = "convert";
     gsCommand = "gs";
   }
 }
  public static void pdf2ImagePdf(String pdfPath, String targetPdf, int qa)
     throws Pdf2ImageException, Image2PdfException {
   String imageDirPath = pdfPath.substring(0, pdfPath.lastIndexOf(".")).replace("\\", "/");
   log.info("pdf2image:{}>>>{}", pdfPath, imageDirPath);
   pdfPath = pdfPath.replace("\\", "/");
   File pdf = new File(pdfPath);
   String pdfName = pdf.getName();
   File imageDir = new File(imageDirPath);
   if (!imageDir.exists()) {
     imageDir.mkdirs();
   }
   String imageName = pdfName.substring(0, pdfName.lastIndexOf(".")) + ".png";
   String imageFilePath = imageDirPath + "/" + imageName;
   imageFilePath = imageFilePath.replace("\\", "/");
   String pdf2ImgCmd = String.format(cmdExpress, command, qa, pdfPath, imageFilePath);
   log.info("pdf2ImgCmd:{}", pdf2ImgCmd);
   try {
     Process pro = Runtime.getRuntime().exec(pdf2ImgCmd);
     pro.waitFor(5, TimeUnit.MINUTES);
   } catch (Exception e) {
     log.error("pdf转图片你失败:", e);
     throw new Pdf2ImageException(e.getMessage());
   }
   String inputFile = imageDirPath + "/*.png";
   String cmdEx = "%s  -density 150 -quality %s -limit memory 10mb -limit map 10mb %s %s";
   String img2PdfCmd = String.format(cmdEx, command, qa, inputFile, targetPdf);
   log.info("convert2PdfCmd:{}", img2PdfCmd);
   try {
     Process pro = Runtime.getRuntime().exec(img2PdfCmd);
     pro.waitFor(3, TimeUnit.MINUTES);
   } catch (Exception e) {
     log.error("pdf转图片你失败:", e);
     throw new Image2PdfException(e.getMessage());
   }
   FileUtil.del(imageDirPath);
 }
 /**
  * @param src  源文件
  * @param dest 目标文件
  * @throws IOException
  * @throws DocumentException
  */
 public static void compressPdf(String src, String dest, int qa) throws IOException {
   String compressCommand = "%s -dQUIET -dNOSAFER -r%s -sDEVICE=pdfwrite -dCompatibilityLevel=1.3 -dPDFSETTINGS=/screen  -dNOPAUSE -dBATCH -dColorImageResolution=150 -sOutputFile=%s %s";
   src = src.replace("\\", "/");
   dest = dest.replace("\\", "/");
   String cmd = String.format(compressCommand, gsCommand, qa, dest, src);
   log.info(cmd);
   try {
     Process process = Runtime.getRuntime().exec(cmd);
     process.waitFor(3, TimeUnit.MINUTES);
   } catch (Exception e) {
     log.info("文档转换失败:", e);
     throw new PdfCompressException(e.getMessage());
   }
 }
   public static void pdf2ImagePdfWithMax(String source, String targetPdf, long size)
     throws IOException {
   File sourceFile = new File(source);
   if (sourceFile.length() <= size) {
     log.info("sourceFile's length:{}>size:{},just copy", sourceFile.length(), size);
     FileUtils.copyFile(sourceFile, new File(targetPdf));
   } else {
     String targetTmpPdf = targetPdf.substring(0, targetPdf.lastIndexOf(".")) + "-tmp" + ".pdf";
     try {
       FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
         pdf2ImagePdf(source, targetTmpPdf, 96);
         compressPdf2FixLength(targetTmpPdf, targetPdf, size);
         return true;
       });
       YhConstant.ITEM_POOL.submit(futureTask);
       try {
         futureTask.get(5, TimeUnit.MINUTES);
       } catch (Exception e) {
         throw new PdfCompressException("压缩失败:" + e.getMessage());
       }
     } finally {
       File file = new File(targetTmpPdf);
       if (file.exists()) {
         file.delete();
       }
     }
   }
 }
方案描述:
流程依旧是 pdf转图片->图片合并成pdf->pdf压缩
只是通过系统层ghostscript+ImageMagick来实现
cenos:
	yum install -y ghostscript ImageMagick
	vi /etc/ImageMagick-6/policy.xml
	将 <policy domain="module"这一行取消注释,并改为:
	 <policy domain="module" rights="read|write" pattern="{PS,PDF,XPS}" />
unbuntu:
	apt install -y ghostscript ImageMagick
	同样需要修改etc/magick安装目录下的policy.xml文件
windows:
	自行安装且添加环境变量。
问题
ghostscript压缩pdf稍微费些内存,但是比起java要好好多。建议在ghostscript压缩加入线程池进行并发控制,降低内存爆掉的风险。
总结
Java就是TMD费内存,JVM优化其实也就那样,5家客户同事在用的saas系统,我只能xms xmx服务器剩余的4个G,再怎么优化也是醉了。
  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
000000_【课程介绍 —— 写在前面的话】_Java学习概述笔记.pdf 010101_【第1章:JAVA概述及开发环境搭建】_JAVA发展概述笔记.pdf 010102_【第1章:JAVA概述及开发环境搭建】_Java开发环境搭建笔记.pdf 010201_【第2章:简单Java程序】_简单Java程序笔记.pdf 010301_【第3章:Java基础程序设计】_Java数据类型笔记.pdf 010302_【第3章:Java基础程序设计】_运算符、表达式与语句笔记.pdf 010303_【第3章:Java基础程序设计】_判断与循环语句笔记.pdf 010401_【第4章:数组与方法】_数组的定义及使用笔记.pdf 010402_【第4章:数组与方法】_方法的声明及使用笔记.pdf 010403_【第4章:数组与方法】_数组的引用传递笔记.pdf 010404_【第4章:数组与方法】_Java新特性对数组的支持笔记.pdf 020501_【第5章:面向对象基础】_面向对象、类与对象的定义笔记.pdf 020502_【第5章:面向对象基础】_类与对象的进一步研究笔记.pdf 020503_【第5章:面向对象基础】_封装性笔记.pdf 020504_【第5章:面向对象基础】_构造方法与匿名对象笔记.pdf 020505_〖第5章:面向对象基础〗_实例讲解—类设计分析(学生类)笔记.pdf 020506_【第5章:面向对象基础】_String类笔记.pdf 020507_【第5章:面向对象基础】_String类的常用方法.pdf 020508_【第5章:面向对象基础】_引用传递及其应用笔记.pdf 020509_【第5章:面向对象基础】_this关键字笔记.pdf 020510_【第5章:面向对象基础】_static关键字笔记.pdf 020511_【第5章:面向对象基础】_理解main方法笔记.pdf 020512_【第5章:面向对象基础】_代码块笔记.pdf 020513_【第5章:面向对象基础】_构造方法私有化笔记.pdf 020514_【第5章:面向对象基础】_对象数组笔记.pdf 020515_【第5章:面向对象基础】_内部类笔记.pdf 020516_〖第5章:面向对象基础〗_实例讲解—系统登陆笔记.pdf 020517_〖第5章:面向对象基础〗_实例讲解—单向链表(1)笔记.pdf 020518_〖第5章:面向对象基础〗_实例讲解—单向链表(2)笔记.pdf 020601_【第6章:面向对象(高级)】_继承的基本实现笔记.pdf 020602_【第6章:面向对象(高级)】_继承的进一步研究笔记.pdf 020603_〖第6章:面向对象(高级)〗_范例:继承的应用笔记.pdf 020604_【第6章:面向对象(高级)】_final关键字笔记.pdf 020605_【第6章:面向对象(高级)】_抽象类的基本概念笔记.pdf 020606_【第6章:面向对象(高级)】_接口的基本概念笔记.pdf 020607_【第6章:面向对象(高级)】_对象的多态性笔记.pdf 020608_【第6章:面向对象(高级)】_instanceof关键字笔记.pdf 020609_【第6章:面向对象(高级)】_抽象类与接口的应用笔记.pdf 020610_〖第6章:面向对象(高级)〗_实例分析:宠物商店笔记.pdf 020611_【第6章:面向对象(高级)】_Object类笔记.pdf 020612_【第6章:面向对象(高级)】_包装类笔记.pdf 020613_【第6章:面向对象(高级)】_匿名内部类笔记.pdf 020701_【第7章:异常的基本概念】_异常的基本概念笔记.pdf 020702_【第7章:异常的基本概念】_异常的其他概念笔记.pdf 020801_【第8章:包及访问控制权限】_包的定义及导入笔记.pdf 020802_【第8章:包及访问控制权限】_访问控制权限及命名规范笔记.pdf 030901_【第9章:多线程】_认识多线程笔记.pdf 030902_【第9章:多线程】_线程常用操作方法笔记.pdf 030903_〖第9章:多线程〗_线程操作范例笔记.pdf 030904_【第9章:多线程】_同步与死锁笔记.pdf 030905_【第9章:多线程】_线程操作案例——生产者和消费者笔记.pdf 030906_【第9章:多线程】_线程生命周期笔记.pdf 031001_【第10章:泛型】_泛型入门笔记.pdf 031002_【第10章:泛型】_通配符笔记.pdf 031003_【第10章:泛型】_泛型的其他应用笔记.pdf 031004_〖第10章:泛型〗_实例讲解—泛型操作范例笔记.pdf 031101_【第11章:Java常用类库】_StringBuffer笔记.pdf 031102_【第11章:Java常用类库】_Runtime类笔记.pdf 031103_【第11章:Java常用类库】_国际化程序笔记.pdf 031104_【第11章:Java常用类库】_System类笔记.pdf 031105_【第11章:Java常用类库】_日期操作类(Date、Calendar)笔记.pdf 031106_【第11章:Java常用类库】_日期操作类(DateFormat、SimpleDateFormat)笔记.pdf 031107_〖第11章:Java常用类库〗_实例操作:取得当前日期笔记.pdf 031108_【第11章:Java常用类库】_Math与Random类笔记.pdf 031109_【第11章:Java常用类库】_NumberFormat笔记.pdf 031110_【第11章:Java常用类库】_大数操作(BigIntger、BigDecimal)笔记.pdf 031111_【第11章:Java常用类库】_对象克隆技术笔记.pdf 031112_【第11章:Java常用类库】_Arrays笔记.pdf 031113_【第11章:Java常用类库】_比较器(Comparable、Comparator)笔记.pdf 031114_【第11章:Java常用类库】_观察者设计模式笔记.pdf 031115_【第11章:Java常用类库】_正则表达式笔记.pdf 031116_【第11章:Java常用类库】_定时调度笔记.pdf 031201_【第12章:JAVA IO】_File类笔记.pdf 031202_【第12章:JAVA IO】_RandomAccessFile笔记.pdf 031203_【第12章:JAVA IO】_字节流与字符流笔记.pdf 031204_【第12章:JAVA IO】_字节-字符转换流笔记.pdf 031205_【第12章:JAVA IO】_内存操作流笔记.pdf 031206_【第12章:JAVA IO】_管道流笔记.pdf 031207_【第12章:JAVA IO】_打印流笔记.pdf 031208_【第12章:JAVA IO】_System类对IO的支持笔记.pdf 031209_【第12章:JAVA IO】_BufferedReader笔记.pdf 031210_〖第12章:JAVA IO〗_IO操作实例笔记.pdf 031211_【第12章:JAVA IO】_Scanner笔记.pdf 031212_【第12章:JAVA IO】_数据操作流笔记.pdf 031213_【第12章:JAVA IO】_合并流笔记.pdf 031214_【第12章:JAVA IO】_压缩流笔记.pdf 031215_【第12章:JAVA IO】_回退流笔记.pdf 031216_【第12章:JAVA IO】_字符编码笔记.pdf 031217_【第12章:JAVA IO】_对象序列化笔记.pdf 031218_〖第12章:JAVA IO〗_实例操作—单人信息管理程序笔记.pdf 031219_〖第12章:JAVA IO〗_实例操作:投票程序笔记.pdf 031301_【第13章:Java类集】_认识类集、Collection接口笔记.pdf 031302_【第13章:Java类集】_List接口笔记.pdf 031303_【第13章:Java类集】_LinkedList类笔记.pdf 031304_【第13章:Java类集】_Set接口笔记.pdf 031305_【第13章:Java类集】_排序及重复元素说明笔记.pdf 031306_【第13章:Java类集】_SortedSet接口笔记.pdf 031307_【第13章:Java类集】_Iterator接口笔记.pdf 031308_【第13章:Java类集】_ListIterator接口笔记.pdf 031309_【第13章:Java类集】_foreach及Enumeration接口笔记.pdf 031310_【第13章:Java类集】_Map接口笔记.pdf 031311_【第13章:Java类集】_Map接口使用的注意事项笔记.pdf 031312_【第13章:Java类集】_IdentityHashMap类笔记.pdf 031313_【第13章:Java类集】_SortedMap类笔记.pdf 031314_【第13章:Java类集】_集合工具类:Collections笔记.pdf 031315_【第13章:Java类集】_Stack类笔记.pdf 031316_【第13章:Java类集】_属性类:Properties笔记.pdf 031317_〖第13章:Java类集〗_范例讲解:一对多关系笔记.pdf 031318_〖第13章:Java类集〗_范例讲解:多对多关系笔记.pdf 031401_【第14章:枚举】_枚举的作用笔记.pdf 031402_【第14章:枚举】_Enum笔记.pdf 031403_【第14章:枚举】_类集对Enum的支持笔记.pdf 031404_【第14章:枚举】_枚举的其他应用笔记.pdf 031501_【第15章:Java反射机制】_认识Class类笔记.pdf 031502_【第15章:Java反射机制】_Class类的使用笔记.pdf 031503_【第15章:Java反射机制】_反射应用——取得类的结构笔记.pdf 031504_【第15章:Java反射机制】_Java反射机制的深入研究笔记.pdf 031505_【第15章:Java反射机制】_动态代理笔记.pdf 031506_【第15章:Java反射机制】_工厂设计模式笔记.pdf 031601_【第16章:Annotation】_系统内建Annotation笔记.pdf 031602_【第16章:Annotation】_自定义Annotation笔记.pdf 031603_【第16章:Annotation】_反射与Annotation笔记.pdf 031604_【第16章:Annotation】_深入Annotation笔记.pdf 031701_【第17章:Java数据库编程】_JDBC概述笔记.pdf 031702_【第17章:Java数据库编程】_MySQL数据库笔记.pdf 031703_【第17章:Java数据库编程】_SQL语法基础笔记.pdf 031704_【第17章:Java数据库编程】_JDBC操作步骤及数据库连接操作笔记.pdf 031705_【第17章:Java数据库编程】_执行数据库更新操作笔记.pdf 031706_【第17章:Java数据库编程】_ResultSet接口笔记.pdf 031707_【第17章:Java数据库编程】_PreparedStatement接口笔记.pdf 031708_【第17章:Java数据库编程】_处理大数据对象(1)—处理CLOB数据笔记.pdf 031709_【第17章:Java数据库编程】_处理大数据对象(2)—处理BLOB数据笔记.pdf 031710_【第17章:Java数据库编程】_CallableStatement接口笔记.pdf 031711_【第17章:Java数据库编程】_JDBC 2.0操作笔记.pdf 031712_【第17章:Java数据库编程】_事务处理笔记.pdf 031713_【第17章:Java数据库编程】_使用元数据分析数据库笔记.pdf 031714_【第17章:Java数据库编程】_使用JDBC连接Oracle笔记.pdf 031801_【第18章:图形界面】_AWT、Swing简介笔记.pdf 031802_【第18章:图形界面】_基本容器:JFrame笔记.pdf 031803_【第18章:图形界面】_标签组件:JLabel笔记.pdf 031804_【第18章:图形界面】_按钮组件:JButton笔记.pdf 031805_【第18章:图形界面】_布局管理器笔记.pdf 031806_【第18章:图形界面】_其他容器笔记.pdf 031807_【第18章:图形界面】_不弹起的按钮组件:JToggleButton笔记.pdf 031808_【第18章:图形界面】_文本组件:JTextComponent笔记.pdf 031809_【第18章:图形界面】_事件处理笔记.pdf 031810_【第18章:图形界面】_单选钮:JRadioButton笔记.pdf 031811_【第18章:图形界面】_复选框:JCheckBox笔记.pdf 031812_【第18章:图形界面】_列表框:JList笔记.pdf 031812_【第18章:图形界面】_下拉列表框:JComboBox笔记.pdf 031813_【第18章:图形界面】_菜单组件笔记.pdf 031814_【第18章:图形界面】_文件选择框笔记.pdf 031815_【第18章:图形界面】_表格笔记.pdf 031901_【第19章:Java网络编程】_IP(Internet Protocol)与InetAddress笔记.pdf 031902_【第19章:Java网络编程】_URL与URLConnection笔记.pdf 031903_【第19章:Java网络编程】_URLEncoder与URLDecoder笔记.pdf 031904_【第19章:Java网络编程】_TCP程序设计笔记.pdf 031905_【第19章:Java网络编程】_UDP程序设计笔记.pdf 032001_【第20章:Java新IO】_缓冲区与Buffer笔记.pdf 032002_【第20章:Java新IO】_通道(Channel)笔记.pdf 032003_【第20章:Java新IO】_文件锁笔记.pdf 032004_【第20章:Java新IO】_字符集笔记.pdf 032005_【第20章:Java新IO】_Selector笔记.pdf 042101_【课程讲解】_附录:Eclipse开发工具笔记.pdf 050101_〖开发实例〗_Java开发实例讲解(人员管理)笔记.pdf
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我的孤独与美酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值