1.序

2020/3/17日JDK14正式发版,但是现在大部分公司还是在使用jdk 8。所以我们今天继续聊聊jdk8。

2.jdk8 详解

2.1编程语言

2.1.1Lambda 表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
举个例子

// Java 8之前:
new Thread(new Runnable() {
   
    @Override
    public void run() {
   
    System.out.println("Before Java8");
    }
}).start();

// Java 8
new Thread( () -> System.out.println("In Java8, Lambda expression") ).start();

用() -> {}代码块替代了整个匿名类

2.1.2方法引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。

下面,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

@FunctionalInterface
public interface Supplier<T> {
   
    T get();
}
 
class Car {
   
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
   
        return supplier.get();
    }
 
    public static void collide(final Car car) {
   
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
   
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
   
        System.out.println("Repaired " + this.toString());
    }
}

构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

静态方法引用:它的语法是Class::static_method,实例如下:
cars.forEach( Car::collide );

特定类的任意对象的方法引用:它的语法是Class::method实例如下:
cars.forEach( Car::repair );

特定对象的方法引用:它的语法是instance::method实例如下:
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

2.1.3默认方法

Java 8 新增了接口的默认方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。

为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

语法

2.1.3.1.默认方法语法格式如下:

public interface Vehicle {
   
   default void print(){
   
      System.out.println("我是一辆车!");
   }
}

2.1.3.2.多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Vehicle {
   
   default void print(){
   
      System.out.println("我是一辆车!");
   }
}
 
public interface FourWheeler {
   
   default void print(){
   
      System.out.println("我是一辆四轮车!");
   }
}

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   
   default void print(){
   
      System.out.println("我是一辆四轮汽车!");
   }
}

第二种解决方案可以使用 super 来调用指定接口的默认方法:

public class Car implements Vehicle, FourWheeler {
   
   public void print(){
   
      Vehicle.super.print();
   }
}

2.1.3.3静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

public interface Vehicle {
   
   default void print(){
   
      System.out.println("我是一辆车!");
   }
    // 静态方法
   static void blowHorn(){
   
      System.out.println("按喇叭!!!");
   }
}

2.1.4可重复注解

注解并不是什么新鲜东西了,比如spring中存在大量注解简化我们的配置。但是在JDK8之前,我们是不能使用重复注解的,即某个位置相同注解只能出现一次。

比如我们想编写一个定时任务的注解,使用者可以配置在每天哪一小时触发,而且允许用户配置多个时间。传统做法是:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraditionalAnnoSchedule {
   
    int[] hour() default {
   0};
}


@TraditionalAnnoSchedule(hour = {
   0, 8, 12})
public class Target {
   
    public static void main(String[] args) {
   
        TraditionalAnnoSchedule[] annotations = Target.class.getAnnotationsByType(TraditionalAnnoSchedule.class);
        for (TraditionalAnnoSchedule each : annotations) {
   
            System.out.println(Arrays.toString(each.hour()));
        }
    }
}

上面是比较传统的做法,下面我们使用JDK8的重复注解特性改造下上面的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
   
    Schedule[] value();
}
 
// JDK8新增的@Repeatable
@Repeatable(Schedules.class)
public @interface Schedule {
   
    int hour() default 0;
}

@Schedule(hour = 0)
@Schedule(hour = 8)
@Schedule(hour = 12)
public class Target {
   
    public static void main(String[] args) {
   
        // 推荐的方式
        Schedule[] annotations = Target.class.getAnnotationsByType(Schedule.class);
        for (Schedule each : annotations) {
   
            System.out.println(each.hour());
        }
 
        // 老的方式
        Schedule[] schedules = Target.class.getAnnotation(Schedules.class).value();
        for (Schedule each : schedules) {
   
            System.out.println(each.hour());
        }
 
    }
}

这里有个使用@Repeatable( Schedules.class )的注解类Schedule,Schedules仅仅是Schedule注解的数组,对使用者而言,Target就拥有了两个Schedule注解,而不是1个Schedules注解。同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型。

2.1.5 类型注解

类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),可以在编译的时候检测出runtime error(例如:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error),以提高代码质量。这就是类型注解的作用。
注意:使用Checker Framework可以找到类型注解出现的地方并检查。
例如下面的代码。

import checkers.nullness.quals.*;
public class TestDemo{
   
    void sample() {
   
        @NonNull Object my = new Object();
    }
}

@Encrypted String data
List<@NonNull String> strings
MyGraph = (@Immutable Graph) tmpGraph;

2.1.6方法参数注解

2.2安全

  • 默认启用客户端 TLS 1.2
  • AccessController.doPrivileged 的新变体支持代码断言其权限的子集,而不会阻止完全遍历堆栈来检查其他权限
  • 更强大的基于密码的加密算法
  • JSSE 服务器端支持 SSL/TLS 服务器名称指示 (SNI) 扩展
  • 支持 AEAD 算法:SunJCE 提供程序得到了增强,支持 AES/GCM/NoPadding 密码实现以及 GCM 算法参数。而且 SunJSSE 提供程序也得到了增强,支持基于 AEAD 模式的密码套件。请参阅 Oracle 提供程序文档,JEP 115。
  • 密钥库增强,包括新的域密钥库类型 java.security.DomainLoadStoreParameter, 和为 keytool 实用程序新增的命令选项-importpassword
  • SHA-224 消息摘要
  • 增强了对 NSA Suite B 加密的支持
  • 更好地支持高熵随机数生成
  • 新增了 java.security.cert.PKIXRevocationChecker 类,用于配置 X.509 证书的撤销检查
  • 适用于 Windows 的 64 位 PKCS11
  • Kerberos 5 重放缓存中新增了 rcache 类型
  • 支持 Kerberos 5 协议转换和受限委派
  • 默认禁用 Kerberos 5 弱加密类型
  • 适用于 GSS-API/Kerberos 5 机制的未绑定 SASL
  • 针对多个主机名称的 SASL 服务
  • JNI 桥接至 Mac OS X 上的原生 JGSS
  • SunJSSE 提供程序中支持更强大的临时 DH 密钥
  • JSSE 中支持服务器端加密套件首选项自定义

2.3部署

现在可以使用 URLPermission 允许沙盒小程序和 Java Web Start 应用连接回启动它们的服务器。不再授予 SocketPermission 。
在所有安全级别,主 JAR 文件的 JAR 文件清单中都需要 Permissions 属性。

2.4脚本

Rhino Javascript 引擎已被替换为 Nashorn JavaScript 引擎

2.5 Compact profiles

2.6 lang 包/util 包

  • 并行数组排序
  • 标准编码和解码 Base64
  • 无符号算术支持
/** * 8.新增base64加解密API */
@Test
     public void testBase64(){
   
         final String text = "就是要测试加解密!!abjdkhdkuasu!!@@@@";
         String encoded = Base64.getEncoder()
             .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
         System.out.println("加密后="+ encoded );
          
         final String decoded = new String( 
             Base64.getDecoder().decode( encoded ),
             StandardCharsets.UTF_8 );
         System.out.println( "解密后="+decoded );
     }

2.7 Java DB

JDK 8 包含 Java DB 10.10。

2.8 并发

2.8.1 concurrent包


2.8.2 ConcurrentHashMap

2.8.3 atomic 包

2.8.4 ForkJoinPool

1 ForkJoinPool 不是为了替代 ExecutorService,而是它的补充,在某些应用场景下性能比 ExecutorService 更好。
2 ForkJoinPool 主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数,例如 quick sort 等。
3 ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时,最好配合使用 ManagedBlocker。

2.8.5 StampedLock

StampedLock类,在JDK1.8时引入,是对读写锁ReentrantReadWriteLock的增强,该类提供了一些功能,优化了读锁、写锁的访问, 同时使读写锁之间可以互相转换,更细粒度控制并发。

该类的设计初衷是作为一个内部工具类,用于辅助开发其它线程安全组件,用得好,该类可以提升系统性能,用不好, 容易产生死锁和其它莫名其妙的问题。

先来看下,为什么有了ReentrantReadWriteLock,还要引入StampedLock?

ReentrantReadWriteLock使得多个读线程同时持有读锁(只要写锁未被占用),而写锁是独占的。

但是,读写锁如果使用不当,很容易产生“饥饿”问题:

比如在读线程非常多,写线程很少的情况下,很容易导致写线程“饥饿”,虽然使用“公平”策略可以一定程度上缓解这个问题,但是“公 平”策略是以牺牲系统吞吐量为代价的。

StampedLock的主要特点概括一下,有以下几点:

1.所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功;
2. 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
3. StampedLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
4. StampedLock有三种访问模式:
①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似 请登录后复制
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③Optimistic reading(乐观读模式):这是一种优化的读模式。
5. StampedLock支持读锁和写锁的相互转换 我们知道RRW中,当线程获取到写锁后,可以降级为读锁,但是读锁是不能直接升级为写锁的。 StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。
6. 无论写锁还是读锁,都不支持Conditon等待

我们知道,在ReentrantReadWriteLock中,当读锁被使用时,如果有线程尝试获取写锁,该写线程会阻塞。 但是,在Optimistic reading中,即使读线程获取到了读锁,写线程尝试获取写锁也不会阻塞,这相当于对读模式的优化,但是可 能会导致数据不一致的问题。所以,当使用Optimistic reading获取到读锁时,必须对获取结果进行校验。

2.9 Java Mission Control

2.10 集合

新的java.util.stream包中的类提供了一个 Stream API,支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作,例如顺序或并行的 map-reduce 转换。

针对存在键冲突的 HashMap 的性能改进

//stream
@Test
      public void testStream(){
   
          List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
          System.out.println("求和:"+nums
                  .stream()//转成Stream
                  .filter(team -> team!=null)//过滤
                  .distinct()//去重
                  .mapToInt(num->num*2)//map操作
                  .skip(2)//跳过前2个元素
                  .limit(4)//限制取前4个元素
                  .peek(System.out::println)//流式处理对象函数
                  .sum());//
      }
  //数组并行(parallel)操作
@Test
     public void testParallel(){
   
         long[] arrayOfLong = new long [ 20000 ];        
         //1.给数组随机赋值
         Arrays.parallelSetAll( arrayOfLong, 
             index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
         //2.打印出前10个元素
         Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
             i -> System.out.print( i + " " ) );
         System.out.println();
         //3.数组排序
         Arrays.parallelSort( arrayOfLong );     
         //4.打印排序后的前10个元素
         Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
             i -> System.out.print( i + " " ) );
         System.out.println();
     }

2.11 JavaFX

  • 本版本中实施了新的 Modena 主题。有关更多信息,请参阅 xexperience.com.
  • 新的 SwingNode 类允许开发人员将 Swing 内容嵌入到 JavaFX 应用中。请参阅SwingNode javadoc 和 将 Swing 内容嵌入 JavaFX 应用中。.
  • 新的 UI 控件包括 DatePicker 和 TreeTableView 控件。
  • javafx.print 程序包为 JavaFX Printing API 提供了公共类。有关更多信息,请参阅 javadoc
  • 3D 图形特性现在包括 3D 形状、摄像头、灯光、子场景、材料、挑选和抗锯齿。JavaFX 3D 图形库中新增了 Shape3D (Box, Cylinder, MeshView和 Sphere子类 ), SubScene, Material, PickResult, LightBase (AmbientLight 和 PointLight 子类) , 和 SceneAntialiasing API 类。此版本中的Camera API 类也已更新。请参阅 javafx.scene.shape.Shape3D, javafx.scene.SubScene, javafx.scene.paint.Material, javafx.scene.input.PickResult和, javafx.scene.SceneAntialiasing, 类的相关 javadoc 以及 JavaFX 3D 图形入门 文档。
  • WebView WebView 类包含新特性和改进。有关其他 HTML5 特性(包括 Web 套接字、Web 辅助进程和 Web 字体)的更多信息,请参阅
  • 增强了文本支持,包括双向文本、复杂文本脚本(如泰语和印地语控件)以及文本节点中的多行多样式文本。
  • 此版本添加了对 Hi-DPI 显示的支持。
  • CSS Styleable* 类已成为公共 API。有关更多信息,请参阅 javafx.css javadoc。
  • 新的 ScheduledService 类允许自动重新启动服务。
  • JavaFX 现在可用于 ARM 平台。适用于 ARM 的 JDK 包含 JavaFX 的基础组件、图形组件和控制组件。

2.12 国际化

  • Unicode 增强,包括对 Unicode 6.2.0 的支持
  • 采用 Unicode CLDR 数据和 java.locale.providers 系统属性
  • 新增日历和区域设置 API
  • 支持将自定义资源包作为扩展进行安装

2.13 IO/NIO

  • 全新的基于 Solaris 事件端口机制的面向 Solaris 的 SelectorProvider 实现。要使用它,请将系统属性java.nio.channels.spi.Selector 的值设置为 sun.nio.ch.EventPortSelectorProvider.
  • 减小 <JDK_HOME>/jre/lib/charsets.jar 文件的大小
  • 提高了 java.lang.String(byte[], *) 构造函数和 java.lang.String.getBytes() 方法的性能。

2.14 工具

2.14.1 jjs

Java 8的Nashorn JavaScript引擎。Nashorn是于Java 8中用于取代Rhino(Java 6,Java 7)的JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。与先前的Rhino引擎相比,它有二到十倍的性能提升。

jjs是个基于Nashorn引擎的命令行工具。你可以通过该工具快速地在Java上运行JavaScript代码,就像是一个REPL。

2.14.2 jdeps

可通过 jdeps 命令行工具来分析类文件。

2.14.3 jarsigner

The jarsigner工具提供了一个选项用于请求获取时间戳机构 (TSA) 的签名时间戳。

2.14.4 javac

  • javac 命令的 -parameters 选项可用于存储正式参数名称,并启用反射 API 来检索正式参数名称。
  • 命令现已正确实施了 Java 语言规范 (JLS) 第 15.21 节中的相等运算符的类型规则。
  • javac工具现在支持检查 javadoc 注释的内容,从而避免在运行javadoc 时生成的文件中产生各种问题,例如无效的 HTML 或可访问性问题。可通过新的-Xdoclint 选项来启用此特性。有关更多详细信息,请参阅运行“javac-X”时的输出。此特性也可以在javac -X". This feature is also available in the javadoc工具中使用,并且默认启用。
  • javac 工具现在支持根据需要生成原生标头。这样便无需在构建管道中单独运行 javah 工具。可以使用新的 -h 选项在 javac 中启用此特性,该选项用于指定写入头文件的目录。将为任何具有原生方法或者使用 java.lang.annotation.Native类型的新批注的类进行批注的常量字段生成头文件。

2.14.5 javadoc

  • javadoc 工具支持新的 DocTree API,让您可以将 Javadoc 注释作为抽象语法树来进行遍历。
  • javadoc 工具支持新的 Javadoc Access API,让您可以直接从 Java 应用中调用 Javadoc 工具,而无需执行新的进程。有关更多信息,请参阅 [javadoc 新特性] (https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/whatsnew-8.html)页面。
  • javadoc工具现在支持检查javadoc 注释的内容,从而避免在运行 javadoc 时生成的文件中产生各种问题,例如无效的 HTML 或可访问性问题。此特性默认为启用状态,可以通过新的-Xdoclint 选项加以控制。有关更多详细信息,请参阅运行 “javadoc -X” 时的输出。. javac 工具也支持此特性,但默认情况下并未启用它。

2.15 日期时间包

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
Local(本地) − 简化了日期时间的处理,没有时区的问题。
Zoned(时区) − 通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

2.15.1本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

package geym.conc.fastjsonTest;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class Test10 {
   
    public static void main(String args[]){
   
        Test10 java8tester = new Test10();
        java8tester.testLocalDateTime();
    }

    public void testLocalDateTime(){
   

        // 获取当前的日期时间
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("当前时间: " + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1: " + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();

        System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);

        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2: " + date2);

        // 12 december 2014
        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3: " + date3);

        // 22 小时 15 分钟
        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4: " + date4);

        // 解析字符串
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5: " + date5);
    }
}

2.15.2使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:

package geym.conc.fastjsonTest;

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Test10 {
   
    public static void main(String args[]){
   
        Test10 java8tester = new Test10();
        java8tester.testZonedDateTime();
    }

    public void testZonedDateTime(){
   

        // 获取当前时间日期
        ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
        System.out.println("date1: " + date1);

        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + id);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("当期时区: " + currentZone);
    }
}

2.16 网络

已添加 java.net.URLPermission 类。
在 java.net.HttpURLConnection类中,如果安装了安全管理器,那么请求打开连接的调用需要权限。

2.17 HotSpot

新增的硬件内部函数以便使用高级加密标准 (AES)。 UseAES 和 UseAESIntrinsics 标志用于为 硬件启用基于硬件的 AES 内部函数。硬件必须是 2010 年或更新的 Westmere 硬件。例如,要启用硬件 AES,请使用以下标志:
-XX:+UseAES -XX:+UseAESIntrinsics
要禁用硬件 AES,请使用以下标志: -XX:-UseAES -XX:-UseAESIntrinsics

2.17.1 默认方法支持

2.17.2 永久代删除

JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)元空间
//-XX:MetaspaceSize初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整
//-XX:MaxMetaspaceSize最大空间,默认是没有限制
//-XX:MinMetaspaceFreeRatio在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
//-XX:MaxMetaspaceFreeRatio在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

2.18 Java XML

2.19 JDBC

  • 删除了 JDBC-ODBC Bridge。
  • JDBC 4.2 引入了新特性。

2.20 Pack200

  • Pack200 支持 JSR 292 引入的常量池条目和新字节码
  • JDK8 支持 JSR-292、JSR-308 和 JSR-335 指定的类文件更改

维基百科
Pack200是Sun为更快地在网络上传输JAR文件而在JSR 200中提出的一种HTTP压缩方式。Pack200也可以指代从Sun JDK 1.50版就开始提供的Pack200压缩工具以及Pack200压缩文件。

在HTTP压缩方式中,Pack200可以和GZIP压缩格式协同使用(这种情况下被称为“pack200-gzip”)以提供比只使用GZIP压缩格式更高的压缩率。Pack200专为压缩JAR文件(尤其是其中的字节码部分)而优化。此技术也应用于Java Web Start的Java程序部署中。

3.总结

4.个人推广

博客地址
https://blog.csdn.net/weixin_41563161