From a192e7f3977f742c4f5572dda7fcd95ff50b5de5 Mon Sep 17 00:00:00 2001 From: Wasabi1234 Date: Fri, 2 Aug 2019 22:57:14 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E7=AA=81=E7=A0=B4Java=E9=9D=A2=E8=AF=95?= =?UTF-8?q?=E7=B3=BB=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Java/JAVA-Calendar.md | 53 + ...05\345\255\230\346\250\241\345\236\213.md" | 403 ++++++ ...76\346\224\266\351\233\206\345\231\250.md" | 221 +++ ...66\351\233\206\347\256\227\346\263\225.md" | 249 ++++ ...41\345\256\236\344\276\213\345\214\226.md" | 108 ++ ...37\346\234\272\345\257\271\350\261\241.md" | 121 ++ ...75\347\232\204\350\277\207\347\250\213.md" | 275 ++++ ...43\200\207)---\347\256\200\344\273\213.md" | 28 + ...14\347\232\204\347\233\221\346\216\247.md" | 65 + ...270\203)---GC-\350\260\203\344\274\230.md" | 26 + ...272\214)---VM-\347\233\221\346\216\247.md" | 33 + ...01\346\214\207\344\273\244\357\274\211.md" | 243 ++++ "Java/JVM/JVM\346\214\207\344\273\244.md" | 217 +++ ...06\346\236\220\344\271\213synchronized.md" | 145 ++ ...ava-Fork-Join-\346\241\206\346\236\266.md" | 135 ++ ...(Object-value)\346\226\271\346\263\225.md" | 116 ++ ...13---extends-T-\345\222\214---super-T-.md" | 148 ++ ...--ThreadLocal-\345\216\237\347\220\206.md" | 426 ++++++ ...a-\347\232\204\346\263\250\350\247\243.md" | 1 + ...73\345\212\240\350\275\275\345\231\250.md" | 205 +++ ...20\347\240\201\345\210\206\346\236\220.md" | 133 ++ ...47\274\226Stream-DistinctOps,-SliceOps.md" | 1 + ...20\347\240\201\345\211\226\346\236\220.md" | 348 +++++ ...va\344\270\216\347\272\277\347\250\213.md" | 61 + ...25\347\232\204\346\274\224\345\217\230.md" | 74 + ...31\350\241\250\350\276\276\345\274\217.md" | 197 +++ ...a\344\270\255\347\232\204BlockingQueue.md" | 795 +++++++++++ ...4\270\255\347\232\204VO,PO\347\255\211.md" | 156 ++ ...04\351\224\201\344\274\230\345\214\226.md" | 125 ++ ...35\347\232\204\345\214\272\345\210\253.md" | 72 + ...le\345\205\263\351\224\256\345\255\227.md" | 371 +++++ ...50\241\345\274\217jdk\345\222\214cglib.md" | 224 +++ ...25\347\232\204\347\220\206\350\247\243.md" | 87 ++ ...20\347\240\201\350\247\243\346\236\220.md" | 789 ++++++++++ ...5\244\207linux\345\221\275\344\273\244.md" | 758 ++++++++++ ...44\271\213IllegalMonitorStateException.md" | 48 + ...DK\345\221\275\344\273\244\350\241\214.md" | 91 ++ ...r-\347\272\277\347\250\213\346\261\240.md" | 1269 +++++++++++++++++ ...va\350\257\255\346\263\225\347\263\226.md" | 27 + ...\201\350\247\243\346\236\220---HashMap.md" | 714 ++++++++++ ...41\351\252\214\350\257\201\347\240\201.md" | 12 + ...72\346\234\254\346\263\250\350\247\243.md" | 19 + ...t-\345\244\232\346\250\241\345\235\227.md" | 16 + ...\344\270\200)-\345\257\274\350\257\273.md" | 7 + ...02\345\270\270\345\244\204\347\220\206.md" | 17 + ...gBoot\351\233\206\346\210\220BootStrap.md" | 11 + ...ngBoot\351\233\206\346\210\220RabbitMQ.md" | 9 + ...0\220Thymeleaf\346\250\241\346\235\277.md" | 49 + ...22\346\235\200\351\241\271\347\233\256.md" | 17 + ...gboot\351\233\206\346\210\220Thymeleaf.md" | 6 + ...0\351\207\217\347\264\242\345\274\225).md" | 35 + ...27\347\232\204\345\274\200\345\217\221.md" | 43 + ...72\350\204\232\346\211\213\346\236\266.md" | 89 ++ ...5\345\212\241\350\260\203\347\224\250).md" | 188 +++ ...1\344\270\216\345\256\236\347\216\260).md" | 100 ++ ...37\347\232\204\345\274\200\345\217\221.md" | 96 ++ ...44\270\200)---\346\246\202\350\277\260.md" | 26 + ...\344\270\200)-\347\256\200\344\273\213.md" | 9 + ...45\345\222\214\346\266\210\346\201\257.md" | 5 + ...55\347\232\204\345\256\236\350\267\265.md" | 103 ++ ...14\344\270\216\345\217\221\347\216\260.md" | 121 ++ ...15\345\212\241\344\273\213\347\273\215.md" | 18 + ...15\347\275\256\344\270\255\345\277\203.md" | 41 + ...\236\346\210\230(\345\215\201)-Hystrix.md" | 5 + ...15\345\212\241\346\213\206\345\210\206.md" | 95 ++ ...0\216@Resource\344\271\213\344\272\211.md" | 236 +++ .../Spring--Http\346\272\220\347\240\201.md" | 541 +++++++ ...44\270\255\346\226\207\347\211\210-3-9.md" | 697 +++++++++ ...50\345\237\237\347\256\241\347\220\206.md" | 172 +++ ...37\345\221\275\345\221\250\346\234\237.md" | 24 + ...13\345\212\241\347\256\241\347\220\206.md" | 42 + ...35\350\265\226\346\263\250\345\205\245.md" | 593 ++++++++ Java/Spring/SpringBean.md | 449 ++++++ ...37\347\220\206\350\257\246\350\247\243.md" | 269 ++++ Java/Spring/SpringMVC-Restful.md | 4 + ...06\346\213\246\346\210\252\345\231\250.md" | 75 + ...13@CookieValue\346\263\250\350\247\243.md" | 21 + ...3\345\214\226\345\257\271\346\257\224).md" | 575 ++++++++ ...50\345\261\200\345\274\202\345\270\270.md" | 92 ++ ...60\346\215\256\347\273\221\345\256\232.md" | 723 ++++++++++ ...50\350\247\243\350\257\264\346\230\216.md" | 12 + ...40\344\270\216\351\235\242\350\257\225.md" | 128 ++ ...\350\277\207\347\250\213(\344\270\212).md" | 174 +++ ...03\346\200\247\345\215\261\351\231\251.md" | 322 +++++ ...346\200\247Performance-and-Scalability.md" | 320 +++++ ...5\274\217\351\224\201-(Explicit-Locks).md" | 362 +++++ ...05\267-(Building-Custom-Synchronizers).md" | 783 ++++++++++ ...ic-Variables-and-Non-blocking-Synchron.md" | 300 ++++ ...5\255\230\346\250\241\345\236\213(JMM).md" | 326 +++++ ...04\345\273\272\346\250\241\345\235\227.md" | 128 ++ ...46\211\247\350\241\214(Task-Execution).md" | 152 ++ ...10\344\270\216\345\205\263\351\227\255.md" | 185 +++ ...40\347\232\204\344\275\277\347\224\250.md" | 146 ++ ...71\345\205\250\346\200\273\347\273\223.md" | 874 ++++++++++++ ...\222\214Docker\347\256\200\344\273\213.md" | 14 + ...34\346\233\264\344\272\244\347\273\207.md" | 139 ++ ...257\225(00) - \350\257\264\346\230\216.md" | 96 ++ ...\345\270\210\346\213\233\350\201\230JD.md" | 21 + ...350\277\20710\344\270\207+\357\274\237.md" | 45 + ...40\347\202\271\350\257\264\346\230\216.md" | 47 + ...00\346\234\257\351\200\211\345\236\213.md" | 131 ++ ...30\345\217\257\347\224\250\346\200\247.md" | 99 ++ ...10\350\264\271\346\225\260\346\215\256.md" | 60 + ...40\344\270\252\345\260\217\346\227\266.md" | 60 + ...0\346\201\257\351\230\237\345\210\227?.md" | 46 + ...42\350\257\225\346\212\200\345\267\247.md" | 24 + ...16\350\277\236\347\216\257\347\202\256.md" | 32 + ...16\347\232\204\346\236\266\346\236\204.md" | 103 ++ ...45\344\275\234\346\265\201\347\250\213.md" | 142 ++ ...50\347\275\262\347\232\204\345\221\242.md" | 27 + ...00\345\267\247\346\200\273\347\273\223.md" | 0 ...00\344\270\252\351\227\256\351\242\230.md" | 61 + ...77\347\250\213\346\250\241\345\236\213.md" | 92 ++ ...02\347\224\250\345\234\272\346\231\257.md" | 108 ++ ...344\270\200\344\270\252LRU\357\274\237.md" | 110 ++ ...12\351\253\230\345\217\257\347\224\250.md" | 35 + ...13\345\217\212\345\216\237\347\220\206.md" | 92 ++ ...30\345\217\257\347\224\250\346\200\247.md" | 25 + ...50\345\205\265\346\236\266\346\236\204.md" | 82 ++ ...42\345\244\261\351\227\256\351\242\230.md" | 70 + ...25\345\261\202\345\216\237\347\220\206.md" | 84 ++ ...05\345\214\226\346\234\272\345\210\266.md" | 132 ++ ...17\347\232\204\345\216\237\347\220\206.md" | 260 ++++ ...77\351\200\217\351\227\256\351\242\230.md" | 38 + ...56\344\270\200\350\207\264\346\200\247.md" | 224 +++ ...36\344\272\211\351\227\256\351\242\230.md" | 24 + ...16\344\271\210\346\240\267\347\232\204.md" | 31 + ...00\345\267\247\346\200\273\347\273\223.md" | 25 + ...37\350\277\236\347\216\257\347\202\256.md" | 53 + ...5\225\245\350\246\201\347\224\250dubbo.md" | 84 ++ ...45\344\275\234\345\216\237\347\220\206.md" | 52 + ...01\347\232\204\345\215\217\350\256\256.md" | 66 + ...06\347\232\204\347\255\226\347\225\245.md" | 80 ++ ...347\232\204SPI\346\234\272\345\210\266.md" | 137 ++ ...45\345\217\212\351\207\215\350\257\225.md" | 154 ++ ...04\345\271\202\347\255\211\346\200\247.md" | 67 + ...04\351\241\272\345\272\217\346\200\247.md" | 43 + ...347\232\204RPC\346\241\206\346\236\266.md" | 41 + ...02\347\224\250\345\234\272\346\231\257.md" | 67 + ...60\347\232\204\344\274\230\345\212\243.md" | 398 ++++++ ...10\347\232\204\345\256\236\347\216\260.md" | 154 ++ ...43\345\206\263\346\226\271\346\241\210.md" | 198 +++ ...66\346\236\204\350\256\276\350\256\241.md" | 110 ++ ...06\345\272\223\345\210\206\350\241\250.md" | 171 +++ ...50\347\232\204\345\256\236\350\267\265.md" | 71 + ...06\345\272\223\345\210\206\350\241\250.md" | 108 ++ ...id\347\232\204\347\224\237\346\210\220.md" | 278 ++++ ...43\345\206\263\346\226\271\346\241\210.md" | 104 ++ ...56\345\216\273\345\223\252\344\272\206.md" | 95 ++ ...04\351\241\272\345\272\217\346\200\247.md" | 55 + ...24\350\277\233\350\277\207\347\250\213.md" | 188 +++ .../1.md" | 138 ++ ...15\347\272\247\346\234\272\345\210\266.md" | 80 ++ ...76\350\256\241\345\216\237\345\210\231.md" | 95 ++ ...47\345\260\217\346\216\247\345\210\266.md" | 182 +++ ...45\350\257\242\346\216\245\345\217\243.md" | 386 +++++ ...11\345\205\250\344\277\235\346\212\244.md" | 40 + ...41\347\232\204\346\200\273\347\273\223.md" | 37 + ...73\344\270\216\351\231\220\346\265\201.md" | 67 + ...\256\241(0) - \345\257\274\350\257\273.md" | 20 + ...20\347\273\264\347\233\221\346\216\247.md" | 54 + ...76\350\256\241\345\216\237\345\210\231.md" | 103 ++ ...06\346\236\266\350\257\264\346\230\216.md" | 130 ++ 163 files changed, 26105 insertions(+) create mode 100644 Java/JAVA-Calendar.md create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md" create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md" create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md" create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\345\257\271\350\261\241\345\256\236\344\276\213\345\214\226.md" create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\346\216\242\347\247\230HotSpot\350\231\232\346\213\237\346\234\272\345\257\271\350\261\241.md" create mode 100644 "Java/JVM/JVM\345\256\236\346\210\230---\347\261\273\345\212\240\350\275\275\347\232\204\350\277\207\347\250\213.md" create mode 100644 "Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\343\200\207)---\347\256\200\344\273\213.md" create mode 100644 "Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\200)---\345\237\272\344\272\216JDK\345\221\275\344\273\244\350\241\214\347\232\204\347\233\221\346\216\247.md" create mode 100644 "Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\203)---GC-\350\260\203\344\274\230.md" create mode 100644 "Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\272\214)---VM-\347\233\221\346\216\247.md" create mode 100644 "Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\345\205\253)---Java\344\273\243\347\240\201\345\261\202\350\260\203\344\274\230\357\274\210\345\255\227\350\212\202\347\240\201\346\214\207\344\273\244\357\274\211.md" create mode 100644 "Java/JVM/JVM\346\214\207\344\273\244.md" create mode 100644 "Java/JVM/JVM\346\272\220\347\240\201\345\210\206\346\236\220\344\271\213synchronized.md" create mode 100644 "Java/Java-Fork-Join-\346\241\206\346\236\266.md" create mode 100644 "Java/Java-Map\347\232\204containsKey(Object-key)\345\222\214containsValue(Object-value)\346\226\271\346\263\225.md" create mode 100644 "Java/Java-\346\263\233\345\236\213\350\247\243\346\203\221\344\271\213---extends-T-\345\222\214---super-T-.md" create mode 100644 "Java/Java-\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230---ThreadLocal-\345\216\237\347\220\206.md" create mode 100644 "Java/Java-\347\232\204\346\263\250\350\247\243.md" create mode 100644 "Java/Java-\347\261\273\345\212\240\350\275\275\345\231\250.md" create mode 100644 "Java/Java8-\345\216\237\345\255\220\345\274\271\347\261\273\344\271\213LongAdder\346\272\220\347\240\201\345\210\206\346\236\220.md" create mode 100644 "Java/Java8\346\265\201\347\274\226Stream-DistinctOps,-SliceOps.md" create mode 100644 "Java/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md" create mode 100644 "Java/Java\344\270\216\347\272\277\347\250\213.md" create mode 100644 "Java/Java\344\270\255Collections-sort()\346\226\271\346\263\225\347\232\204\346\274\224\345\217\230.md" create mode 100644 "Java/Java\344\270\255\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" create mode 100644 "Java/Java\344\270\255\347\232\204BlockingQueue.md" create mode 100644 "Java/Java\344\270\255\347\232\204VO,PO\347\255\211.md" create mode 100644 "Java/Java\344\270\255\347\232\204\351\224\201\344\274\230\345\214\226.md" create mode 100644 "Java/Java\344\270\255\347\261\273\345\236\213\345\217\202\346\225\260\342\200\234-T-\342\200\235\345\222\214\346\227\240\347\225\214\351\200\232\351\205\215\347\254\246\342\200\234---\342\200\235\347\232\204\345\214\272\345\210\253.md" create mode 100644 "Java/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md" create mode 100644 "Java/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md" create mode 100644 "Java/Java\345\244\232\347\272\277\347\250\213\344\270\255join\346\226\271\346\263\225\347\232\204\347\220\206\350\247\243.md" create mode 100644 "Java/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md" create mode 100644 "Java/Java\345\274\200\345\217\221\344\272\272\345\221\230\345\277\205\345\244\207linux\345\221\275\344\273\244.md" create mode 100644 "Java/Java\345\274\202\345\270\270\344\271\213IllegalMonitorStateException.md" create mode 100644 "Java/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" create mode 100644 "Java/Java\346\272\220\347\240\201\350\247\243\346\236\220---ThreadPoolExecutor-\347\272\277\347\250\213\346\261\240.md" create mode 100644 "Java/Java\350\257\255\346\263\225\347\263\226.md" create mode 100644 "Java/Java\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220---HashMap.md" create mode 100644 "Java/Spring Boot/SpringBoot+Security-\345\217\221\351\200\201\347\237\255\344\277\241\351\252\214\350\257\201\347\240\201.md" create mode 100644 "Java/Spring Boot/SpringBoot-\344\270\211\345\244\247\345\237\272\346\234\254\346\263\250\350\247\243.md" create mode 100644 "Java/Spring Boot/SpringBoot-\345\244\232\346\250\241\345\235\227.md" create mode 100644 "Java/Spring Boot/SpringBoot2-0\345\223\215\345\272\224\345\274\217\347\274\226\347\250\213\347\263\273\345\210\227(\344\270\200)-\345\257\274\350\257\273.md" create mode 100644 "Java/Spring Boot/SpringBoot\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206.md" create mode 100644 "Java/Spring Boot/SpringBoot\351\233\206\346\210\220BootStrap.md" create mode 100644 "Java/Spring Boot/SpringBoot\351\233\206\346\210\220RabbitMQ.md" create mode 100644 "Java/Spring Boot/SpringBoot\351\233\206\346\210\220Thymeleaf\346\250\241\346\235\277.md" create mode 100644 "Java/Spring Boot/Springboot-\347\247\222\346\235\200\351\241\271\347\233\256.md" create mode 100644 "Java/Spring Boot/Springboot\351\233\206\346\210\220Thymeleaf.md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\344\270\203)----\345\271\277\345\221\212\346\243\200\347\264\242\347\263\273\347\273\237(\345\212\240\350\275\275\345\205\250\351\207\217\347\264\242\345\274\225).md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\344\270\211)---\351\200\232\347\224\250\346\250\241\345\235\227\347\232\204\345\274\200\345\217\221.md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\344\272\214)---\346\220\255\345\273\272\350\204\232\346\211\213\346\236\266.md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\344\272\224)---\345\271\277\345\221\212\346\243\200\347\264\242\347\263\273\347\273\237(\345\276\256\346\234\215\345\212\241\350\260\203\347\224\250).md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\345\205\255)----\345\271\277\345\221\212\346\243\200\347\264\242\347\263\273\347\273\237(\345\271\277\345\221\212\346\225\260\346\215\256\347\264\242\345\274\225\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260).md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\345\256\236\346\210\230(\345\233\233)---\346\212\225\346\224\276\347\263\273\347\273\237\347\232\204\345\274\200\345\217\221.md" create mode 100644 "Java/Spring Cloud/SpringCloud-\345\271\277\345\221\212\347\263\273\347\273\237\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260(\344\270\200)---\346\246\202\350\277\260.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\344\270\200)-\347\256\200\344\273\213.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\344\270\203)-\345\274\202\346\255\245\345\222\214\346\266\210\346\201\257.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\344\270\203)-\346\266\210\346\201\257\346\234\215\345\212\241\345\234\250\347\224\265\345\225\206\344\270\255\347\232\204\345\256\236\350\267\265.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\344\270\211)-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\344\272\214)-\345\276\256\346\234\215\345\212\241\344\273\213\347\273\215.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\345\205\255)-\347\273\237\344\270\200\351\205\215\347\275\256\344\270\255\345\277\203.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\345\215\201)-Hystrix.md" create mode 100644 "Java/Spring Cloud/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(\345\233\233)-\345\276\256\346\234\215\345\212\241\344\270\255\347\232\204\346\234\215\345\212\241\346\213\206\345\210\206.md" create mode 100644 "Java/Spring/@Autowired-\344\270\216@Resource\344\271\213\344\272\211.md" create mode 100644 "Java/Spring/Spring--Http\346\272\220\347\240\201.md" create mode 100644 "Java/Spring/Spring-5-0\344\270\255\346\226\207\347\211\210-3-9.md" create mode 100644 "Java/Spring/Spring-Bean\347\232\204\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md" create mode 100644 "Java/Spring/Spring-Bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" create mode 100644 "Java/Spring/Spring-\344\272\213\345\212\241\347\256\241\347\220\206.md" create mode 100644 "Java/Spring/Spring-\345\256\236\346\210\230---IoC\345\256\271\345\231\250\347\232\204\344\276\235\350\265\226\346\263\250\345\205\245.md" create mode 100755 Java/Spring/SpringBean.md create mode 100755 "Java/Spring/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" create mode 100644 Java/Spring/SpringMVC-Restful.md create mode 100644 "Java/Spring/SpringMVC-\347\232\204\345\244\204\347\220\206\346\213\246\346\210\252\345\231\250.md" create mode 100644 "Java/Spring/SpringMVC\344\271\213@CookieValue\346\263\250\350\247\243.md" create mode 100644 "Java/Spring/SpringMVC\344\271\213Controller\346\237\245\346\211\276(Spring4-0-3-Spring5-0-4\346\272\220\347\240\201\350\277\233\345\214\226\345\257\271\346\257\224).md" create mode 100644 "Java/Spring/SpringMVC\345\205\250\345\261\200\345\274\202\345\270\270.md" create mode 100644 "Java/Spring/SpringMVC\346\225\260\346\215\256\347\273\221\345\256\232.md" create mode 100644 "Java/Spring/SpringMVC\347\232\204@ResponseBody\346\263\250\350\247\243\350\257\264\346\230\216.md" create mode 100755 "Java/Spring/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" create mode 100644 "Java/Spring/Spring\346\272\220\347\240\201\350\247\243\346\236\220-IoC\345\256\271\345\231\250\347\232\204\345\210\235\345\247\213\345\214\226\350\277\207\347\250\213(\344\270\212).md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22710\344\271\213\351\201\277\345\205\215\346\264\273\350\267\203\346\200\247\345\215\261\351\231\251.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22711\344\271\213\346\200\247\350\203\275\344\270\216\345\217\257\344\274\270\347\274\251\346\200\247Performance-and-Scalability.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713\344\271\213\346\230\276\345\274\217\351\224\201-(Explicit-Locks).md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22714\344\271\213\346\236\204\345\273\272\350\207\252\345\256\232\344\271\211\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267-(Building-Custom-Synchronizers).md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22715\344\271\213\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266(Atomic-Variables-and-Non-blocking-Synchron.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22716\344\271\213Java\345\206\205\345\255\230\346\250\241\345\236\213(JMM).md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\2275\344\271\213\345\237\272\347\241\200\346\236\204\345\273\272\346\250\241\345\235\227.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\2276\344\271\213\344\273\273\345\212\241\346\211\247\350\241\214(Task-Execution).md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\2277\344\271\213\345\217\226\346\266\210\344\270\216\345\205\263\351\227\255.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\2278\344\271\213\347\272\277\347\250\213\346\261\240\347\232\204\344\275\277\347\224\250.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\347\237\245\350\257\206\347\202\271\345\205\250\346\200\273\347\273\223.md" create mode 100644 "Java/\345\271\266\345\217\221\347\274\226\347\250\213/\345\217\262\344\270\212\346\234\200\345\277\253Docker\345\205\245\351\227\250!(\344\270\200)-\345\256\271\345\231\250\346\212\200\346\234\257\345\222\214Docker\347\256\200\344\273\213.md" create mode 100755 "\350\201\214\344\270\232\345\217\221\345\261\225/\346\274\253\346\274\253\345\244\247\345\255\246\346\261\237\346\271\226\345\275\225 - \347\210\261\344\270\216\346\201\250\345\224\257\344\270\216\345\267\245\344\275\234\346\233\264\344\272\244\347\273\207.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(00) - \350\257\264\346\230\216.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(01)-\347\216\260\344\273\243\344\272\222\350\201\224\347\275\221java\345\267\245\347\250\213\345\270\210\346\213\233\350\201\230JD.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(02) - Redis\345\246\202\344\275\225\351\200\232\350\277\207\350\257\273\345\206\231\345\210\206\347\246\273\346\235\245\346\211\277\350\275\275\350\257\273\350\257\267\346\261\202QPS\350\266\205\350\277\20710\344\270\207+\357\274\237.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(03)-\345\205\263\344\272\216\344\272\222\350\201\224\347\275\221Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273\350\256\255\347\273\203\350\257\276\347\250\213\347\232\204\345\207\240\347\202\271\350\257\264\346\230\216.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(05)- \346\266\210\346\201\257\351\230\237\345\210\227\346\212\200\346\234\257\351\200\211\345\236\213.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(06)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\351\253\230\345\217\257\347\224\250\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(07)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\225\260\346\215\256.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(10)-\347\231\276\344\270\207\346\266\210\346\201\257\345\234\250\346\266\210\346\201\257\351\230\237\345\210\227\351\207\214\347\247\257\345\216\213\344\272\206\345\207\240\344\270\252\345\260\217\346\227\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(11)-\344\275\240\346\200\216\344\271\210\350\256\276\350\256\241\344\270\200\344\270\252\346\266\210\346\201\257\351\230\237\345\210\227?.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(12) - \346\200\273\347\273\223\346\266\210\346\201\257\351\230\237\345\210\227\347\233\270\345\205\263\351\227\256\351\242\230\347\232\204\351\235\242\350\257\225\346\212\200\345\267\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(13)\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\350\277\236\347\216\257\347\202\256.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(14)-\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\347\232\204\346\236\266\346\236\204.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(15)-\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216Elastic Search\347\232\204\345\267\245\344\275\234\346\265\201\347\250\213.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(17)-\344\275\240\344\273\254\345\205\254\345\217\270\347\224\237\344\272\247\347\216\257\345\242\203\347\232\204\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\346\230\257\346\200\216\344\271\210\351\203\250\347\275\262\347\232\204\345\221\242.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(18)-\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\346\212\200\345\267\247\346\200\273\347\273\223.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(19) - \345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\347\232\204\347\254\254\344\270\200\344\270\252\351\227\256\351\242\230.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(20)-Redis\347\272\277\347\250\213\346\250\241\345\236\213.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(21) - redis\346\234\211\345\223\252\344\272\233\346\225\260\346\215\256\347\261\273\345\236\213,\350\260\210\350\260\210\351\200\202\347\224\250\345\234\272\346\231\257.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(22) - Redis\347\232\204\350\277\207\346\234\237\347\255\226\347\225\245\357\274\237\346\211\213\345\206\231\344\270\200\344\270\252LRU\357\274\237.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23)-\345\246\202\344\275\225\344\277\235\350\257\201redis\351\253\230\345\271\266\345\217\221\345\217\212\351\253\230\345\217\257\347\224\250.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-4)-\345\206\215\346\267\261\345\205\245Redis Replication\347\232\204\345\256\214\346\225\264\346\211\247\350\241\214\346\265\201\347\250\213\345\217\212\345\216\237\347\220\206.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-6)-\344\270\273\344\273\216\346\236\266\346\236\204\344\270\213\347\232\204Redis\345\246\202\344\275\225\345\201\232\345\210\260\351\253\230\345\217\257\347\224\250\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-7) - Redis\347\232\204\345\223\250\345\205\265\346\236\266\346\236\204.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-8) - Redis\345\223\250\345\205\265\344\270\273\345\244\207\345\210\207\346\215\242\347\232\204\346\225\260\346\215\256\344\270\242\345\244\261\351\227\256\351\242\230.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-9) - \346\267\261\345\205\245\350\247\243\346\236\220Redis\345\223\250\345\205\265\345\272\225\345\261\202\345\216\237\347\220\206.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(24)-Redis\347\232\204\346\214\201\344\271\205\345\214\226\346\234\272\345\210\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(25) - Redis cluster\351\233\206\347\276\244\346\250\241\345\274\217\347\232\204\345\216\237\347\220\206.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(26)-\350\257\264\350\257\264\345\246\202\344\275\225\345\272\224\345\257\271\347\274\223\345\255\230\351\233\252\345\264\251\344\273\245\345\217\212\347\251\277\351\200\217\351\227\256\351\242\230.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(27) - \345\246\202\344\275\225\344\277\235\350\257\201\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\347\232\204\346\225\260\346\215\256\344\270\200\350\207\264\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(28) - \345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(29) - \347\224\237\344\272\247\347\216\257\345\242\203\347\232\204redis\351\233\206\347\276\244\347\232\204\351\203\250\347\275\262\346\236\266\346\236\204\346\230\257\346\200\216\344\271\210\346\240\267\347\232\204.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(30) - \345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230\345\233\236\347\255\224\346\212\200\345\267\247\346\200\273\347\273\223.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(31)-\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\350\277\236\347\216\257\347\202\256.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(32)-\344\270\272\344\273\200\344\271\210\350\246\201\346\212\212\347\263\273\347\273\237\346\213\206\345\210\206\346\210\220\345\210\206\345\270\203\345\274\217\347\232\204\357\274\237\344\270\272\345\225\245\350\246\201\347\224\250dubbo.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(33)-Dubbo\347\232\204\345\267\245\344\275\234\345\216\237\347\220\206.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(34)-dubbo\346\224\257\346\214\201\347\232\204\345\215\217\350\256\256.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(35)-Dubbo\350\264\237\350\275\275\345\235\207\350\241\241\345\217\212\345\212\250\346\200\201\344\273\243\347\220\206\347\232\204\347\255\226\347\225\245.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(36)-\350\201\212\350\201\212Dubbo\347\232\204SPI\346\234\272\345\210\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(37)-\345\237\272\344\272\216Dubbo\347\232\204\346\234\215\345\212\241\346\262\273\347\220\206\343\200\201\346\234\215\345\212\241\351\231\215\347\272\247\344\273\245\345\217\212\351\207\215\350\257\225.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(38)-\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\345\271\202\347\255\211\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(39)-\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(40)-\345\246\202\344\275\225\350\256\276\350\256\241\344\270\200\344\270\252\347\261\273\344\274\274Dubbo\347\232\204RPC\346\241\206\346\236\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(41)-ZooKeeper\347\232\204\351\200\202\347\224\250\345\234\272\346\231\257.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(42) - Redis & ZooKeeper\344\270\244\347\247\215\345\210\206\345\270\203\345\274\217\351\224\201\345\256\236\347\216\260\347\232\204\344\274\230\345\212\243.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(43)-\345\210\206\345\270\203\345\274\217Session\346\226\271\346\241\210\347\232\204\345\256\236\347\216\260.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(44)-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(45)-\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\347\232\204\346\236\266\346\236\204\350\256\276\350\256\241.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(46)-\345\210\206\345\272\223\345\210\206\350\241\250.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(47)-\345\210\206\345\272\223\345\210\206\350\241\250\347\232\204\345\256\236\350\267\265.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(48)-\350\256\276\350\256\241\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(49)-\345\210\206\345\272\223\345\210\206\350\241\250\344\271\213\345\220\216\345\205\250\345\261\200id\347\232\204\347\224\237\346\210\220.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(50)-MySQL\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\344\270\273\344\273\216\345\220\214\346\255\245\345\273\266\346\227\266\350\247\243\345\206\263\346\226\271\346\241\210.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(8)-MQ\347\232\204\346\225\260\346\215\256\345\216\273\345\223\252\344\272\206.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(9)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\351\241\272\345\272\217\346\200\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\357\274\21053\357\274\211- \345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\274\224\350\277\233\350\277\207\347\250\213.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/1.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/13_\345\274\200\345\217\221\345\223\201\347\211\214\345\220\215\347\247\260\350\216\267\345\217\226\346\216\245\345\217\243\347\232\204\345\237\272\344\272\216\346\234\254\345\234\260\347\274\223\345\255\230\347\232\204fallback\351\231\215\347\272\247\346\234\272\345\210\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\347\252\201\347\240\264Java\351\235\242\350\257\225-hystrix\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\345\217\212\350\256\276\350\256\241\345\216\237\345\210\231.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(10)-Hystrix\347\232\204\347\272\277\347\250\213\346\261\240+\346\234\215\345\212\241+\346\216\245\345\217\243\345\210\222\345\210\206\344\273\245\345\217\212\350\265\204\346\272\220\346\261\240\347\232\204\345\256\271\351\207\217\345\244\247\345\260\217\346\216\247\345\210\266.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(12) - \345\237\272\344\272\216request cache\350\257\267\346\261\202\347\274\223\345\255\230\346\212\200\346\234\257\344\274\230\345\214\226\346\211\271\351\207\217\345\225\206\345\223\201\346\225\260\346\215\256\346\237\245\350\257\242\346\216\245\345\217\243.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(16) - \345\237\272\344\272\216timeout\346\234\272\345\210\266\346\235\245\344\270\272\345\225\206\345\223\201\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\350\260\203\347\224\250\350\266\205\346\227\266\346\217\220\344\276\233\345\256\211\345\205\250\344\277\235\346\212\244.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(17) - \345\237\272\344\272\216Hystrix\347\232\204\351\253\230\345\217\257\347\224\250\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241\347\232\204\346\200\273\347\273\223.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(9) - \345\237\272\344\272\216Hystrix\347\232\204\344\277\241\345\217\267\351\207\217\346\212\200\346\234\257\345\257\271\345\234\260\347\220\206\344\275\215\347\275\256\350\216\267\345\217\226\351\200\273\350\276\221\350\277\233\350\241\214\350\265\204\346\272\220\351\232\224\347\246\273\344\270\216\351\231\220\346\265\201.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(0) - \345\257\274\350\257\273.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(1) - hystrix\344\270\216\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\357\274\232\350\265\204\346\272\220\351\232\224\347\246\273+\351\231\220\346\265\201+\347\206\224\346\226\255+\351\231\215\347\272\247+\350\277\220\347\273\264\347\233\221\346\216\247.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - hystrix\350\246\201\350\247\243\345\206\263\347\232\204\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\351\227\256\351\242\230\344\273\245\345\217\212\345\205\266\350\256\276\350\256\241\345\216\237\345\210\231.md" create mode 100644 "\351\235\242\350\257\225\347\263\273\345\210\227/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\346\236\266\346\236\204\347\232\204\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(3) -\347\224\265\345\225\206\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\274\223\345\255\230\350\203\214\346\231\257\345\217\212\346\241\206\346\236\266\350\257\264\346\230\216.md" diff --git a/Java/JAVA-Calendar.md b/Java/JAVA-Calendar.md new file mode 100644 index 0000000000..64594b83ad --- /dev/null +++ b/Java/JAVA-Calendar.md @@ -0,0 +1,53 @@ + Calendar.getInstance() 中所获得的实例就是一个 "GreogrianCalendar" 对象(与通过 new GregorianCalendar() 获得的结果一致)。 + +#Calendar 与 Date 的转换 +Calendar calendar = Calendar.getInstance(); +// 从一个 Calendar 对象中获取 Date 对象 +Date date = calendar.getTime(); +// 将 Date 对象反应到一个 Calendar 对象中, +// Calendar/GregorianCalendar 没有构造函数可以接受 Date 对象 +// 所以我们必需先获得一个实例,然后设置 Date 对象 +calendar.setTime(date); +#注意的事项: +##1. Calendar 的 set() 方法 + +set(int field, int value) - 是用来设置"年/月/日/小时/分钟/秒/微秒"等值 + +field 的定义在 Calendar 中 + +set(int year, int month, int day, int hour, int minute, int second) 但没有 + +set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不会自动将 MilliSecond 清为 0。 + +另外,月份的起始值为0而不是1,所以要设置八月时,我们用7而不是8。 + +calendar.set(Calendar.MONTH, 7); + +我们通常需要在程序逻辑中将它清为 0, Calendar 不是马上就刷新其内部的记录 + +在 Calendar 的方法中,get() 和 add() 会让 Calendar 立刻刷新。Set() 的这个特性会给我们的开发带来一些意想不到的结果。 +##add() 与 roll() 的区别 + +add() 的功能非常强大,add 可以对 Calendar 的字段进行计算。如果需要减去值,那么使用负数值就可以了,如 add(field, -value)。 + +add() 有两条规则: + +当被修改的字段超出它可以的范围时,那么比它大的字段会自动修正。如: +Calendar cal1 = Calendar.getInstance(); +cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31 +cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1,对吗? +System.out.println(cal1.getTime()); //结果是 2000-9-30 + +另一个规则是,如果比它小的字段是不可变的(由 Calendar 的实现类决定),那么该小字段会修正到变化最小的值。 + +以上面的例子,9-31 就会变成 9-30,因为变化最小。 + +Roll() 的规则只有一条: +当被修改的字段超出它可以的范围时,那么比它大的字段不会被修正。如: + +Calendar cal1 = Calendar.getInstance(); +cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日 +cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二 +cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日 +cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日 +WEEK_OF_MONTH 比 MONTH 字段小,所以 roll 不能修正 MONTH 字段。 diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md" new file mode 100644 index 0000000000..ddf75bcd8b --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md" @@ -0,0 +1,403 @@ +# 0 [相关源码](https://github.com/Wasabi1234) + +内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 +JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行 +不同的JVM对于内存的划分方式和管理机制存在着部分差异 +结合JVM虚拟机规范,来探讨经典的JVM内存布局 +![JVM内存模型-1](http://upload-images.jianshu.io/upload_images/2614605-246286b040ad10c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![JVM内存模型-2](https://upload-images.jianshu.io/upload_images/4685968-2502bef3bd1d1692.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 1 Program Counter Register (程序计数寄存器) +Register 的命名源于CPU的寄存器,CPU只有把数据装载到寄存器才能够运行 +寄存器存储指令相关的现场信息,由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。这样必然导致经常中断或恢复,如何保证分毫无差呢? +每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常。 + +## 1.1. 定义 +程序计数器是一块较小的内存空间,可看作当前线程正在执行的字节码的行号指示器 +如果当前线程正在执行的是 +- Java方法 +计数器记录的就是当前线程正在执行的字节码指令的地址 +- 本地方法 +那么程序计数器值为undefined +## 1.2. 作用 +程序计数器有两个作用 +- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 +- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 +## 1.3. 特点 +一块较小的内存空间 +线程私有。每条线程都有一个独立的程序计数器。 +是唯一一个不会出现OOM的内存区域。 +生命周期随着线程的创建而创建,随着线程的结束而死亡。 + +# 2. Java虚拟机栈(JVM Stack) + +## 2.1. 定义 +相对于基于寄存器的运行环境来说,JVM是基于栈结构的运行环境 +栈结构移植性更好,可控性更强 +JVM中的虚拟机栈是描述Java方法执行的内存区域,它是线程私有的 + +栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程 +在活动线程中,只有位于栈顶的帧才是有效的,称为`当前栈帧` +正在执行的方法称为`当前方法` +栈帧是方法运行的基本结构 + +在执行引擎运行时,所有指令都只能针对当前栈帧进行操作 +`StackOverflowError`表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中 +JVM能够横扫千军,虚拟机栈就是它的心腹大将,当前方法的栈帧,都是正在战斗的战场,其中的操作栈是参与战斗的士兵 +![操作栈的压栈与出栈](https://upload-images.jianshu.io/upload_images/4685968-a6b55efe15a7a9ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +虚拟机栈通过压/出栈的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上 +在执行的过程中,如果出现异常,会进行异常回溯,返回地址通过异常处理表确定 +栈帧在整个JVM体系中的地位颇高,包括局部变量表、操作栈、动态连接、方法返回地址等 +- 局部变量表 +存放方法参数和局部变量 +相对于类属性变量的准备阶段和初始化阶段来说,局部变量没有准备阶段,必须显式初始化 +如果是非静态方法,则在index[0]位置上存储的是方法所属对象的实例引用,随后存储的是参数和局部变量 +字节码指令中的STORE指令就是将操作栈中计算完成的局部变量写回局部变量表的存储空间内 +- 操作栈 +操作栈是一个初始状态为空的桶式结构栈 +在方法执行过程中,会有各种指令往栈中写入和提取信息 +JVM的执行引擎是基于栈的执行引擎,其中的栈指的就是操作栈 +字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的stack属性中 + +下面用一段简单的代码说明操作栈与局部变量表的交互 +![](https://upload-images.jianshu.io/upload_images/4685968-7d0dd10c4f74fc3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 详细的字节码操作顺序如下: +![](https://upload-images.jianshu.io/upload_images/4685968-772b5a46c8180573.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +第1处说明:局部变量表就像个中药柜,里面有很多抽屉,依次编号为0, 1, 2,3,.,. n +字节码指令`istore_ 1`就是打开1号抽屉,把栈顶中的数13存进去 +栈是一个很深的竖桶,任何时候只能对桶口元素进行操作,所以数据只能在栈顶进行存取 + +某些指令可以直接在抽屉里进行,比如`inc`指令,直接对抽屉里的数值进行+1操作 +程序员面试过程中,常见的i++和++i的区别,可以从字节码上对比出来 +![i++和++i的区别](https://upload-images.jianshu.io/upload_images/4685968-2cefbb729fa8be63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- `iload_ 1` 从局部变量表的第1号抽屉里取出一个数,压入栈顶,下一步直接在抽屉里实现+1的操作,而这个操作对栈顶元素的值没有影响 +所以istore_ 2只是把栈顶元素赋值给a +- 表格右列,先在第1号抽屉里执行+1操作,然后通过iload_ 1 把第1号抽屉里的数压入栈顶,所以istore_ 2存入的是+1之后的值 + +这里延伸一个信息,i++并非原子操作。即使通过volatile关键字进行修饰,多个线程同时写的话,也会产生数据互相覆盖的问题. + +- 动态连接 +每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程的动态连接 +- 方法返回地址 +方法执行时有两种退出情况 + - 正常退出 +正常执行到任何方法的返回字节码指令,如RETURN、IRETURN、ARETURN等 + - 异常退出 + +无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧 + + +退出可能有三种方式: +- 返回值压入,上层调用栈帧 +- 异常信息抛给能够处理的栈帧 +- PC计数器指向方法调用后的下一条指令 + +Java虚拟机栈是描述Java方法运行过程的内存模型 + +Java虚拟机栈会为每一个即将运行的Java方法创建“栈帧” +用于存储该方法在运行过程中所需要的一些信息 +- 局部变量表 +存放基本数据类型变量、引用类型的变量、returnAddress类型的变量 +- 操作数栈 +- 动态链接 +- 当前方法的常量池指针 +- 当前方法的返回地址 +- 方法出口等信息 + +每一个方法从被调用到执行完成的过程,都对应着一个个栈帧在JVM栈中的入栈和出栈过程 + +> 注意:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。 +> 这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”就是现在讲的虚拟机栈,或者说Java虚拟机栈中的局部变量表部分. +> 真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息. +## 2.2. 特点 +局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建. +而且表的大小在编译期就确定,在创建的时候只需分配事先规定好的大小即可. +在方法运行过程中,表的大小不会改变 + +Java虚拟机栈会出现两种异常 + +- **StackOverFlowError** +若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求的栈深度大于虚拟机允许的最大深度时(但内存空间可能还有很多),就抛出此异常 +- **OutOfMemoryError** +若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常 + +Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡. + + + +# 3. 本地方法栈(Native Method Stack) +本地方法栈和Java虚拟机栈实现的功能与抛出异常几乎相同 +只不过虚拟机栈是为虚拟机执行Java方法(也就是字节码)服务,本地方法区则为虚拟机使用到的Native方法服务. + +在JVM内存布局中,也是线程对象私有的,但是虚拟机栈“主内”,而本地方法栈“主外” +这个“内外”是针对JVM来说的,本地方法栈为Native方法服务 +线程开始调用本地方法时,会进入一个不再受JVM约束的世界 +本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和JVM相同的能力和权限 +当大量本地方法出现时,势必会削弱JVM对系统的控制力,因为它的出错信息都比较黑盒. +对于内存不足的情况,本地方法栈还是会拋出native heap OutOfMemory + +最著名的本地方法应该是`System.currentTimeMillis()`,JNI 使Java深度使用OS的特性功能,复用非Java代码 +但是在项目过程中,如果大量使用其他语言来实现JNI,就会丧失跨平台特性,威胁到程序运行的稳定性 +假如需要与本地代码交互,就可以用中间标准框架进行解耦,这样即使本地方法崩溃也不至于影响到JVM的稳定 +当然,如果要求极高的执行效率、偏底层的跨进程操作等,可以考虑设计为JNI调用方式 + + +# 4 Java堆(Java Heap) +Heap是OOM故障最主要的发源地,它存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用 +通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间 +堆的内存空间既可以固定大小,也可运行时动态地调整,通过如下参数设定初始值和最大值,比如 +``` +-Xms256M. -Xmx1024M +``` +其中-X表示它是JVM运行参数 +- ms是memorystart的简称 最小堆容量 +- mx是memory max的简称 最大堆容量 + +但是在通常情况下,服务器在运行过程中,堆空间不断地扩容与回缩,势必形成不必要的系统压力,所以在线上生产环境中,JVM的Xms和Xmx设置成一样大小,避免在GC后调整堆大小时带来的额外压力 + +堆分成两大块:新生代和老年代 +对象产生之初在新生代,步入暮年时进入老年代,但是老年代也接纳在新生代无法容纳的超大对象 + +新生代= 1个Eden区+ 2个Survivor区 +绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发Young GC。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区,这个区真是名副其实的存在 +Survivor 区分为S0和S1两块内存空间,送到哪块空间呢?每次Young GC的时候,将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态 +如果YGC要移送的对象大于Survivor区容量上限,则直接移交给老年代 +假如一些没有进取心的对象以为可以一直在新生代的Survivor区交换来交换去,那就错了。每个对象都有一个计数器,每次YGC都会加1。 +``` +-XX:MaxTenuringThreshold +``` +参数能配置计数器的值到达某个阈值的时候,对象从新生代晋升至老年代。如果该参数配置为1,那么从新生代的Eden区直接移至老年代。默认值是15,可以在Survivor 区交换14次之后,晋升至老年代 +![对象分配与简要GC流程图](https://upload-images.jianshu.io/upload_images/4685968-57ed647f10498521.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +若`Survivor`区无法放下,或者超大对象的阈值超过上限,则尝试在老年代中进行分配; +如果老年代也无法放下,则会触发Full Garbage Collection(Full GC); +如果依然无法放下,则抛OOM. + +堆出现OOM的概率是所有内存耗尽异常中最高的 +出错时的堆内信息对解决问题非常有帮助,所以给JVM设置运行参数- +``` +XX:+HeapDumpOnOutOfMemoryError +``` +让JVM遇到OOM异常时能输出堆内信息 + +>在不同的JVM实现及不同的回收机制中,堆内存的划分方式是不一样的 + + + + + +存放所有的类实例及数组对象 +除了实例数据,还保存了对象的其他信息,如Mark Word(存储对象哈希码,GC标志,GC年龄,同步锁等信息),Klass Pointy(指向存储类型元数据的指针)及一些字节对齐补白的填充数据(若实例数据刚好满足8字节对齐,则可不存在补白) +## 特点 +Java虚拟机所需要管理的内存中最大的一块. + +堆内存物理上不一定要连续,只需要逻辑上连续即可,就像磁盘空间一样. +堆是垃圾回收的主要区域,所以也被称为GC堆. + +堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的(通过-Xmx和-Xms控制),因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError. + +线程共享 +整个Java虚拟机只有一个堆,所有的线程都访问同一个堆. +它是被所有线程共享的一块内存区域,在虚拟机启动时创建. +而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个 + +# 5 方法区 +## 5.1 定义 +Java虚拟机规范中定义方法区是堆的一个逻辑部分,但是别名Non-Heap(非堆),以与Java堆区分. +方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. + + +## 5.2 特点 +- 线程共享 +方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的.整个虚拟机中只有一个方法区. +- 永久代 +方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为永久代. +- 内存回收效率低 +Java虚拟机规范对方法区的要求比较宽松,可以不实现垃圾收集. +方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效. +对方法区的内存回收的主要目标是:**对常量池的回收和对类型的卸载** + +和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。 + +当方法区内存空间无法满足内存分配需求时,将抛出OutOfMemoryError异常. + +## 5.3 运行时常量池(Runtime Constant Pool) +### 5.3.1 定义 +运行时常量池是方法区的一部分. +方法区中存放三种数据:类信息、常量、静态变量、即时编译器编译后的代码.其中常量存储在运行时常量池中. + +我们知道,.java文件被编译之后生成的.class文件中除了包含:类的版本、字段、方法、接口等信息外,还有一项就是常量池 +常量池中存放编译时期产生的各种字面量和符号引用,.class文件中的常量池中的所有的内容在类被加载后存放到方法区的运行时常量池中。 +PS:int age = 21;//age是一个变量,可以被赋值;21就是一个字面值常量,不能被赋值; +int final pai = 3.14;//pai就是一个符号常量,一旦被赋值之后就不能被修改。 + +Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池( Constant pool table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入运行时常量池中存放。运行时常量池相对于class文件常量池的另外一个特性是具备动态性,java语言并不要求常量一定只有编译器才产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。 + +在近三个JDK版本(6、7、8)中, 运行时常量池的所处区域一直在不断的变化, +在JDK6时它是方法区的一部分 +7又把他放到了堆内存中 +8之后出现了元空间,它又回到了方法区。 +其实,这也说明了官方对“永久代”的优化从7就已经开始了 +### 5.3.2 特性 +class文件中的常量池具有动态性. +Java并不要求常量只能在编译时候产生,Java允许在运行期间将新的常量放入方法区的运行时常量池中. +String类中的intern()方法就是采用了运行时常量池的动态性.当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串,则返回池中的字符串.否则,将此 String 对象添加到池中,并返回此 String 对象的引用. +### 5.3.3 可能抛出的异常 +运行时常量池是方法区的一部分,所以会受到方法区内存的限制,因此当常量池无法再申请到内存时就会抛出OutOfMemoryError异常. + + + +我们一般在一个类中通过public static final来声明一个常量。这个类被编译后便生成Class文件,这个类的所有信息都存储在这个class文件中。 + +当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。 + +当运行时常量池中的某些常量没有被对象引用,同时也没有被变量引用,那么就需要垃圾收集器回收。 + +# 6 直接内存(Direct Memory) +直接内存不是虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域,但在JVM的实际运行过程中会频繁地使用这块区域.而且也会抛OOM + +在JDK 1.4中加入了NIO(New Input/Output)类,引入了一种基于管道和缓冲区的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆里的`DirectByteBuffer`对象作为这块内存的引用来操作堆外内存中的数据. +这样能在一些场景中显著提升性能,因为避免了在Java堆和Native堆中来回复制数据. + +> 综上看来 +程序计数器、Java虚拟机栈、本地方法栈是线程私有的,即每个线程都拥有各自的程序计数器、Java虚拟机栈、本地方法区。并且他们的生命周期和所属的线程一样。 +而堆、方法区是线程共享的,在Java虚拟机中只有一个堆、一个方法栈。并在JVM启动的时候就创建,JVM停止才销毁。 + +# 7 Metaspace (元空间) +在JDK8,元空间的前身Perm区已经被淘汰,在JDK7及之前的版本中,只有Hotspot才有Perm区(永久代),它在启动时固定大小,很难进行调优,并且Full GC时会移动类元信息 + +在某些场景下,如果动态加载类过多,容易产生Perm区的OOM. +比如某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多的类,经常出现致命错误: +``` +Exception in thread ‘dubbo client x.x connector' java.lang.OutOfMemoryError: PermGenspac +``` +为解决该问题,需要设定运行参数 +``` +-XX:MaxPermSize= l280m +``` +如果部署到新机器上,往往会因为JVM参数没有修改导致故障再现。不熟悉此应用的人排查问题时往往苦不堪言,除此之外,永久代在GC过程中还存在诸多问题 + +所以,JDK8使用元空间替换永久代.区别于永久代,元空间在本地内存中分配. +也就是说,只要本地内存足够,它不会出现像永久代中`java.lang.OutOfMemoryError: PermGen space` + +同样的,对永久代的设置参数 `PermSize` 和` MaxPermSize `也会失效 +在JDK8及以上版本中,设定`MaxPermSize`参数,JVM在启动时并不会报错,但是会提示: +``` + Java HotSpot 64Bit Server VM warning:ignoring option MaxPermSize=2560m; support was removed in 8.0 +``` + +默认情况下,“元空间”的大小可以动态调整,或者使用新参数`MaxMetaspaceSize `来限制本地内存分配给类元数据的大小. + +在JDK8里,Perm 区所有内容中 +- 字符串常量移至堆内存 +- 其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间 +![](https://upload-images.jianshu.io/upload_images/4685968-60f401503b9a75e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +比如上图中的Object类元信息、静态属性System.out、整型常量000000等 +图中显示在常量池中的String,其实际对象是被保存在堆内存中的。 + +## **元空间特色** +* 充分利用了Java语言规范:类及相关的元数据的生命周期与类加载器的一致 +* 每个类加载器都有它的内存区域-元空间 +* 只进行线性分配 +* 不会单独回收某个类(除了重定义类 RedefineClasses 或类加载失败) +* 没有GC扫描或压缩 +* 元空间里的对象不会被转移 +* 如果GC发现某个类加载器不再存活,会对整个元空间进行集体回收 + +## **GC** +* Full GC时,指向元数据指针都不用再扫描,减少了Full GC的时间 +* 很多复杂的元数据扫描的代码(尤其是CMS里面的那些)都删除了 +* 元空间只有少量的指针指向Java堆 +这包括:类的元数据中指向java.lang.Class实例的指针;数组类的元数据中,指向java.lang.Class集合的指针。 +* 没有元数据压缩的开销 +* 减少了GC Root的扫描(不再扫描虚拟机里面的已加载类的目录和其它的内部哈希表) +* G1回收器中,并发标记阶段完成后就可以进行类的卸载 + +## **元空间内存分配模型** +* 绝大多数的类元数据的空间都在本地内存中分配 +* 用来描述类元数据的对象也被移除 +* 为元数据分配了多个映射的虚拟内存空间 +* 为每个类加载器分配一个内存块列表 + * 块的大小取决于类加载器的类型 + * Java反射的字节码存取器(sun.reflect.DelegatingClassLoader )占用内存更小 +* 空闲块内存返还给块内存列表 +* 当元空间为空,虚拟内存空间会被回收 +* 减少了内存碎片 + + +最后,从线程共享的角度来看 +- 堆和元空间是所有线程共享的 +- 虚拟机栈、本地方法栈、程序计数器是线程内部私有的 + +从这个角度看一下Java内存结构 +![Java 的线程与内存](https://upload-images.jianshu.io/upload_images/4685968-aa2e002ac2fec675.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 8 从GC角度看Java堆 + +> 堆和方法区都是线程共享的区域,主要用来存放对象的相关信息。我们知道,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行期间才能知道会创建哪些对象,因此, 这部分的内存和回收都是动态的,垃圾收集器所关注的就是这部分内存(本节后续所说的“内存”分配与回收也仅指这部分内存)。而在JDK1.7和1.8对这部分内存的分配也有所不同,下面我们来详细看一下 + +Java8中堆内存分配如下图: +![](http://upload-images.jianshu.io/upload_images/4685968-3d30d129baba9f06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +# 9 JVM关闭 +- 正常关闭:当最后一个非守护线程结束或调用了System.exit或通过其他特定于平台的方式,比如ctrl+c。 +- 强制关闭:调用Runtime.halt方法,或在操作系统中直接kill(发送single信号)掉JVM进程。 +- 异常关闭:运行中遇到RuntimeException 异常等 + +在某些情况下,我们需要在JVM关闭时做一些扫尾的工作,比如删除临时文件、停止日志服务。为此JVM提供了关闭钩子(shutdown hocks)来做这些事件。 + +Runtime类封装java应用运行时的环境,每个java应用程序都有一个Runtime类实例,使用程序能与其运行环境相连。 + +关闭钩子本质上是一个线程(也称为hock线程),可以通过Runtime的addshutdownhock (Thread hock)向主jvm注册一个关闭钩子。hock线程在jvm正常关闭时执行,强制关闭不执行。 + +对于在jvm中注册的多个关闭钩子,他们会并发执行,jvm并不能保证他们的执行顺序。 + +# 参考 +《码出高效》 + +# X 交流学习 +![](https://img-blog.csdnimg.cn/20190504005601174.jpg) +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) +## [博客](http://www.shishusheng.com) + +## [Github](https://github.com/Wasabi1234) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md" new file mode 100644 index 0000000000..4a0b13682f --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md" @@ -0,0 +1,221 @@ +使用分代垃圾收集器,基于以下观察事实(弱分代假设) +- 大多数分配对象的存活时间短 +- 存活时间久的对象很少引用存活时间短的对象 + +由此, HotSpot VM 将堆分为两个物理区空间,这就是分代(永久代只存储元数据, eg. 类的数据结构,保留字符串( Interned String)) + + +根据新生代和老年代各自的特点,我们应该分别为它们选择不同的收集器,以提升垃圾回收效率. +![](http://upload-images.jianshu.io/upload_images/4685968-3a367913acebef67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 1 新生代垃圾收集器 + +## 1.1 Serial垃圾收集器 +一个主要应用于Y-GC的垃圾回收器,采用串行单线程的方式完成GC任务,其中“Stop The World"简称STW,即垃圾回收的某个阶段会暂停整个应用程序的执行 +F-GC的时间相对较长,频繁FGC会严重影响应用程序的性能 +![Serial 回收流程](https://upload-images.jianshu.io/upload_images/4685968-407a95f442c62819.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`单线程 Stop-The-World 式` +![](http://upload-images.jianshu.io/upload_images/4685968-ebdf41221960630c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 单线程 +只会使用一个CPU或一条GC线程进行GC,并且在GC过程中暂停其他所有的工作线程,因此用户的请求或图形化界面会出现卡顿 +- 适合Client模式 +一般客户端应用所需内存较小,不会创建太多的对象,而且堆内存不大,因此GC时间比较短,即使在这段时间停止一切用户线程,也不会感到明显停顿 +- 简单高效 +由于Serial收集器只有一条GC线程,避免了线程切换的开销 +- 采用"复制"算法 +## 1.2 ParNew垃圾收集器 +ParNew是Serial的多线程版本. +![](http://upload-images.jianshu.io/upload_images/4685968-e4947c56e4c734f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 2.1 多线程并行执行 +ParNew由多条GC线程并行地进行垃圾清理. +但清理过程仍然需要暂停一切其他用户线程. +但由于有多条GC线程同时清理,清理速度比Serial有一定的提升 +## 2.2 适合多CPU的服务器环境 +由于使用多线程,是许多运行在 server 模式下的虚拟机首选的新生代收集器 +- 与Serial性能对比 +ParNew和Serial唯一区别就是使用了多线程垃圾回收,在多CPU的环境下性能比Serial会有一定程度的提升 +但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial,双CPU环境也不一定就比Serial高效 +默认开启的收集线程数与CPU数量相同 +## 2.3 采用"复制"算法 +## 2.4 追求"降低停顿时间" +和Serial相比,ParNew使用多线程的目的就是缩短GC时间,从而减少用户线程被停顿的时间 +# 3 Parallel : 吞吐量为先! +## 3.1 Scavenge垃圾收集器 +Parallel Scavenge和ParNew一样都是并行的多线程、新生代收集器,都使用"复制"算法(`Stop-The-World`)进行垃圾回收 + +但它们有个巨大不同点: +- ParNew收集器追求降低GC时用户线程的停顿时间,适合交互式应用,良好的反应速度提升用户体验. +- Parallel Scavenge追求可控的CPU吞吐量,能够在较短的时间内完成指定任务,适合不需太多交互的后台运算 + +>吞吐量是指用户线程运行时间占CPU总时间的比例. +>CPU总时间包括 : 用户线程运行时间 和 GC线程运行的时间. +> 因此,吞吐量越高表示用户线程运行时间越长,从而用户线程能够被快速处理完. + +- 降低停顿时间的两种方式 +1.在多CPU环境中使用多条GC线程,从而垃圾回收的时间减少,从而用户线程停顿的时间也减少; +2.实现GC线程与用户线程并发执行。所谓并发,就是用户线程与GC线程交替执行,从而每次停顿的时间会减少,用户感受到的停顿感降低,但线程之间不断切换意味着需要额外的开销,从而垃圾回收和用户线程的总时间将会延长。 + +- Parallel Scavenge提供的参数 + - -XX:GCTimeRadio +直接设置吞吐量大小,GC时间占总时间比率.相当于是吞吐量的倒数. + + - -XX:MaxGCPauseMillis +设置最大GC停顿时间 +Parallel Scavenge会根据这个值的大小确定新生代的大小. +这个值越小,新生代就越小,从而收集器就能以较短时间进行一次GC +但新生代变小后,回收的频率就会提高,吞吐量也降下来了,因此要合理控制这个值 + - -XX:+UseAdaptiveSizePolicy +开启GC **自适应的调节策略(区别于ParNew)**. +我们只要设置最大堆(-Xmx)和MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survior的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio +## 3.2 Old垃圾收集器 +Parallel Scavenge的老年代版本,一般它们搭配使用,追求CPU吞吐量 + +它们在垃圾收集时都是由多条GC线程并行执行,并暂停一切用户线程,使用"标记-整理"算法.因此,由于在GC过程中没有使垃圾收集和用户线程并行执行,因此它们是追求吞吐量的垃圾收集器. +![](http://upload-images.jianshu.io/upload_images/4685968-7dfdda76a61a31cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# **老年代垃圾收集器** +# 1 Serial Old垃圾收集器 +Serial的老年代版本,都是单线程收集器,GC时只启动一条GC线程,因此都适合客户端应用. + + +它们唯一的区别就是 +- Serial Old工作在老年代,使用"标记-整理"算法 +- Serial工作在新生代,使用"复制"算法. +![](http://upload-images.jianshu.io/upload_images/4685968-b150a60a1bae36a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 4 CMS垃圾收集器(Concurrent Mark Sweep Collector) : 低延迟为先! +回收停顿时间比较短、目前比较常用的垃圾回收器。它通过初始标记(InitialMark)、并发标记(Concurrent Mark)、重新标记( Remark)、并发清除( Concurrent Sweep )四个步骤完成垃圾回收工作 +由于CMS采用的是“标记-清除算法",因此戸生大量的空间碎片。为了解决这个问题,CMS可以通过配置 +``` +-XX:+UseCMSCompactAtFullCollection +``` +参数,强制JVM在FGC完成后対老年代迸行圧縮,执行一次空间碎片整理,但是空间碎片整理阶段也会引发STW。为了减少STW次数,CMS还可以通过配置 +``` +-XX:+CMSFullGCsBeforeCompaction=n +``` +参数,在执行了n次FGC后, JVM再在老年代执行空间碎片整理 + +对许多应用来说,快速响应比端到端的吞吐量更为重要 + +管理新生代的方法与 parallel 和 serial 相同 +在老年代则尽可能并发执行,每个 GC 周期只有2次短的停顿 + +一种追求最短停顿时间的收集器 +在GC时使得用户线程和GC线程并发执行,因此在GC过程中用户也不会感受到明显卡顿 +但用户线程和GC线程之间不停地切换会有额外的开销,因此垃圾回收总时间就会被延长 +**垃圾回收过程** +前两步需要"Stop The World" +- 初始标记 (Initial Mark) +停止一切用户线程,仅使用一条初始标记线程对所有与GC Roots直接相关联的 老年代对象进行标记,速度很快 +- 并发标记 (Concurrent Marking Phase) +使用多条并发标记线程并行执行,并与用户线程并发执行.此过程进行可达性分析,标记所有这些对象可达的存货对象,速度很慢 +- 重新标记 ( Remark) +因为并发标记时有用户线程在执行,标记结果可能有变化 +停止一切用户线程,并使用多条重新标记线程并行执行,重新遍历所有在并发标记期间有变化的对象进行最后的标记.这个过程的运行时间介于初始标记和并发标记之间 +- 并发清除 (Concurrent Sweeping) +只使用一条并发清除线程,和用户线程们并发执行,清除刚才标记的对象 +这个过程非常耗时 +![](http://upload-images.jianshu.io/upload_images/4685968-fb1723e25639cf21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 4.1 **CMS的缺点** +- 吞吐量低 +由于CMS在GC过程用户线程和GC线程并行,从而有线程切换的额外开销 +因此CPU吞吐量就不如在GC过程中停止一切用户线程的方式来的高 +- 无法处理浮动垃圾,导致频繁Full GC +由于垃圾清除过程中,用户线程和GC线程并发执行,也就是用户线程仍在执行,那么在执行过程中会产生垃圾,这些垃圾称为"浮动垃圾" +如果CMS在GC过程中,用户线程需要在老年代中分配内存时发现空间不足,就需再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC +- 使用"标记-清除"算法产生碎片空间 +由于CMS使用了"标记-清除"算法, 因此清除之后会产生大量的碎片空间,不利于空间利用率.不过CMS提供了应对策略: + - 开启-XX:+UseCMSCompactAtFullCollection +开启该参数后,每次FullGC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块儿.但每次都整理效率不高,因此提供了以下参数. + - 设置参数-XX:CMSFullGCsBeforeCompaction +本参数告诉CMS,经过了N次Full GC过后再进行一次内存整理. + +# 5 G1收集器(Garbage-First) +Hotspot 在JDK7中推出了新一代 G1 ( Garbage-First Garbage Collector )垃圾回收,通过 +``` +-XX:+UseG1GC +``` +参数启用 +和CMS相比,Gl具备压缩功能,能避免碎片向題,G1的暂停时间更加可控。性能总体还是非常不错的,G1是当今最前沿的垃圾收集器成果之一. + +## 5.1 G1的特点 +- 追求停顿时间 +- 多线程GC +- 面向服务端应用 +- 整体来看基于标记-整理和局部来看基于复制算法合并 +不会产生内存空间碎片 +- 可对整个堆进行垃圾回收 +- 可预测的停顿时间 +## 5.2 G1的内存模型 +没有新生代和老年代的概念,而是将Java堆划分为一块块独立的大小相等的Region. +当要进行垃圾收集时,首先估计每个Region中的垃圾数量,每次都从垃圾回收价值最大的Region开始回收,因此可以获得最大的回收效率 +![](https://upload-images.jianshu.io/upload_images/4685968-db8d827f816cca34.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + G1将Java堆空间分割成了若干相同大小的区域,即region +包括 +- Eden +- Survivor +- Old +- Humongous + +其中,`Humongous `是特殊的Old类型,专门放置大型对象. +这样的划分方式意味着不需要一个连续的内存空间管理对象.G1将空间分为多个区域,**优先回收垃圾最多**的区域. +G1采用的是**Mark-Copy** ,有非常好的空间整合能力,不会产生大量的空间碎片 +G1的一大优势在于可预测的停顿时间,能够尽可能快地在指定时间内完成垃圾回收任务 +在JDK11中,已经将G1设为默认垃圾回收器,通过jstat命令可以查看垃圾回收情况,在YGC时S0/S1并不会交换. + +## 5.3 Remembered Set +一个对象和它内部所引用的对象可能不在同一个Region中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析? +当然不是,每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,从而在进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对所有堆内存的遍历. +## 5.4 G1垃圾收集过程 + +- 初始标记 +标记与GC Roots直接关联的对象,停止所有用户线程,只启动一条初始标记线程,这个过程很快. +- 并发标记 +进行全面的可达性分析,开启一条并发标记线程与用户线程并行执行.这个过程比较长. +- 最终标记 +标记出并发标记过程中用户线程新产生的垃圾.停止所有用户线程,并使用多条最终标记线程并行执行. +- 筛选回收 +回收废弃的对象.此时也需要停止一切用户线程,并使用多条筛选回收线程并行执行. +![这里写图片描述](http://upload-images.jianshu.io/upload_images/4685968-0ea2b3a18ecc561a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +S0/S1的功能由G1中的Survivor region来承载,通过GC日志可以观察到完整的垃圾回收过程如下,其中就有Survivor regions的区域从0个到1个 +![](https://upload-images.jianshu.io/upload_images/4685968-ad5f6b99b3de502e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + 红色标识的为G1中的四种region,都处于Heap中. +G1执行时使用4个worker并发执行,在初始标记时,还是会触发STW,如第一步所示的Pause + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md" new file mode 100644 index 0000000000..80f2fc0a09 --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md" @@ -0,0 +1,249 @@ +Java会对内存进行自动分配与回收管理,使上层业务更加安全,方便地使用内存实现程序逻辑 +在不同的JVM实现及不同的回收机制中,堆内存的划分方式是不一样的 +这里简要介绍垃圾回收( Garbage Collection, GC)。垃圾回收的主要目的是清除不再使用的对象,自动释放内存. + +# 静态内存分配和回收 +在程序开始运行时由编译器分配的内存 +在被编译时就已经能够确定需要的空间,当程序被加载时系统把内存一次性分配给它,这些内存不会在程序执行时发生变化,直到程序执行结束时才回收内存. + +- 包括原生数据类型及对象的引用 + +- 这些静态内存空间在栈上分配,方法运行结束,对应的栈帧撤销,内存空间被回收. + +- 每个栈帧中的本地变量表都是在类被加载的时候就确定的,每一个栈帧中分配多少内存基本上是在类结构确定时就已知了,因此这几块区域内存分配和回收都具备确定性,就不需要过多考虑回收问题了 + +# 动态内存分配和回收 +- 在程序执行时才知道要分配的存储空间大小,对象何时被回收也是不确定的,只有等到该对象不再使用才会被回收. + +堆和方法区的内存回收具有不确定性,因此垃圾收集器在回收堆和方法区内存的时候花了一点心思. + +# 1 Java堆内存的回收 +## 1.1 判定回收的对象 +GC是如何判断对象是否可以被回收的呢?为了判断对象是否存活,JVM引入了GC Roots +如果一个对象与GC Roots之间没有直接或间接的引用关系,比如某个失去任何引用的对象,或者两个互相环岛状循环引用的对象等,判决这些对象“死缓”,是可以被回收的 + +在对堆进行对象回收之前,首先要判断哪些是无效对象(一个对象不被任何对象或变量引用)需要被回收 + +一般有两种判别方式: +- 引用计数法 (Reference Counting) +每个对象都有一个整型的计数器,当这个对象被一个变量或对象引用时,该计数器加一;当该引用失效时,计数器值减一.当计数器为0时,就认为该对象是无效对象. +- 可达性分析法 (Reachability Analysis) +所有和GC Roots直接或间接关联的对象都是有效对象,和GC Roots没有关联的对象就是无效对象. +![](http://upload-images.jianshu.io/upload_images/1801191-72215219d013bf70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`GC Roots对象` + - 虚拟机栈(栈帧中的本地变量表)中引用的对象 + - 方法区中静态属性引用的对象 + - 方法区中常量引用的对象 + - 本地方法栈JNI(native方法)引用的对象 + +> **GC Roots并不包括堆中对象所引用的对象!这样就不会出现循环引用.** + +- 两者对比 +引用计数法虽然简单,但存在无法解决对象之间相互循环引用的严重问题,且伴随加减法操作的性能影响. + +因此,目前主流语言均使用可达性分析方法来判断对象是否有效. + +# 2 回收无效对象的过程 +当经可达性算法筛选出失效的对象之后,并不是立即清除,而是再给对象一次重生的机会 +- 判断是否覆盖finalize() + - 未覆盖该或已调用过该方法,直接释放对象内存 + - 已覆盖该方法且还未被执行,则将finalize()扔到F-Queue队列中 +- 执行F-Queue中的finalize() + 虚拟机会以较低的优先级执行这些finalize(),不会确保所有的finalize()都会执行结束 +如果finalize()中出现耗时操作,虚拟机就直接停止执行,将该对象清除 +- 对象重生或死亡 + - 如果在执行finalize()方法时,将this赋给了某一个引用,则该对象重生 + - 如果没有,那么就会被垃圾收集器清除 +> 注意:强烈不建议使用finalize()进行任何操作! +如果需要释放资源,请用try-finally或者其他方式都能做得更好. +> 因为finalize()不确定性大,开销大,无法保证各个对象的调用顺序. + +以下代码示例看到:一个对象的finalize被执行,但依然可以存活 +``` +/** + * 演示两点: + * 1.对象可以在被GC时自救 + * 2.这种自救机会只有一次,因为一个对象的finalize()最多只能被系统自动调用一次,因此第二次自救失败 + * @author sss + * @since 17-9-17 下午12:02 + * + */ +public class FinalizeEscapeGC { + + private static FinalizeEscapeGC SAVE_HOOK = null; + + private void isAlive() { + System.out.println("yes,I am still alive :)"); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + System.out.println("finalize methodd executed!"); + FinalizeEscapeGC.SAVE_HOOK = this; + } + + + public static void main(String[] args) throws InterruptedException { + SAVE_HOOK = new FinalizeEscapeGC(); + + // 对象第一次成功自救 + SAVE_HOOK = null; + System.gc(); + // 因为finalize方法优先级很低,所以暂停0.5s以等待它 + Thread.sleep(500); + if (SAVE_HOOK != null) { + SAVE_HOOK.isAlive(); + } else { + System.out.println("no,I am dead :("); + } + + // 自救失败 + SAVE_HOOK = null; + System.gc(); + Thread.sleep(500); + if (SAVE_HOOK != null) { + SAVE_HOOK.isAlive(); + } else { + System.out.println("no,I am dead :("); + } + } +} +``` +运行结果 + +``` +finalize methodd executed! +yes,I am still alive :) +no,I am dead :( +``` + +# 3 方法区的内存回收 +使用`复制算法`实现堆的内存回收,堆被分为`新生代`和`老年代` +- 新生代中的对象"朝生夕死",每次垃圾回收都会清除掉大量对象 +- 老年代中的对象生命较长,每次垃圾回收只有少量的对象被清除 + +由于方法区中存放生命周期较长的类信息、常量、静态变量. +因此方法区就像堆的老年代,每次GC只有少量垃圾被清除. + +方法区中主要清除两种垃圾 +- 废弃常量 +- 无用类 + +## 3.1 回收废弃常量 +回收废弃常量和回收对象类似,只要常量池中的常量不被任何变量或对象引用,那么这些常量就会被清除. + +## 3.2 回收无用类 +判定无用类的条件则较为苛刻 +- 该类所有实例都已被回收 +即Java堆不存在该类的任何实例 +- 加载该类的ClassLoader已被回收 +- 该类的java.lang.Class对象没有被任何对象或变量引用,无法通过反射访问该类的方法 +只要一个类被虚拟机加载进方法区,那么在堆中就会有一个代表该类的对象:java.lang.Class.这个对象在类被加载进方法区的时候创建,在方法区中该类被删除时清除. + + +# 4 垃圾收集算法 + +## 4.1 标记-清除(Mark-Sweep) +`最基础`的收集算法,后续算法也都是基于此并改进其不足而得. + +该算法会从每个GC Roots出发,依次标记有引用关系的对象,最后将没有被标记的对象清除 + +### 不足 +这种算法会带来大量的空间碎片,导致需要分配一个较大连续空间时容易触发FullGC,降低了空间利用率. +![标记-清除算法](http://upload-images.jianshu.io/upload_images/4685968-2f4485f2b21d0496?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +为了解决这个问题,又提出了“标记-整理算法”,该算法类似计算机的磁盘整理,首先会从GC Roots出发标记存活的对象,然后将存活对象整理到内存空间的一端,形成连续的已使用空间,最后把已使用空间之外的部分全部清理掉,这样就不会产生空间碎片的问题 + +## 4.2 复制算法(Mark-Copy) +为了能够并行地标记和整理将空间分为两块,每次只激活其中一块,垃圾回收时只需把存活的对象复制到另一块未激活空间上,将未激活空间标记为已激活,将已激活空间标记为未激活,然后清除原空间中的原对象 + +将内存分成大小相等两份,只将数据存储在其中一块上 +- 当需要回收时,首先标记废弃数据 +- 然后将有用数据复制到另一块内存 +- 最后将第一块内存空间全部清除 +![复制算法](http://upload-images.jianshu.io/upload_images/4685968-df08eebdab044bef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 4.2.1 分析 + - 这种算法避免了空间碎片,但内存缩小了一半. + - 每次都需将有用数据全部复制到另一片内存,效率不高 + +### 4.2.2 解决空间利用率问题 +堆内存空间分为较大的Eden和两块较小的Survivor,每次只使用Eden和Survivor区的一块。这种情形下的“ Mark-Copy"减少了内存空间的浪费。“Mark-Copy”现作为主流的YGC算法进行新生代的垃圾回收。 +在新生代中,由于大量对象都是"朝生夕死",也就是一次垃圾收集后只有少量对象存活 +因此我们可以将内存划分成三块 + - `Eden、Survior1、Survior2` +- 内存大小分别是8:1:1 + +分配内存时,只使用Eden和一块Survior1. +- 当发现Eden+Survior1的内存即将满时,JVM会发起一次`Minor GC`,清除掉废弃的对象, +- 并将所有存活下来的对象复制到另一块Survior2中. +- 接下来就使用Survior2+Eden进行内存分配 + +通过这种方式,只需要浪费10%的内存空间即可实现带有压缩功能的垃圾收集方法,避免了内存碎片的问题. + +### 4.2.3 分配担保 +准备为一个对象分配内存时,发现此时Eden+Survior中空闲的区域无法装下该对象 +就会触发`MinorGC`(新生代 GC 算法),对该区域的废弃对象进行回收. + +但如果MinorGC过后只有少量对象被回收,仍然无法装下新对象 + - 那么此时需要将Eden+Survior中的`所有对象`都`转移到老年代`中,然后再将新对象存入Eden区.这个过程就是"分配担保". + +在发生 minor gc 前,虚拟机会检测`老年代最大可用连续空间是否大于新生代所有对象总空间` +若成立,minor gc 可确保安全 +若不成立,JVM会查看 `HandlePromotionFailure`是否允许担保失败 +- 若允许 +那么会继续检测老年代最大可用的连续空间是否 > 历次晋升到老年代对象的平均大小 + - 若大于 +则将尝试进行一次 minor gc,尽管这次 minor gc 是有风险的 + - 若小于或 HandlePromotionFailure 设置不允许冒险 +改为进行一次 full gc (老年代GC) + +## 4.3 标记-压缩算法(Mark-Compact) +在回收前,标记过程仍与"标记-清除"一样 +但后续不是直接清理可回收对象,而是 + - 将所有存活对象移到一端 + - 直接清掉端边界之外内存 +![标记-整理算法](http://upload-images.jianshu.io/upload_images/4685968-794931bd0bba6b8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 分析 +这是一种**老年代垃圾收集算法**. +老年代中对象一般寿命较长,每次垃圾回收会有大量对象存活 +因此如果选用"复制"算法,每次需要較多的复制操作,效率低 + +而且,在新生代中使用"复制"算法 +当 Eden+Survior 都装不下某个对象时,可使用老年代内存进行"分配担保" + +而如果在老年代使用该算法,那么在老年代中如果出现 Eden+Survior 装不下某个对象时,没有其他区域给他作分配担保 + +因此,老年代中一般使用"标记-压缩"算法 + +## 4.4 分代收集算法(Generational Collection) +当前商业虚拟机都采用此算法. +根据对象存活周期的不同将Java堆划分为老年代和新生代,根据各个年代的特点使用最佳的收集算法. +- 老年代中对象存活率高,无额外空间对其分配担保,必须使用"标记-清除"或"标记-压缩"算法 +- 新生代中存放"朝生夕死"的对象,用复制算法,只需要付出少量存活对象的复制成本,就可完成收集 + +# 5 Java中引用的种类 +Java中根据生命周期的长短,将引用分为4类 +- 强引用 +我们平时所使用的引用就是强引用 +类似`A a = new A();` +即通过关键字new创建的对象所关联的引用就是强引用 +只要强引用还存在,该对象永远不会被回收 +- 软引用 +一些还有用但并非必需的对象 +只有当堆即将发生OOM异常时,JVM才会回收软引用所指向的对象. +软引用通过SoftReference类实现 +软引用的生命周期比强引用短一些 +- 弱引用 +也是描述非必需对象,比软引用更弱 +所关联的对象只能存活到下一次GC发生前. +只要垃圾收集器工作,无论内存是否足够,弱引用所关联的对象都会被回收. +弱引用通过WeakReference类实现. +- 虚引用 +也叫幽灵(幻影)引用,最弱的引用关系. +它和没有引用没有区别,无法通过虚引用取得对象实例. +设置虚引用唯一的作用就是在该对象被回收之前收到一条系统通知. +虚引用通过PhantomReference类来实现. + + + diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\345\257\271\350\261\241\345\256\236\344\276\213\345\214\226.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\345\257\271\350\261\241\345\256\236\344\276\213\345\214\226.md" new file mode 100644 index 0000000000..aa67ce8ca4 --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\345\257\271\350\261\241\345\256\236\344\276\213\345\214\226.md" @@ -0,0 +1,108 @@ +Java是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象 +在实例化对象的过程中,JVM中发生了什么化学反应呢? + +(1)下面从最简单的 +![](https://upload-images.jianshu.io/upload_images/4685968-2746aafdc112f2e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +代码进行分析,利用javap -verbose- p命令查看对象创建的字节码 +![](https://upload-images.jianshu.io/upload_images/4685968-e237247e0a2739df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +● new : 如果找不到Class对象,则进行类加载 +加载成功后,则在堆中分配内存,从`Object`开始到本类路径上的所有属性值都要分配内存 +分配完毕之后,进行零值初始化 +在分配过程中,注意引用是占据存储空间的,它是一个变量,占用4个字节 +这个指令完毕后,将指向实例对象的引用变量压入虛拟机栈顶 + +● dup : 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量 +如果``方法有参数,还需要把参数压入操作栈中 +两个引用变量的目的不同,其中压至底下的引用用于赋值,或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关方法 + +● invokespecial : 调用对象实例方法,通过栈顶的引用变量调用方法 +- ``是类初始化时执行的方法 +- 是对象初始化时执行的方法 + +(2) 前面所述是从字节码的角度看待对象的创建过程,现在从执行步骤的角度来分析: +● 确认类元信息是否存在 +当JVM接收到new指令时,首先在`metaspace`内检查需要创建的类元信息是否存在 +若不存在,在双亲委派模式下,使用当前类加载器以`ClassLoader+包名+类名`为Key进行查找对应的.class文件 +- 如果没有找到文件,则抛出`ClassNotFoundException` +- 如果找到,则进行类加载,并生成对应的Class类对象 + +● 分配对象内存 +首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可(4个字节),接着在堆中划分一块内存给新对象 +在分配内存空间时,需要进行同步操作,比如采用CAS失败重试、区域加锁等方式保证分配操作的原子性 + +● 设定默认值 +成员变量值都需要设定为默认值,即各种不同形式的零值 + +● 设置对象头 +设置新对象的哈希码、GC信息、锁信息对象所属的类元信息等 +这个过程的具体设置方式取决于JVM实现 + +● 执行init方法 +初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量 + +# 1 对象的创建过程 +当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作 + 1. 检查常量池中是否有要创建的这个对象所属类的符号引用 + - 若无,说明这个类还没有被定义!抛ClassNotFoundException + - 若有,转2 + 2. 检查这个符号引用所代表的类是否已被JVM加载 + - 若否,就找该类的class文件,并加载进方法区 + - 若是,转3 + 3. 根据方法区中该类的信息确定该类所需的内存大小 + 一个对象所需的内存大小是在这个对象所属类被定义完就能确定的! +且一个类所生产的所有对象的内存大小是一样的! +JVM在一个类被加载进方法区的时候就知道该类生产的每一个对象所需要的内存大小 +从堆中划分一块对应大小的内存空间给新的对象,分配堆中内存有两种方式 +- 指针碰撞(Bump the Pointer) +如果JVM的垃圾收集器采用**复制算法**或**标记-整理算法**,那么堆中空闲内存是完整的区域,并且空闲内存和已使用内存之间由一个指针标记. +那么当为一个对象分配内存时,只需移动指针即可.因此,这种在完整空闲区域上通过移动指针来分配内存的方式就叫做"指针碰撞" +- 空闲列表 (Free List) +如果JVM的GC器采用**标记-清除算法**,那么堆中空闲区域和已使用区域交错,因此需要用一张“空闲列表”来记录堆中哪些区域是空闲区域,从而在创建对象的时候根据这张“空闲列表”找到空闲区域,并分配内存 + +综上所述:JVM究竟采用哪种内存分配方法,取决于它使用了何种GC器 +为对象中的成员变量赋上初始值(默认初始化) + 4. 设置对象头(Object Header) + 5. 调用对象的构造函数进行初始化 + +至此,整个对象的创建过程就完成了 +# 2 对象的内存布局 +一个对象从逻辑角度看,由域和方法构成 +从物理角度来看,对象是存储在堆中的一串二进制数 + +对象在内存中存储的布局分三部分 + +- 对象头(Header) +- 实例数据(Instance Data) +- 对齐补充(Padding) +##2.1 对象头 +- 存储对象在运行过程中自身所需要的一些数据 +哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等 + - 类型指针 + 即对象指向它的类元数据的指针,JVM通过该指针能确定这个对象是哪个类的实例. + 另外,如果对象是一个数组,那么对象头中还要包含数组长度(因为从数组的元数据无法确定数组的大小). + ##2.2 实例数据 +实例数据部分就是程序定义的各种字段的内容,包含父/子类的都会记录下来 +##2.3 对齐填充(并非必然存在,无特别含义,仅起占位符作用) +HotSpot要求对象的大小必须是8字节的整数倍 +由于对象起始地址必须是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为8的整数倍 +# 3 访问对象的过程 +栈上的reference数据存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式 +- 句柄访问方式 +Java堆中需要有一块叫做"句柄池"的内存,存放所有对象的地址和所有对象所属类的类信息 +![](http://upload-images.jianshu.io/upload_images/4685968-0cef2d0c4cf527d0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +reference中存放的是对象在句柄池中的地址. +访问对象时,首先需要通过reference找到该对象的句柄,然后根据句柄中对象的地址再访问对象 +- 直接指针访问方式 +reference直接存放对象地址,从而不需要句柄池,通过引用能够直接访问对象 +但对象所在的内存空间中需要额外的策略存储对象所属的类信息的地址 +![](http://upload-images.jianshu.io/upload_images/4685968-ca169ca1646753a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 两种方式的比较 +使用句柄最大好处是reference中存储的是稳定的句柄地址,在对象被移动时也只改变句柄中的实例数据指针,而reference本身不需要修改 +`而HotSpot采用直接指针访问方式`,因为它只需一次寻址操作,节省了一次指针定位的时间开销,对象的访问又十分频繁,从而性能比句柄访问方式快一倍 + + + + + diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\346\216\242\347\247\230HotSpot\350\231\232\346\213\237\346\234\272\345\257\271\350\261\241.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\346\216\242\347\247\230HotSpot\350\231\232\346\213\237\346\234\272\345\257\271\350\261\241.md" new file mode 100644 index 0000000000..b36eb00981 --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\346\216\242\347\247\230HotSpot\350\231\232\346\213\237\346\234\272\345\257\271\350\261\241.md" @@ -0,0 +1,121 @@ +Java是面向对象的静态强类型语言,声明并创建对象的代码很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量操作该对象 + +在实例化对象的过程中,JVM中发生了什么呢? + +(1)下面从最简单的 +![](https://upload-images.jianshu.io/upload_images/4685968-2746aafdc112f2e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +代码进行分析,利用javap -verbose- p命令查看对象创建的字节码 +![](https://upload-images.jianshu.io/upload_images/4685968-e237247e0a2739df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +● new : 如果找不到Class对象,则进行类加载 +加载成功后,则在堆中分配内存,从`Object`开始到本类路径上的所有属性值都要分配内存 +分配完毕之后,进行零值初始化 +在分配过程中,注意引用是占据存储空间的,它是一个变量,占用4个字节 +这个指令完毕后,将指向实例对象的引用变量压入虛拟机栈顶 + +● dup : 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象的引用变量 +如果``方法有参数,还需要把参数压入操作栈中 +两个引用变量的目的不同,其中压至底下的引用用于赋值,或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关方法 + +● invokespecial : 调用对象实例方法,通过栈顶的引用变量调用方法 +- ``是类初始化时执行的方法 +- 是对象初始化时执行的方法 + +(2) 前面所述是从字节码的角度看待对象的创建过程,现在从执行步骤的角度来分析: +● 确认类元信息是否存在 +当JVM接收到new指令时,首先在`metaspace`内检查需要创建的类元信息是否存在 +若不存在,在双亲委派模式下,使用当前类加载器以`ClassLoader+包名+类名`为Key进行查找对应的.class文件 +- 如果没有找到文件,则抛出`ClassNotFoundException` +- 如果找到,则进行类加载,并生成对应的Class类对象 + +● 分配对象内存 +首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可(4个字节),接着在堆中划分一块内存给新对象 +在分配内存空间时,需要进行同步操作,比如采用CAS失败重试、区域加锁等方式保证分配操作的原子性 + +● 设定默认值 +成员变量值都需要设定为默认值,即各种不同形式的零值 + +● 设置对象头 +设置新对象的哈希码、GC信息、锁信息对象所属的类元信息等 +这个过程的具体设置方式取决于JVM实现 + +● 执行init方法 +初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量 + +# 1 对象的创建 +当虚拟机遇到一条含有new的指令时,会进行一系列对象创建的操作 + +1.检查常量池中是否有要创建的这个对象所属类的符号引用 + - 若无,说明这个类还没有被定义!抛ClassNotFoundException + - 若有,转2 + + +2.检查这个符号引用所代表的类是否已被JVM加载 + - 若否,就找该类的class文件,并加载进方法区 + - 若是,转3 + +3.根据方法区中该类的信息确定该类所需的内存大小 + +一个对象所需的内存大小是在这个对象所属类被定义完就能确定的! + +且一个类所生产的所有对象的内存大小是一样的! + +JVM在一个类被加载进方法区的时候就知道该类生产的每一个对象所需要的内存大小 +从堆中划分一块对应大小的内存空间给新的对象,分配堆中内存有两种方式 +- 指针碰撞(Bump the Pointer) +如果JVM的垃圾收集器采用**复制算法**或**标记-整理算法**,那么堆中空闲内存是完整的区域,并且空闲内存和已使用内存之间由一个指针标记. +那么当为一个对象分配内存时,只需移动指针即可.因此,这种在完整空闲区域上通过移动指针来分配内存的方式就叫做"指针碰撞" +- 空闲列表 (Free List) +如果JVM的GC器采用**标记-清除算法**,那么堆中空闲区域和已使用区域交错,因此需要用一张“空闲列表”来记录堆中哪些区域是空闲区域,从而在创建对象的时候根据这张“空闲列表”找到空闲区域,并分配内存 + +综上所述:JVM究竟采用哪种内存分配方法,取决于它使用了何种GC器 +为对象中的成员变量赋上初始值(默认初始化) + 4. 设置对象头(Object Header) + 5. 调用对象的构造函数进行初始化 + +至此,整个对象的创建过程就完成了 + +# 2 对象的内存布局 +一个对象从逻辑角度看,由域和方法构成 +从物理角度来看,对象是存储在堆中的一串二进制数 + +对象在内存中存储的布局分三部分 + +- 对象头(Header) +- 实例数据(Instance Data) +- 对齐补充(Padding) + +## 2.1 对象头 +- 存储对象在运行过程中自身所需要的一些数据 +哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等 + - 类型指针 + 即对象指向它的类元数据的指针,JVM通过该指针能确定这个对象是哪个类的实例. + 另外,如果对象是一个数组,那么对象头中还要包含数组长度(因为从数组的元数据无法确定数组的大小). + +## 2.2 实例数据 +实例数据部分就是程序定义的各种字段的内容,包含父/子类的都会记录下来 + +## 2.3 对齐填充(并非必然存在,无特别含义,仅起占位符作用) +HotSpot要求对象的大小必须是8字节的整数倍 +由于对象起始地址必须是8字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为8的整数倍 + +# 3 访问对象的过程 +栈上的reference数据存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式 +- 句柄访问方式 +Java堆中需要有一块叫做"句柄池"的内存,存放所有对象的地址和所有对象所属类的类信息 +![](http://upload-images.jianshu.io/upload_images/4685968-0cef2d0c4cf527d0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +reference中存放的是对象在句柄池中的地址. +访问对象时,首先需要通过reference找到该对象的句柄,然后根据句柄中对象的地址再访问对象 +- 直接指针访问方式 +reference直接存放对象地址,从而不需要句柄池,通过引用能够直接访问对象 +但对象所在的内存空间中需要额外的策略存储对象所属的类信息的地址 +![](http://upload-images.jianshu.io/upload_images/4685968-ca169ca1646753a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 两种方式的比较 +使用句柄最大好处是reference中存储的是稳定的句柄地址,在对象被移动时也只改变句柄中的实例数据指针,而reference本身不需要修改 +`而HotSpot采用直接指针访问方式`,因为它只需一次寻址操作,节省了一次指针定位的时间开销,对象的访问又十分频繁,从而性能比句柄访问方式快一倍 + + + + + diff --git "a/Java/JVM/JVM\345\256\236\346\210\230---\347\261\273\345\212\240\350\275\275\347\232\204\350\277\207\347\250\213.md" "b/Java/JVM/JVM\345\256\236\346\210\230---\347\261\273\345\212\240\350\275\275\347\232\204\350\277\207\347\250\213.md" new file mode 100644 index 0000000000..b7400587b9 --- /dev/null +++ "b/Java/JVM/JVM\345\256\236\346\210\230---\347\261\273\345\212\240\350\275\275\347\232\204\350\277\207\347\250\213.md" @@ -0,0 +1,275 @@ +任何程序都需要加载到内存才能与CPU进行交流 +同理, 字节码.class文件同样需要加载到内存中,才可以实例化类 +`ClassLoader`的使命就是提前加载.class 类文件到内存中 +在加载类时,使用的是Parents Delegation Model(溯源委派加载模型) + +Java的类加载器是一个运行时核心基础设施模块,主要是在启动之初进行类的加载、链接、初始化 +![Java 类加载过程](https://upload-images.jianshu.io/upload_images/4685968-bfddab53299e22e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 第一步,Load阶段 +读取类文件产生二进制流,并转为特定数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应类的java.lang.Class实例 + +## 第二步,Link阶段 +包括验证、准备、解析三个步骤 +- 验证是更详细的校验,比如final是否合规、类型是否正确、静态变量是否合理等 +- 准备阶段是为静态变量分配内存,并设定默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局 +## 第三步,Init 阶段 +执行类构造器 方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值 + + 类加载是一个将.class字节码文件实例化成Class对象并进行相关初始化的过程。 +在这个过程中,JVM会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过的静态代码块、静态变量赋值语句等。 +某些类在使用时,也可以按需由类加载器进行加载。 + +全小写的class是关键字,用来定义类 +而首字母大写的Class,它是所有class的类 +这句话理解起来有难度,类已经是现实世界中某种事物的抽象,为什么这个抽象还是另外一个类Class的对象? +示例代码如下: +![](https://upload-images.jianshu.io/upload_images/4685968-341974cb59ee682a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-7ebceb2445fc0725.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-5bd82815858024ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a82c475218ac4970.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + ● 第1处说明: +Class类下的`newInstance()`在JDK9中已经置为过时,使用`getDeclaredConstructor().newInstance()`的方式 +着重说明一下new与newInstance的区别 +- new是强类型校验,可以调用任何构造方法,在使用new操作的时候,这个类可以没有被加载过 +- 而Class类下的newInstance是弱类型,只能调用无参构造方法 + - 如果没有默认构造方法,就拋出`InstantiationException`异常; + - 如果此构造方法没有权限访问,则拋 `IllegalAccessException`异常 + +Java 通过类加载器把类的实现与类的定义进行解耦,所以是实现面向接口编程、依赖倒置的必然选择。 + +● 第2处说明: +可以使用类似的方式获取其他声明,如注解、方法等 +![类的反射信息](https://upload-images.jianshu.io/upload_images/4685968-7d54a82692945875.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +● 第3处说明: private 成员在类外是否可以修改? +通过`setccessible(true)`,即可使用Class类的set方法修改其值 +如果没有这一步,则抛出如下异常: +![](https://upload-images.jianshu.io/upload_images/4685968-47acf654ed276c95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +### 类加载器 +类加载器是如何定位具体的类文件并读取的呢? + +在类加载器家族中存在着类似人类社会的权力等级制度 + +- 最高层的`Bootstrap` +在JVM启动时创建的,通常由与操作系统相关的本地代码实现,是最根基的类加载器,负责装载最核心的Java类,比如Object、System、 String ,Java运行时的rt.jar等jar包 +- JDK9的`Platform ClassLoader` +负责加载\lib\ext目录中的,或者java.ext.dirs系统变量指定的路径中的所以类库; +加载一些扩展的系统类,比如XML、加密、压缩相关的功能类等; +JDK9之前是`Extension ClassLoader`. +- 第三层 `Application ClassLoader` +应用类加载器,主要是加载用户定义的`CLASSPATH`路径下的类 + +第二、三层类加载器为Java语言实现,用户也可以自定义类加载器 +查看本地类加载器的方式如下: +![](https://upload-images.jianshu.io/upload_images/4685968-c6a9d7f0910d3bdc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +在JDK8环境中,执行结果如下 +![](https://upload-images.jianshu.io/upload_images/4685968-a1b077e3a4bc4af3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +AppClassLoader的Parent为Bootstrap,它是通过C/C++实现的,并不存在于JVM体系内,所以输出为 null +![](https://upload-images.jianshu.io/upload_images/4685968-d6de545693f4fcb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类 +如果低层次的类加载器想加载一个未知类,要非常礼貌地向上逐级询问:“请问,这个类已经加载了吗?” +被询问的高层次类加载器会自问两个问题 +- 我是否已加载过此类 +- 如果没有,是否可以加载此类 + +只有当所有高层次类加载器在两个问题的答案均为“否”时,才可以让当前类加载器加载这个未知类 +左侧绿色箭头向上逐级询问是否已加载此类,直至`Bootstrap ClassLoader`,然后向下逐级尝试是否能够加载此类,如果都加载不了,则通知发起加载请求的当前类加载器,准予加载 +在右侧的三个小标签里,列举了此层类加载器主要加载的代表性类库,事实上不止于此 + +通过如下代码可以查看Bootstrap 所有已加载类库 +![](https://upload-images.jianshu.io/upload_images/4685968-840a04505b5cb5a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +执行结果 +![](https://upload-images.jianshu.io/upload_images/4685968-cb57ddcff0fd24ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +Bootstrap加载的路径可以追加,不建议修改或删除原有加载路径 +在JVM中增加如下启动参数,则能通过`Class.forName`正常读取到指定类,说明此参数可以增加Bootstrap的类加载路径: +``` +-Xbootclasspath/a:/Users/sss/book/ easyCoding/byJdk11/src +``` +如果想在启动时观察加载了哪个jar包中的哪个类,可以增加 +``` +-XX:+TraceClassLoading +``` +此参数在解决类冲突时非常实用,毕竟不同的JVM环境对于加载类的顺序并非是一致的 +有时想观察特定类的加载上下文,由于加载的类数量众多,调试时很难捕捉到指定类的加载过程,这时可以使用条件断点功能 +比如,想查看HashMap的加载过程,在loadClass处打个断点,并且在condition框内输入如图 +![设置条件断点](https://upload-images.jianshu.io/upload_images/4685968-6a294b8a75dac812.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +### JVM如何确立每个类在JVM的唯一性 +类的全限定名和加载这个类的类加载器的ID + +在学习了类加载器的实现机制后,知道双亲委派模型并非强制模型,用户可以自定义类加载器,在什么情况下需要自定义类加载器呢? +- 隔离加载类 +在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境 +比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包 +- 修改类加载方式 +类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载 +- 扩展加载源 +比如从数据库、网络,甚至是电视机机顶盒进行加载 +- 防止源码泄露 +Java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。 + +实现自定义类加载器的步骤 +- 继承ClassLoader +- 重写findClass()方法 +- 调用defineClass()方法 + +一个简单的类加载器实现的示例代码如下 +![](https://upload-images.jianshu.io/upload_images/4685968-7bb954b558e90030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +由于中间件一般都有自己的依赖jar包,在同一个工程内引用多个框架时,往往被迫进行类的仲裁 +按某种规则jar包的版本被统一指定, 导致某些类存在包路径、类名相同的情况,就会引起类冲突,导致应用程序出现异常 +主流的容器类框架都会自定义类加载器,实现不同中间件之间的类隔离,有效避免了类冲突。 +# 1 加载的定位 +> “加载”是“类加载”(Class Loading)过程的第一步 +## 1.1 加载的过程 +在加载的过程中,JVM主要做3件事情 +- 通过一个类的全限定名来获取定义此类的二进制字节流(class文件) +在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化的条件时,就根据要被初始化的这个类的全限定名找到该类的二进制字节流,开始加载过程 +- 将这个字节流的静态存储结构转化为方法区的运行时数据结构 +- 在内存中创建一个该类的java.lang.Class对象,作为方法区该类的各种数据的访问入口 + +程序在运行中所有对该类的访问都通过这个类对象,也就是这个Class对象是提供给外界访问该类的接口 +## 1.2 加载源 +JVM规范对于加载过程给予了较大的宽松度.一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取 +- zip包 +Jar、War、Ear等 +- 其它文件生成 +由JSP文件中生成对应的Class类. +- 数据库中 +将二进制字节流存储至数据库中,然后在加载时从数据库中读取.有些中间件会这么做,用来实现代码在集群间分发 +- 网络 +从网络中获取二进制字节流.典型就是Applet. +- 运行时计算生成 +动态代理技术,用PRoxyGenerator.generateProxyClass为特定接口生成形式为"*$Proxy"的代理类的二进制字节流. +## 1.3 类和数组加载过程的区别 +数组也有类型,称为“数组类型”.如: +``` +String[] str = new String[10]; +``` +这个数组的数组类型是`Ljava.lang.String`,而String只是这个数组的元素类型 + +当程序在运行过程中遇到new关键字创建一个数组时,由JVM直接创建数组类,再由类加载器创建数组中的元素类型. + +而普通类的加载由类加载器创建.既可以使用系统提供的引导类加载器,也可以由用户自定义的类加载器完成(即重写一个类加载器的loadClass()方法) +## 1.4 加载过程的注意点 +- JVM规范并未给出类在方法区中存放的数据结构 +类完成加载后,二进制字节流就以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,虚拟机规范并没有指定 +- JVM规范并没有指定Class对象存放的位置 +在二进制字节流以特定格式存储在方法区后,JVM会创建一个java.lang.Class类的对象,作为本类的外部访问接口 +既然是对象就应该存放在Java堆中,不过JVM规范并没有给出限制,不同的虚拟机根据自己的需求存放这个对象 +HotSpot将Class对象存放在方法区. +- 加载阶段和链接阶段是交叉的 +类加载的过程中每个步骤的开始顺序都有严格限制,但每个步骤的结束顺序没有限制.也就是说,类加载过程中,必须按照如下顺序开始: +>加载 -> 链接 -> 初始化 + +但结束顺序无所谓,因此由于每个步骤处理时间的长短不一就会导致有些步骤会出现交叉 +# 2 验证 +验证阶段比较耗时,它非常重要但不一定必要(因为对程序运行期没有影响),如果所运行的代码已经被反复使用和验证过,那么可以使用`-Xverify:none`参数关闭,以缩短类加载时间 +## 2.1 验证的目的 +保证二进制字节流中的信息符合虚拟机规范,并没有安全问题 +## 2.2 验证的必要性 +虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行.也就是说,Java语言的安全性是通过编译器来保证的. + +但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。 + +通过上文可知,虚拟机规范中没有限制二进制字节流的来源,在字节码层面上,上述Java代码无法做到的都是可以实现的,至少语义上是可以表达出来的,为了防止字节流中有安全问题,需要验证! +## 2.3 验证的过程 +- 文件格式验证 +验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机处理. +本验证阶段是基于二进制字节流进行的,只有`通过本阶段验证,才被允许存到方法区` +后面的三个验证阶段都是基于方法区的存储结构进行,不会再直接操作字节流. + +> 通过上文可知,加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区 +> 而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区 +> 也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作 +这个过程印证了:加载和验证是交叉进行的 + +- 元数据验证 +对字节码描述信息进行语义分析,确保符合Java语法规范. +- 字节码验证 +本阶段是验证过程的最复杂的一个阶段. +本阶段对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件. +字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全 +- 符号引用验证 +发生在JVM将符号引用转化为直接引用的时候,这个转化动作发生在解析阶段,对类自身以外的信息进行匹配校验,确保解析能正常执行. +# 3 准备 +完成两件事情 +- 为已在方法区中的类的静态成员变量分配内存 +- 为静态成员变量设置初始值 +初始值为0、false、null等 +![](http://upload-images.jianshu.io/upload_images/4685968-f62a56c034c51820.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +``` +public static final int value = 123; +``` +准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性中,在准备阶段就将constantValue的值赋给该字段(此处将value赋为123). +# 4 解析 +解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程. +# 5 初始化 +真正开始执行类中定义的Java程序代码(或者说是字节码) +初始化阶段就是执行类构造器clinit()的过程. + +clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。 +## 5.1 初始化过程的注意点 +- clinit()方法是IDE自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,IDE收集的顺序是由语句在源文件中出现的顺序所决定的. +- 静态代码块只能访问到出现在静态代码块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问. +``` +public class Test { + static { + i=0; + System.out.println(i);//编译失败:"非法向前引用" + } + static int i = 1; +} +``` +- 实例构造器init()需要显示调用父类构造函数,而类的clinit()不需要调用父类的类构造函数,虚拟机会确保子类的clinit()方法执行前已经执行完毕父类的clinit()方法.因此在JVM中第一个被执行的clinit()方法的类肯定是java.lang.Object. +- 如果一个类/接口中没有静态代码块,也没有静态成员变量的赋值操作,那么编译器就不会为此类生成clinit()方法. +- 接口也需要通过clinit()方法为接口中定义的静态成员变量显示初始化。 +- 接口中不能使用静态代码块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成clinit()方法.不同的是,执行接口的clinit()方法不需要先执行父接口的clinit()方法.只有当父接口中的静态成员变量被使用到时才会执行父接口的clinit()方法. +- 虚拟机会保证在多线程环境中一个类的clinit()方法别正确地加锁,同步.当多条线程同时去初始化一个类时,只会有一个线程去执行该类的clinit()方法,其它线程都被阻塞等待,直到活动线程执行clinit()方法完毕. + +> 其他线程虽会被阻塞,只要有一个clinit()方法执行完,其它线程唤醒后不会再进入clinit()方法.同一个类加载器下,一个类型只会初始化一次. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\343\200\207)---\347\256\200\344\273\213.md" "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\343\200\207)---\347\256\200\344\273\213.md" new file mode 100644 index 0000000000..fe72d78775 --- /dev/null +++ "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\343\200\207)---\347\256\200\344\273\213.md" @@ -0,0 +1,28 @@ +![](https://upload-images.jianshu.io/upload_images/4685968-33035abe8ef76f8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-cf91eb08b7f1bcc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-8a997d2ecc519ba3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c0d46a45f844ac9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-127a9f968b9f2bca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-d21348cd6b5a6a2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c549d2ebba4007c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4f04d0a3ecdd8090.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 收获 +![](https://upload-images.jianshu.io/upload_images/4685968-d5e1011563b06859.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-48afdfa386f84979.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-9c4a89f78443980b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-02999327f173ffd5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-b1d769508ccc15b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 计划安排 +## 基于JDK命令行工具的监控 +![](https://upload-images.jianshu.io/upload_images/4685968-19b4acab0cf50a41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3ccb5c411b7a9942.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 基于Btrace的监控调试 +![](https://upload-images.jianshu.io/upload_images/4685968-273b74fc03cb7e7f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## Tomcat性能监控与调优 +![](https://upload-images.jianshu.io/upload_images/4685968-91b0634596ca43be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## Nginx性能监控与调优 +![](https://upload-images.jianshu.io/upload_images/4685968-090cbb1c0a829cdd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## JVM层GC调优 +![](https://upload-images.jianshu.io/upload_images/4685968-b8579d32e4aa76ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## JVM字节码与Java代码层调优 +![](https://upload-images.jianshu.io/upload_images/4685968-8bad9b8d0c9cc3dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) diff --git "a/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\200)---\345\237\272\344\272\216JDK\345\221\275\344\273\244\350\241\214\347\232\204\347\233\221\346\216\247.md" "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\200)---\345\237\272\344\272\216JDK\345\221\275\344\273\244\350\241\214\347\232\204\347\233\221\346\216\247.md" new file mode 100644 index 0000000000..ec0dc4b3d6 --- /dev/null +++ "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\200)---\345\237\272\344\272\216JDK\345\221\275\344\273\244\350\241\214\347\232\204\347\233\221\346\216\247.md" @@ -0,0 +1,65 @@ +# 1 JVM的参数类型 +![](https://upload-images.jianshu.io/upload_images/4685968-c3d630c35921ef3e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 标准参数 +![](https://upload-images.jianshu.io/upload_images/4685968-d9a2caed3c70e363.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## X 参数 +![](https://upload-images.jianshu.io/upload_images/4685968-36aa325e5aa882d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![JDK8默认混合模式](https://upload-images.jianshu.io/upload_images/4685968-d51270fdcaa59b7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![更为编译执行](https://upload-images.jianshu.io/upload_images/4685968-e63d2a9dc4b84f81.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## XX 参数 +![](https://upload-images.jianshu.io/upload_images/4685968-df514d5bb6b1ba93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-ee3ab014c641a459.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-eb6ad233da284eb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-dd0e0efc049f68ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 2 查看JVM运行时参数 +![](https://upload-images.jianshu.io/upload_images/4685968-3dc1345851a41222.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3efa76cbfa5f3729.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 查看XX选项的值 +- -XX:+PrintCommandLineFlags +- -XX:+PrintFlagsInitial -XX:+PrintFlagsFinal +![](https://upload-images.jianshu.io/upload_images/4685968-3365ede0c36c24e7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-2fa01042c93bb9ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![重定向到文本文件](https://upload-images.jianshu.io/upload_images/4685968-ba9262a02db7b704.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## jps +![jps](https://upload-images.jianshu.io/upload_images/4685968-b001d1166ef29bd4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![jps -l](https://upload-images.jianshu.io/upload_images/4685968-7fd046c9ca7419f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## jinfo +![](https://upload-images.jianshu.io/upload_images/4685968-958eb9084e37765d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 3 jstat查看JVM统计信息 +![](https://upload-images.jianshu.io/upload_images/4685968-6152877a6fd3d12f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 3.1 类装载 +![](https://upload-images.jianshu.io/upload_images/4685968-fcf19859fe9853c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 3.2 GC +![](https://upload-images.jianshu.io/upload_images/4685968-cfcd242ffae74229.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-0673720236b8c6dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## -gc输出结果 +![](https://upload-images.jianshu.io/upload_images/4685968-21033eab9130f123.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### JVM 的内存结构 +![](https://upload-images.jianshu.io/upload_images/4685968-d1b10aef1ac30942.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## JIT 编译 +![](https://upload-images.jianshu.io/upload_images/4685968-21d0f29d0f6ebbcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-b58d13ff46ce4199.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 4 jmap + MAT 实战内存溢出 +## 堆区 +![](https://upload-images.jianshu.io/upload_images/4685968-1799898c82d15414.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-59caf7a72915c7c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 非堆区 +![](https://upload-images.jianshu.io/upload_images/4685968-0ad3bbee4eb603b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3e2f874a0e5fca13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-7ef84c46801894f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 5 导出内存映像文件 +## OutofMemory(OOM)相关的选项 +如果程序发生了OOM后,JVM可以配置一些选项来做些善后工作,比如把内存给dump下来,或者自动采取一些别的动作 +- -XX:+HeapDumpOnOutOfMemoryError +在内存出现OOM的时候,把Heap转存(Dump)到文件以便后续分析,文件名通常是`java_pid.hprof` +- -XX:HeapDumpPath= +指定heap转存文件的存储路径,需要指定的路径下有足够的空间来保存转存文件 +- -XX:OnOutOfMemoryError +指定一个可行性程序或者脚本的路径,当发生OOM的时候,去执行这个脚本 +![](https://upload-images.jianshu.io/upload_images/4685968-6c2f25ca31c79899.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-38da79139ddf1ff7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-5d3a5066ab57066d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-d368c169ca25e061.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-2d0c94547582fdbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3d4c21c5f43717de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 6 MAT分析内存溢出 diff --git "a/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\203)---GC-\350\260\203\344\274\230.md" "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\203)---GC-\350\260\203\344\274\230.md" new file mode 100644 index 0000000000..a6f030693f --- /dev/null +++ "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\270\203)---GC-\350\260\203\344\274\230.md" @@ -0,0 +1,26 @@ +# 0 主要内容 +![](https://upload-images.jianshu.io/upload_images/4685968-6aa986aa1a8eff2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-7b555dcce7b4a5cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 1 内存结构 +## 1.1 运行时数据区 +![](https://upload-images.jianshu.io/upload_images/4685968-b700be36cd98f2ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![https://docs.oracle.com/javase/specs/jvms/se8/html/index.html](https://upload-images.jianshu.io/upload_images/4685968-01c553f684a22519.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### PC 寄存器 +![](https://upload-images.jianshu.io/upload_images/4685968-3c6cfeb0a9e3c47c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-6d6c5759cc058edc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### JVM 栈 +![](https://upload-images.jianshu.io/upload_images/4685968-88a03c5ffc39ed60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-75030c323b8410b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 堆 +![](https://upload-images.jianshu.io/upload_images/4685968-f5dd962dce7aca56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-335dada6197f182c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 方法区 +![](https://upload-images.jianshu.io/upload_images/4685968-e1e0a7133ab2238d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-56edc462426ab336.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 运行时常量池 +![](https://upload-images.jianshu.io/upload_images/4685968-5ba743e46716fd27.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-85127110efc744f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 本地方法栈 +![](https://upload-images.jianshu.io/upload_images/4685968-713d4580316a457c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-9418b3555ae10a94.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-cb21e2dc0d99a35d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) diff --git "a/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\272\214)---VM-\347\233\221\346\216\247.md" "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\272\214)---VM-\347\233\221\346\216\247.md" new file mode 100644 index 0000000000..25f17135d6 --- /dev/null +++ "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\344\272\214)---VM-\347\233\221\346\216\247.md" @@ -0,0 +1,33 @@ +![](https://upload-images.jianshu.io/upload_images/4685968-a9e3e1b3e8423961.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 1 监控本地java进程 +![](https://upload-images.jianshu.io/upload_images/4685968-7afa05325eebf996.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![jvisualvm](https://upload-images.jianshu.io/upload_images/4685968-20b285175202099b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![ 可直接监视本地 Java 线程](https://upload-images.jianshu.io/upload_images/4685968-eb00cc4341eea83a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c31370bf86a70428.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-682ce936dfb2aafb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c4274175ac8106da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-e0f0f92f558be1fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +导入 +![](https://upload-images.jianshu.io/upload_images/4685968-c9a088a5a3909394.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-049a5e2216db1607.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-1923ddeccc6e0bfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-9b3757961f7c0589.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-002269794e5d97b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-cf0e1cb18256e2aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 抽样器 +![](https://upload-images.jianshu.io/upload_images/4685968-8b81d75de8c5c16e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +由此,不需再使用 +``` +System.out.println(System.currentTimeMillis()); +``` +验证方法执行时间!!! +![](https://upload-images.jianshu.io/upload_images/4685968-345083285ef6cbbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +插件 +![](https://upload-images.jianshu.io/upload_images/4685968-81c0a86c878f26cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![https://visualvm.github.io/pluginscenters.html](https://upload-images.jianshu.io/upload_images/4685968-c0017c6ad2eba92b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3cc8eed7afd2c2fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +![](https://upload-images.jianshu.io/upload_images/4685968-b301857c5fdae059.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-fd57a78ced8651d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +安装无法连接到服务器!!! +# 2 监控远程的java进程 diff --git "a/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\345\205\253)---Java\344\273\243\347\240\201\345\261\202\350\260\203\344\274\230\357\274\210\345\255\227\350\212\202\347\240\201\346\214\207\344\273\244\357\274\211.md" "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\345\205\253)---Java\344\273\243\347\240\201\345\261\202\350\260\203\344\274\230\357\274\210\345\255\227\350\212\202\347\240\201\346\214\207\344\273\244\357\274\211.md" new file mode 100644 index 0000000000..2307e9da85 --- /dev/null +++ "b/Java/JVM/JVM\346\200\247\350\203\275\350\260\203\344\274\230\345\256\236\346\210\230(\345\205\253)---Java\344\273\243\347\240\201\345\261\202\350\260\203\344\274\230\357\274\210\345\255\227\350\212\202\347\240\201\346\214\207\344\273\244\357\274\211.md" @@ -0,0 +1,243 @@ +# 0 主要内容 +![](https://upload-images.jianshu.io/upload_images/4685968-98d2f60376422b87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +#1 JVM字节码指令与 javap +![](https://upload-images.jianshu.io/upload_images/4685968-b7c70f5b346ef29d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 样例代码 +![](https://upload-images.jianshu.io/upload_images/4685968-562849e9307f4f8a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 将结果重定向到一个 txt 文件中,便于分析 +![](https://upload-images.jianshu.io/upload_images/4685968-864542f0bc938769.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## Field Descriptors +字段描述符表示类,实例或局部变量的类型。它是由语法生成的一系列字符: +``` +FieldDescriptor: + FieldType + +FieldType: + BaseType + ObjectType + ArrayType + +BaseType: + B + C + D + F + I + J + S + Z + +ObjectType: + L ClassName ; + +ArrayType: + [ ComponentType + +ComponentType: + FieldType +``` +`L ClassName;`: 引用类型 +`[` : 数组类型 +都是ASCII字符 + +ClassName表示以内部形式编码的二进制类或接口名称 + +字段描述符的解释类型如表 +![](https://upload-images.jianshu.io/upload_images/4685968-16239c4760aaa62e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +表示数组类型的字段描述符仅在表示具有255或更少维度的类型时才有效 + +int类型的实例变量的字段描述符就是I. + +Object类型的实例变量的字段描述符是`Ljava / lang / Object` +请注意,使用的是类Object的二进制名称的内部形式。 + +一个多维double数组实例变量的字段描述符,double d [] [] [],是[[[D +## Method Descriptors +方法描述符表示方法采用的参数及其返回的值: +``` +MethodDescriptor: + ( ParameterDescriptor* ) ReturnDescriptor +``` +参数描述符表示传递给方法的参数: +``` +ParameterDescriptor: + FieldType +``` +返回描述符表示从方法返回的值的类型。它是由语法生成的一系列字符: +``` +ReturnDescriptor: + FieldType + VoidDescriptor + +VoidDescriptor: + V +``` +字符V表示该方法没有返回值(其返回类型为void) + +方法描述符仅在表示总长度为255或更小的方法参数时才有效,其中该长度包括在实例或接口方法调用的情况下对此的贡献 +总长度是通过对各个参数的贡献求和来计算的,其中long或double类型的参数对长度贡献两个单位,而任何其他类型的参数贡献一个单位 + +### 方法的方法描述符 +比如方法 +``` +Object m(int i, double d, Thread t) {..} +``` +是`(IDLjava / lang / Thread;)Ljava / lang / Object;` +注意,使用了Thread和Object的二进制名称的内部形式 + +无论m是类方法还是实例方法,对于m的方法描述符是相同的 +虽然传递了一个实例方法,但是除了预期的参数之外,对当前类实例的引用不会反映在方法描述符中 +对此的引用由调用实例方法的Java虚拟机的方法调用指令隐式传递 +对此的引用不会传递给类方法。 +``` +Classfile /Volumes/doc/IDEAProjects/monitor_tuning/target/classes/com/sss/monitortuning/cp8/Test1.class + Last modified 2019-1-4; size 629 bytes + MD5 checksum 91438ed9eb3506def6a5580d6b448ebb + Compiled from "Test1.java" +public class com.sss.monitortuning.cp8.Test1 + minor version: 0 + major version: 52 // JDK版本号 + flags: ACC_PUBLIC, ACC_SUPER +// 常量池 +Constant pool: + #1 = Methodref #5.#24 // java/lang/Object."":()V + #2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream; + #3 = Methodref #27.#28 // java/io/PrintStream.println:(I)V + #4 = Class #29 // com/sss/monitortuning/cp8/Test1 即类名 + #5 = Class #30 // java/lang/Object + #6 = Utf8 + #7 = Utf8 ()V + #8 = Utf8 Code + #9 = Utf8 LineNumberTable + #10 = Utf8 LocalVariableTable + #11 = Utf8 this + #12 = Utf8 Lcom/sss/monitortuning/cp8/Test1; + #13 = Utf8 main + #14 = Utf8 (Ljava/lang/String;)V + #15 = Utf8 args + #16 = Utf8 Ljava/lang/String; + #17 = Utf8 a + #18 = Utf8 I + #19 = Utf8 b + #20 = Utf8 c + #21 = Utf8 MethodParameters + #22 = Utf8 SourceFile + #23 = Utf8 Test1.java + #24 = NameAndType #6:#7 // "":()V + #25 = Class #31 // java/lang/System + #26 = NameAndType #32:#33 // out:Ljava/io/PrintStream; + #27 = Class #34 // java/io/PrintStream + #28 = NameAndType #35:#36 // println:(I)V + #29 = Utf8 com/sss/monitortuning/cp8/Test1 + #30 = Utf8 java/lang/Object + #31 = Utf8 java/lang/System + #32 = Utf8 out + #33 = Utf8 Ljava/io/PrintStream; + #34 = Utf8 java/io/PrintStream + #35 = Utf8 println + #36 = Utf8 (I)V +{ + public com.sss.monitortuning.cp8.Test1(); + descriptor: ()V + flags: ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method java/lang/Object."":()V + 4: return + LineNumberTable: + line 7: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/sss/monitortuning/cp8/Test1; + + public static void main(java.lang.String); + descriptor: (Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=4, args_size=1 + 0: iconst_2 + 1: istore_1 + 2: iconst_3 + 3: istore_2 + 4: iload_1 + 5: iload_2 + 6: iadd + 7: istore_3 + 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; + 11: iload_3 + 12: invokevirtual #3 // Method java/io/PrintStream.println:(I)V + 15: return + LineNumberTable: + line 9: 0 + line 10: 2 + line 11: 4 + line 12: 8 + line 13: 15 + LocalVariableTable: + Start Length Slot Name Signature + 0 16 0 args Ljava/lang/String; + 2 14 1 a I + 4 12 2 b I + 8 8 3 c I + MethodParameters: + Name Flags + args +} +SourceFile: "Test1.java" + + /*** + public static void main(java.lang.String); + descriptor: (Ljava/lang/String;)V + flags: ACC_PUBLIC, ACC_STATIC + Code: + # 操作数栈的深度2 + # 本地变量表最大长度(slot为单位),64位的是2,其他是1,索引从0开始,如果是非static方法索引0代表this,后面是入参,后面是本地变量 + # 1个参数,实例方法多一个this参数 + stack=2, locals=4, args_size=1 + 0: iconst_2 #常量2压栈 + 1: istore_1 #出栈保存到本地变量1里面 + 2: iconst_3 #常量3压栈 + 3: istore_2 #出栈保存到本地变量2里面 + 4: iload_1 #局部变量1压栈 + 5: iload_2 #局部变量2压栈 + 6: iadd # 栈顶两个元素相加,计算结果压栈 + 7: istore_3 # 出栈保存到局部变量3里面 + 8: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; + 11: iload_3 + 12: invokevirtual #22 // Method java/io/PrintStream.println:(I)V + 15: return + LineNumberTable: + // 代码行数 : 常量池 + line 5: 0 + line 6: 2 + line 7: 4 + line 8: 8 + line 9: 15 + LocalVariableTable: + Start Length Slot Name Signature + 0 16 0 args Ljava/lang/String; + 2 14 1 a I + 4 12 2 b I + 8 8 3 c I + **/ +``` +# 基于栈的架构 +![](https://upload-images.jianshu.io/upload_images/4685968-b642515f808bfef0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +把常量2压栈,存到本地变量1中 +![](https://upload-images.jianshu.io/upload_images/4685968-2ad691ed43d31bc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +把常量3压栈,存到本地变量2中 +![](https://upload-images.jianshu.io/upload_images/4685968-7be399e19ea7592c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +将本地变量1数据压栈 +将本地变量2数据压栈 +![](https://upload-images.jianshu.io/upload_images/4685968-d1b964019cf5a0a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +栈顶两元素相加,存到本地变量3 +![](https://upload-images.jianshu.io/upload_images/4685968-4298c2d9b2e6293c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-72c447370ff99ef7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-962f378779b48b9c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 3 i++ 与 ++i +![](https://upload-images.jianshu.io/upload_images/4685968-c744b124347c1aef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +对于 for 循环,无任何区别! +# 4 字符串拼接 + diff --git "a/Java/JVM/JVM\346\214\207\344\273\244.md" "b/Java/JVM/JVM\346\214\207\344\273\244.md" new file mode 100644 index 0000000000..cb6170c8a3 --- /dev/null +++ "b/Java/JVM/JVM\346\214\207\344\273\244.md" @@ -0,0 +1,217 @@ +```java +指令码 助记符 说明 +0×00 nop 什么都不做 +0×01 aconst_null 将null推送至栈顶 +0×02 iconst_m1 将int型-1推送至栈顶 +0×03 iconst_0 将int型0推送至栈顶 +0×04 iconst_1 将int型1推送至栈顶 +0×05 iconst_2 将int型2推送至栈顶 +0×06 iconst_3 将int型3推送至栈顶 +0×07 iconst_4 将int型4推送至栈顶 +0×08 iconst_5 将int型5推送至栈顶 +0×09 lconst_0 将long型0推送至栈顶 +0x0a lconst_1 将long型1推送至栈顶 +0x0b fconst_0 将float型0推送至栈顶 +0x0c fconst_1 将float型1推送至栈顶 +0x0d fconst_2 将float型2推送至栈顶 +0x0e dconst_0 将double型0推送至栈顶 +0x0f dconst_1 将double型1推送至栈顶 +0×10 bipush 将单字节的常量值(-128~127)推送至栈顶 +0×11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶 +0×12 ldc 将int, float或String型常量值从常量池中推送至栈顶 +0×13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引) +0×14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引) +0×15 iload 将指定的int型本地变量推送至栈顶 +0×16 lload 将指定的long型本地变量推送至栈顶 +0×17 fload 将指定的float型本地变量推送至栈顶 +0×18 dload 将指定的double型本地变量推送至栈顶 +0×19 aload 将指定的引用类型本地变量推送至栈顶 +0x1a iload_0 将第一个int型本地变量推送至栈顶 +0x1b iload_1 将第二个int型本地变量推送至栈顶 +0x1c iload_2 将第三个int型本地变量推送至栈顶 +0x1d iload_3 将第四个int型本地变量推送至栈顶 +0x1e lload_0 将第一个long型本地变量推送至栈顶 +0x1f lload_1 将第二个long型本地变量推送至栈顶 +0×20 lload_2 将第三个long型本地变量推送至栈顶 +0×21 lload_3 将第四个long型本地变量推送至栈顶 +0×22 fload_0 将第一个float型本地变量推送至栈顶 +0×23 fload_1 将第二个float型本地变量推送至栈顶 +0×24 fload_2 将第三个float型本地变量推送至栈顶 +0×25 fload_3 将第四个float型本地变量推送至栈顶 +0×26 dload_0 将第一个double型本地变量推送至栈顶 +0×27 dload_1 将第二个double型本地变量推送至栈顶 +0×28 dload_2 将第三个double型本地变量推送至栈顶 +0×29 dload_3 将第四个double型本地变量推送至栈顶 +0x2a aload_0 将第一个引用类型本地变量推送至栈顶 +0x2b aload_1 将第二个引用类型本地变量推送至栈顶 +0x2c aload_2 将第三个引用类型本地变量推送至栈顶 +0x2d aload_3 将第四个引用类型本地变量推送至栈顶 +0x2e iaload 将int型数组指定索引的值推送至栈顶 +0x2f laload 将long型数组指定索引的值推送至栈顶 +0×30 faload 将float型数组指定索引的值推送至栈顶 +0×31 daload 将double型数组指定索引的值推送至栈顶 +0×32 aaload 将引用型数组指定索引的值推送至栈顶 +0×33 baload 将boolean或byte型数组指定索引的值推送至栈顶 +0×34 caload 将char型数组指定索引的值推送至栈顶 +0×35 saload 将short型数组指定索引的值推送至栈顶 +0×36 istore 将栈顶int型数值存入指定本地变量 +0×37 lstore 将栈顶long型数值存入指定本地变量 +0×38 fstore 将栈顶float型数值存入指定本地变量 +0×39 dstore 将栈顶double型数值存入指定本地变量 +0x3a astore 将栈顶引用型数值存入指定本地变量 +0x3b istore_0 将栈顶int型数值存入第一个本地变量 +0x3c istore_1 将栈顶int型数值存入第二个本地变量 +0x3d istore_2 将栈顶int型数值存入第三个本地变量 +0x3e istore_3 将栈顶int型数值存入第四个本地变量 +0x3f lstore_0 将栈顶long型数值存入第一个本地变量 +0×40 lstore_1 将栈顶long型数值存入第二个本地变量 +0×41 lstore_2 将栈顶long型数值存入第三个本地变量 +0×42 lstore_3 将栈顶long型数值存入第四个本地变量 +0×43 fstore_0 将栈顶float型数值存入第一个本地变量 +0×44 fstore_1 将栈顶float型数值存入第二个本地变量 +0×45 fstore_2 将栈顶float型数值存入第三个本地变量 +0×46 fstore_3 将栈顶float型数值存入第四个本地变量 +0×47 dstore_0 将栈顶double型数值存入第一个本地变量 +0×48 dstore_1 将栈顶double型数值存入第二个本地变量 +0×49 dstore_2 将栈顶double型数值存入第三个本地变量 +0x4a dstore_3 将栈顶double型数值存入第四个本地变量 +0x4b astore_0 将栈顶引用型数值存入第一个本地变量 +0x4c astore_1 将栈顶引用型数值存入第二个本地变量 +0x4d astore_2 将栈顶引用型数值存入第三个本地变量 +0x4e astore_3 将栈顶引用型数值存入第四个本地变量 +0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置 +0×50 lastore 将栈顶long型数值存入指定数组的指定索引位置 +0×51 fastore 将栈顶float型数值存入指定数组的指定索引位置 +0×52 dastore 将栈顶double型数值存入指定数组的指定索引位置 +0×53 aastore 将栈顶引用型数值存入指定数组的指定索引位置 +0×54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置 +0×55 castore 将栈顶char型数值存入指定数组的指定索引位置 +0×56 sastore 将栈顶short型数值存入指定数组的指定索引位置 +0×57 pop 将栈顶数值弹出 (数值不能是long或double类型的) +0×58 pop2 将栈顶的一个(long或double类型的)或两个数值弹出(其它) +0×59 dup 复制栈顶数值并将复制值压入栈顶 +0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶 +0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶 +0x5c dup2 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶 +0x5d dup2_x1 <待补充> +0x5e dup2_x2 <待补充> +0x5f swap 将栈最顶端的两个数值互换(数值不能是long或double类型的) +0×60 iadd 将栈顶两int型数值相加并将结果压入栈顶 +0×61 ladd 将栈顶两long型数值相加并将结果压入栈顶 +0×62 fadd 将栈顶两float型数值相加并将结果压入栈顶 +0×63 dadd 将栈顶两double型数值相加并将结果压入栈顶 +0×64 isub 将栈顶两int型数值相减并将结果压入栈顶 +0×65 lsub 将栈顶两long型数值相减并将结果压入栈顶 +0×66 fsub 将栈顶两float型数值相减并将结果压入栈顶 +0×67 dsub 将栈顶两double型数值相减并将结果压入栈顶 +0×68 imul 将栈顶两int型数值相乘并将结果压入栈顶 +0×69 lmul 将栈顶两long型数值相乘并将结果压入栈顶 +0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶 +0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶 +0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶 +0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶 +0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶 +0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶 +0×70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶 +0×71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶 +0×72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶 +0×73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶 +0×74 ineg 将栈顶int型数值取负并将结果压入栈顶 +0×75 lneg 将栈顶long型数值取负并将结果压入栈顶 +0×76 fneg 将栈顶float型数值取负并将结果压入栈顶 +0×77 dneg 将栈顶double型数值取负并将结果压入栈顶 +0×78 ishl 将int型数值左移位指定位数并将结果压入栈顶 +0×79 lshl 将long型数值左移位指定位数并将结果压入栈顶 +0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶 +0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶 +0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶 +0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶 +0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶 +0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶 +0×80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶 +0×81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶 +0×82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶 +0×83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶 +0×84 iinc 将指定int型变量增加指定值(i++, i–, i+=2) +0×85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶 +0×86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶 +0×87 i2d 将栈顶int型数值强制转换成double型数值并将结果压入栈顶 +0×88 l2i 将栈顶long型数值强制转换成int型数值并将结果压入栈顶 +0×89 l2f 将栈顶long型数值强制转换成float型数值并将结果压入栈顶 +0x8a l2d 将栈顶long型数值强制转换成double型数值并将结果压入栈顶 +0x8b f2i 将栈顶float型数值强制转换成int型数值并将结果压入栈顶 +0x8c f2l 将栈顶float型数值强制转换成long型数值并将结果压入栈顶 +0x8d f2d 将栈顶float型数值强制转换成double型数值并将结果压入栈顶 +0x8e d2i 将栈顶double型数值强制转换成int型数值并将结果压入栈顶 +0x8f d2l 将栈顶double型数值强制转换成long型数值并将结果压入栈顶 +0×90 d2f 将栈顶double型数值强制转换成float型数值并将结果压入栈顶 +0×91 i2b 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶 +0×92 i2c 将栈顶int型数值强制转换成char型数值并将结果压入栈顶 +0×93 i2s 将栈顶int型数值强制转换成short型数值并将结果压入栈顶 +0×94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶 +0×95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 +0×96 fcmpg 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶 +0×97 dcmpl 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 +0×98 dcmpg 比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶 +0×99 ifeq 当栈顶int型数值等于0时跳转 +0x9a ifne 当栈顶int型数值不等于0时跳转 +0x9b iflt 当栈顶int型数值小于0时跳转 +0x9c ifge 当栈顶int型数值大于等于0时跳转 +0x9d ifgt 当栈顶int型数值大于0时跳转 +0x9e ifle 当栈顶int型数值小于等于0时跳转 +0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转 +0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转 +0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转 +0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转 +0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转 +0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转 +0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转 +0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转 +0xa7 goto 无条件跳转 +0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶 +0xa9 ret 返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用) +0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令) +0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令) +0xac ireturn 从当前方法返回int +0xad lreturn 从当前方法返回long +0xae freturn 从当前方法返回float +0xaf dreturn 从当前方法返回double +0xb0 areturn 从当前方法返回对象引用 +0xb1 return 从当前方法返回void +0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶 +0xb3 putstatic 为指定的类的静态域赋值 +0xb4 getfield 获取指定类的实例域,并将其值压入栈顶 +0xb5 putfield 为指定的类的实例域赋值 +0xb6 invokevirtual 调用实例方法 +0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法 +0xb8 invokestatic 调用静态方法 +0xb9 invokeinterface 调用接口方法 +0xba – +0xbb new 创建一个对象,并将其引用值压入栈顶 +0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶 +0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶 +0xbe arraylength 获得数组的长度值并压入栈顶 +0xbf athrow 将栈顶的异常抛出 +0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException +0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶 +0xc2 monitorenter 获得对象的锁,用于同步方法或同步块 +0xc3 monitorexit 释放对象的锁,用于同步方法或同步块 +0xc4 wide 当本地变量的索引超过255时使用该指令扩展索引宽度。 +``` +用法1: wide indexbyte1 indexbyte2 + +其中是iload, fload, aload, lload, dload, istore, fstore, astore, lstore, dstore, ret 之一;索引的值为无符号 (indexbyte1<<8 | indexbyte2). + +用法 2:wide iinc indexbyte1 indexbyte2 constbyte1 constbyte2 + +其中索引的值为无符号 (indexbyte1<<8 | indexbyte2);常量值为有符号(constbyte1<<8 | constbyte2) + +0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶 + +0xc6 ifnull 为null时跳转 + +0xc7 ifnonnull 不为null时跳转 + +0xc8 goto_w 无条件跳转(宽索引) + +0xc9 jsr_w 跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶 diff --git "a/Java/JVM/JVM\346\272\220\347\240\201\345\210\206\346\236\220\344\271\213synchronized.md" "b/Java/JVM/JVM\346\272\220\347\240\201\345\210\206\346\236\220\344\271\213synchronized.md" new file mode 100644 index 0000000000..2d911f2fe8 --- /dev/null +++ "b/Java/JVM/JVM\346\272\220\347\240\201\345\210\206\346\236\220\344\271\213synchronized.md" @@ -0,0 +1,145 @@ +# 1 字节码实现 +javap命令生成的字节码中包含 ** monitorenter ** 和 ** monitorexit **指令 + +synchronized关键字基于上述两个指令实现了锁的获取和释放过程,解释器执行monitorenter时会进入到`InterpreterRuntime.cpp`的`InterpreterRuntime::monitorenter`函数 +![](https://upload-images.jianshu.io/upload_images/4685968-cbcd2d465b7f83c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`JavaThread* thread`指向java中的当前线程 +`BasicObjectLock`类型的elem对象包含一个`BasicLock`类型 _lock 对象和一个指向`Object`对象的指针 _obj + +``` +class BasicObjectLock { + BasicLock _lock; + // object holds the lock; + oop _obj; +} + +``` +`BasicLock`类型 _lock 对象主要用来保存 _obj 所指向的Object对象的对象头数据 + +``` +class BasicLock { + volatile markOop _displaced_header; +} + +``` +**UseBiasedLocking**标识虚拟机是否开启偏向锁功能,如果开启则执行fast_enter逻辑,否则执行slow_enter + +# 2 偏向锁 +## 2.1 引入偏向锁的目的 +在没有多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径 +轻量级锁的获取及释放依赖多次CAS指令,而偏向锁只依赖一次CAS原子指令置换`ThreadID`,不过一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁消耗的性能必须小于之前节省下来的CAS原子操作的性能消耗,不然得不偿失 +JDK 1.6中默认开启偏向锁,可以通过`-XX:-UseBiasedLocking`来禁用偏向锁 + +在HotSpot中,偏向锁的入口位于`synchronizer.cpp`文件的`ObjectSynchronizer::fast_enter`函数: +![](https://upload-images.jianshu.io/upload_images/4685968-61d59362651ec1bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 2.2 偏向锁的获取 +由`BiasedLocking::revoke_and_rebias`方法实现,逻辑如下: +1、通过`markOop mark = obj->mark()`获取对象的markOop数据mark,即对象头的Mark Word +2、判断mark是否为可偏向状态,即mark的偏向锁标志位为 **1**,锁标志位为 **01** +3、判断mark中JavaThread的状态:如果为空,则进入步骤(4);如果指向当前线程,则执行同步代码块;如果指向其它线程,进入步骤(5); +4、通过CAS原子指令设置mark中JavaThread为当前线程ID,如果执行CAS成功,则执行同步代码块,否则进入步骤(5); +5、如果执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块; +## 2.3 偏向锁的撤销 +只有当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,偏向锁的撤销由`BiasedLocking::revoke_at_safepoint`方法实现: +![](https://upload-images.jianshu.io/upload_images/4685968-b90fe2b92d2e7e06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +1、偏向锁的撤销动作必须等待全局安全点; +2、暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态; +3、撤销偏向锁,恢复到无锁(标志位为 **01**)或轻量级锁(标志位为 **00**)的状态; + +偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用 +`-XX:BiasedLockingStartupDelay=0` +参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过 +`XX:-UseBiasedLocking=false` +参数关闭偏向锁。 +## 2.4 轻量级锁 +### 2.4.1 引入轻量级锁的目的 +在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁 +###2.4.2 轻量级锁的获取 +当关闭偏向锁功能,或多个线程竞争偏向锁导致偏向锁升级为轻量级锁,会尝试获取轻量级锁,其入口位于`ObjectSynchronizer::slow_enter` +![](https://upload-images.jianshu.io/upload_images/4685968-99e5814794fb2c0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +1、`markOop mark = obj->mark()`方法获取对象的markOop数据mark; +2、`mark->is_neutral()`方法判断mark是否为无锁状态:mark的偏向锁标志位为 **0**,锁标志位为 **01**; +3、如果mark处于无锁状态,则进入步骤(4),否则执行步骤(6); +4、把mark保存到BasicLock对象的_displaced_header字段; +5、通过CAS尝试将Mark Word更新为指向BasicLock对象的指针,如果更新成功,表示竞争到锁,则执行同步代码,否则执行步骤(6); +6、如果当前mark处于加锁状态,且mark中的ptr指针指向当前线程的栈帧,则执行同步代码,否则说明有多个线程竞争轻量级锁,轻量级锁需要膨胀升级为重量级锁; + +**假设线程A和B同时执行到临界区`if (mark->is_neutral())`**: +1、线程AB都把Mark Word复制到各自的_displaced_header字段,该数据保存在线程的栈帧上,是线程私有的; +2、`Atomic::cmpxchg_ptr`原子操作保证只有一个线程可以把指向栈帧的指针复制到Mark Word,假设此时线程A执行成功,并返回继续执行同步代码块; +3、线程B执行失败,退出临界区,通过`ObjectSynchronizer::inflate`方法开始膨胀锁; + +##### 轻量级锁的释放 + +轻量级锁的释放通过`ObjectSynchronizer::fast_exit`完成。 +![](https://upload-images.jianshu.io/upload_images/4685968-566e2aa55e6e07a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +1、确保处于偏向锁状态时不会执行这段逻辑; +2、取出在获取轻量级锁时保存在BasicLock对象的mark数据dhw; +3、通过CAS尝试把dhw替换到当前的Mark Word,如果CAS成功,说明成功的释放了锁,否则执行步骤(4); +4、如果CAS失败,说明有其它线程在尝试获取该锁,这时需要将该锁升级为重量级锁,并释放; + +### 重量级锁 + +重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。 + +##### 锁膨胀过程 +锁的膨胀过程通过`ObjectSynchronizer::inflate`函数实现 +![](https://upload-images.jianshu.io/upload_images/4685968-64df31b5d4ab16b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +膨胀过程的实现比较复杂,截图中只是一小部分逻辑,完整的方法可以查看`synchronized.cpp`,大概实现过程如下: +1、整个膨胀过程在自旋下完成; +2、`mark->has_monitor()`方法判断当前是否为重量级锁,即Mark Word的锁标识位为 **10**,如果当前状态为重量级锁,执行步骤(3),否则执行步骤(4); +3、`mark->monitor()`方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成; +4、如果当前锁处于膨胀中,说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,这里需要注意一点,虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回; +5、如果当前是轻量级锁状态,即锁标识位为 **00**,膨胀过程如下: +![](https://upload-images.jianshu.io/upload_images/4685968-4a2d2a07972348c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +1、通过omAlloc方法,获取一个可用的ObjectMonitor monitor,并重置monitor数据; +2、通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中,如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成; +3、如果CAS成功,设置monitor的各个字段:_header、_owner和_object等,并返回; + +##### monitor竞争 + +当锁膨胀完成并返回对应的monitor时,并不表示该线程竞争到了锁,真正的锁竞争发生在`ObjectMonitor::enter`方法中。 + +![](https://upload-images.jianshu.io/upload_images/4685968-d79a0bc4a1bfb748.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +1、通过CAS尝试把monitor的_owner字段设置为当前线程; +2、如果设置之前的_owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行_recursions ++ ,记录重入的次数; +3、如果之前的_owner指向的地址在当前线程中,这种描述有点拗口,换一种说法:之前_owner指向的BasicLock在当前线程栈上,说明当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程,该线程成功获得锁并返回; +4、如果获取锁失败,则等待锁的释放; + +##### monitor等待 + +monitor竞争失败的线程,通过自旋执行`ObjectMonitor::EnterI`方法等待锁的释放,EnterI方法的部分逻辑实现如下: + +![](https://upload-images.jianshu.io/upload_images/4685968-75f28ce576503368.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +1、当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ; +2、在for循环中,通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中; +3、node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起,等待被唤醒,实现如下: + +![](https://upload-images.jianshu.io/upload_images/4685968-e797fdcdc32a2f8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +4、当该线程被唤醒时,会从挂起的点继续执行,通过`ObjectMonitor::TryLock`尝试获取锁,TryLock方法实现如下: + +![](https://upload-images.jianshu.io/upload_images/4685968-17d10b24c3369844.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +其本质就是通过CAS设置monitor的_owner字段为当前线程,如果CAS成功,则表示该线程获取了锁,跳出自旋操作,执行同步代码,否则继续被挂起; + +##### monitor释放 + +当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程,具体实现位于`ObjectMonitor::exit`方法中。 + +![](https://upload-images.jianshu.io/upload_images/4685968-e0e92ecdc7a34fbc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +1、如果是重量级锁的释放,monitor中的_owner指向当前线程,即THREAD == _owner; +2、根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过`ObjectMonitor::ExitEpilog`方法唤醒该节点封装的线程,唤醒操作最终由unpark完成,实现如下: + +![](https://upload-images.jianshu.io/upload_images/4685968-3924af3785f6072b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +3、被唤醒的线程,继续执行monitor的竞争; diff --git "a/Java/Java-Fork-Join-\346\241\206\346\236\266.md" "b/Java/Java-Fork-Join-\346\241\206\346\236\266.md" new file mode 100644 index 0000000000..4eaff63355 --- /dev/null +++ "b/Java/Java-Fork-Join-\346\241\206\346\236\266.md" @@ -0,0 +1,135 @@ +#Doug Lea 关于Java 7引入的他写的Fork/Join框架的论文 + +# 0\. 摘要 +这个框架通过(递归的)把问题划分为子任务,然后并行的执行这些子任务,等所有的子任务都结束的时候,再合并最终结果的这种方式来支持并行计算编程。就设计层面来说主要是围绕如何高效的去构建和管理任务队列以及工作线程来展开的。 +# 1\. 简介 +`Fork/Join`并行方式是获取良好的并行计算性能的一种最简单同时也是最有效的设计技术。 +`Fork/Join`并行算法是我们所熟悉的分治算法的并行版本,典型的用法如下: +![](https://upload-images.jianshu.io/upload_images/4685968-15712a0db6dbdb99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`fork`将会启动一个新的并行`Fork/Join`子任务 +`join`会一直等待直到所有的子任务都结束。 +`Fork/Join`算法,如同其他分治算法一样,总是会递归的、反复的划分子任务,直到这些子任务可以用足够简单的、短小的顺序方法来执行。 +# 2\. 设计 +`Fork/Join`程序可以在任何支持以下特性的框架之上运行:框架能够让构建的子任务并行执行,并且拥有一种等待子任务运行结束的机制。 +然而,`java.lang.Thread`类(同时也包括`POSIX pthread`,这些也是`Java`线程所基于的基础)对`Fork/Join`程序来说并不是最优的选择: +* `Fork/Join`任务对同步和管理有简单的和常规的需求。相对于常规的线程来说,`Fork/Join`任务所展示的计算布局将会带来更加灵活的调度策略。例如,`Fork/Join`任务除了等待子任务外,其他情况下是不需要阻塞的。因此传统的用于跟踪记录阻塞线程的代价在这种情况下实际上是一种浪费。 +* 对于一个合理的基础任务粒度来说,构建和管理一个线程的代价甚至可以比任务执行本身所花费的代价更大。尽管粒度是应该随着应用程序在不同特定平台上运行而做出相应调整的。但是超过线程开销的极端粗粒度会限制并行的发挥。 + +简而言之,`Java`标准的线程框架对`Fork/Join`程序而言太笨重了。但是既然线程构成了很多其他的并发和并行编程的基础,完全消除这种代价或者为了这种方式而调整线程调度是不可能(或者说不切实际的)。 + +尽管这种思想已经存在了很长时间了,但是第一个发布的能系统解决这些问题的框架是`Cilk`[5]。`Cilk`和其他轻量级的框架是基于操作系统的基本的线程和进程机制来支持特殊用途的`Fork/Join`程序。这种策略同样适用于`Java`,尽管`Java`线程是基于低级别的操作系统的能力来实现的。创造这样一个轻量级的执行框架的主要优势是能够让`Fork/Join`程序以一种更直观的方式编写,进而能够在各种支持`JVM`的系统上运行。 +![](https://upload-images.jianshu.io/upload_images/4685968-b588beda5de35d5c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`FJTask`框架是基于`Cilk`设计的一种演变。框架采用和操作系统把线程映射到`CPU`上相同的方式来把任务映射到线程上。只是他们会使用`Fork/Join`程序的简单性、常规性以及一致性来执行这种映射。尽管这些框架都能适应不能形式的并行程序,他们优化了`Fork/Join`的设计: + +* 一组工作者线程池是准备好的。每个工作线程都是标准的(『重量级』)处理存放在队列中任务的线程(这地方指的是`Thread`类的子类`FJTaskRunner`的实例对象)。通常情况下,工作线程应该与系统的处理器数量一致。在`Java`中,虚拟机和操作系统需要相互结合来完成线程到处理器的映射。然后对于计算密集型的运算来说,这种映射对于操作系统来说是一种相对简单的任务。任何合理的映射策略都会导致线程映射到不同的处理器。 +* 所有的`Fork/Join`任务都是轻量级执行类的实例,而不是线程实例。在`Java`中,独立的可执行任务必须要实现`Runnable`接口并重写`run`方法。在`FJTask`框架中,这些任务将作为子类继承`FJTask`而不是`Thread`,它们都实现了`Runnable`接口。(对于上面两种情况来说,一个类也可以选择实现`Runnable`接口,类的实例对象既可以在任务中执行也可以在线程中执行。因为任务执行受到来自`FJTask`方法严厉规则的制约,子类化`FJTask`相对来说更加方便,也能够直接调用它们。) +* 我们将采用一个特殊的队列和调度原则来管理任务并通过工作线程来执行任务。这些机制是由任务类中提供的相关方式实现的:主要是由`fork`、`join`、`isDone`(一个结束状态的标示符),和一些其他方便的方法,例如调用`coInvoke`来分解合并两个或两个以上的任务。 +* 一个简单的控制和管理类(这里指的是`FJTaskRunnerGroup`)来启动工作线程池,并初始化执行一个由正常的线程调用所触发的`Fork/Join`任务(就类似于`Java`程序中的`main`方法)。 + +作为一个给程序员演示这个框架如何运行的标准实例,这是一个计算法斐波那契函数的类。 + +``` +class Fib extends FJTask { + static final int threshold = 13; + volatile int number; // arg/result + + Fib(int n) { + number = n; + } + + int getAnswer() { + if (!isDone()) + throw new IllegalStateException(); + return number; + } + + public void run() { + int n = number; + if (n <= threshold) // granularity ctl + number = seqFib(n); + else { + Fib f1 = new Fib(n - 1); + Fib f2 = new Fib(n - 2); + coInvoke(f1, f2); + number = f1.number + f2.number; + } + } + + public static void main(String[] args) { + try { + int groupSize = 2; // for example + FJTaskRunnerGroup group = new FJTaskRunnerGroup(groupSize); + Fib f = new Fib(35); // for example + group.invoke(f); + int result = f.getAnswer(); + System.out.println("Answer: " + result); + } catch (InterruptedException ex) { + } + } + + int seqFib(int n) { + if (n <= 1) return n; + else return seqFib(n − 1) + seqFib(n − 2); + } +} +``` +在保持性能的同时这个程序仍然维持着`Java`多线程程序的可移植性。对程序员来说通常有两个参数值的他们关注 +* 对于工作线程的创建数量,通常情况下可以与平台所拥有的处理器数量保持一致(或者更少,用于处理其他相关的任务,或者有些情况下更多,来提升非计算密集型任务的性能) +* 一个粒度参数代表了创建任务的代价会大于并行化所带来的潜在的性能提升的临界点。这个参数更多的是取决于算法而不是平台。通常在单处理器上运行良好的临界点,在多处理器平台上也会发挥很好的效果。作为一种附带的效益,这种方式能够与`Java`虚拟机的动态编译机制很好的结合,而这种机制在对小块方法的优化方面相对于单块的程序来说要好。这样,加上数据本地化的优势,`Fork/Join`算法的性能即使在单处理器上面的性能都较其他算法要好。 + +### 2.1 `work−stealing` +`Fork/Join`框架的核心在于轻量级调度机制。`FJTask`采用了`Cilk`的`work-stealing`所采用的基本调度策略: +![](https://upload-images.jianshu.io/upload_images/4685968-1bef530ada2d84c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +* 每一个工作线程维护自己的调度队列中的可运行任务 +* 队列以双端队列的形式被维护,不仅支持后进先出 —— `LIFO`的`push`和`pop`操作,还支持先进先出 —— `FIFO`的`take`操作 +* 对于一个给定的工作线程来说,任务所产生的子任务将会被放入到工作者自己的双端队列中 +* 工作线程使用后进先出 —— `LIFO`(最新的元素优先)的顺序,通过弹出任务来处理队列中的任务 +* 当一个工作线程的本地没有任务去运行的时候,它将使用先进先出 —— `FIFO`的规则尝试随机的从别的工作线程中拿(『窃取』)一个任务去运行 +* 当一个工作线程触及了`join`操作,如果可能的话它将处理其他任务,直到目标任务被告知已经结束(通过`isDone`方法)。所有的任务都会无阻塞的完成。 +* 当一个工作线程无法再从其他线程中获取任务和失败处理的时候,它就会退出(通过`yield`、`sleep`和/或者优先级调整)并经过一段时间之后再度尝试直到所有的工作线程都被告知他们都处于空闲的状态。在这种情况下,他们都会阻塞直到其他的任务再度被上层调用。 + +使用`LIFO`处理每个工作线程自己的任务,使用`FIFO`获取别的任务,这是一种被广泛使用的进行递归`Fork/Join`设计的一种调优手段。 + +让窃取任务的线程从队列拥有者相反的方向进行操作会减少线程竞争。同样体现了递归分治算法的`大任务优先策略`。因此,更早期被窃取的任务有可能会提供一个更大的单元任务,从而使得窃取线程能够在将来进行递归分解。 + +作为上述规则的一个后果,对于一些基础的操作而言,使用相对较小粒度的任务比那些仅仅使用粗粒度划分的任务以及那些没有使用递归分解的任务的运行速度要快。尽管相关的少数任务在大多数的`Fork/Join`框架中会被其他工作线程窃取,但是创建许多组织良好的任务意味着只要有一个工作线程处于可运行的状态,那么这个任务就有可能被执行。 +# 3\. 实现 +主要的类是`FJTaskRunner`,它是`java.lang.Thread`的子类。 +`FJTask`自己仅仅维持一个关于结束状态的布尔值,所有其他的操作都是通过当前的工作线程来代理完成的。 +`JFTaskRunnerGroup`类用于创建工作线程,维护一些共享的状态(例如:所有工作线程的标示符,在窃取操作时需要),同时还要协调启动和关闭。 + +只着重讨论两类问题以及在实现这个框架的时候所形成的一些解决方案:支持高效的双端列表操作(`push`、`pop`和`take`), 并且当工作线程在尝试获取新的任务时维持窃取的协议。 +## 3.1 双端队列 +*(校注:双端队列中的元素可以从两端弹出,其限定插入和删除操作在队列的两端进行。)* + +为了能够获得高效以及可扩展的执行任务,任务管理需要越快越好。创建、发布、和弹出(或者出现频率很少的获取)任务在顺序编程模式中会引发程序调用开销。更低的开销可以使得程序员能够构建更小粒度的任务,最终也能更好的利用并行所带来的益处。 + +`Java`虚拟机会负责任务的内存分配。`Java`垃圾回收器使我们不需要再去编写一个特殊的内存分配器去维护任务。相对于其他语言的类似框架,这个原因使我们大大降低了实现`FJTask`的复杂性以及所需要的代码数。 + +双端队列的基本结构采用了很常规的一个结构 —— 使用一个数组(尽管是可变长的)来表示每个队列,同时附带两个索引:`top`索引就类似于数组中的栈指针,通过`push`和`pop`操作来改变。`base`索引只能通过`take`操作来改变。鉴于`FJTaskRunner`操作都是无缝的绑定到双端队列的细节之中,(例如,`fork`直接调用`push`),所以这个数据结构直接放在类之中,而不是作为一个单独的组件。 + +但是双端队列的元素会被多线程并发的访问,在缺乏足够同步的情况下,而且单个的`Java`数组元素也不能声明为`volatile`变量(校注:[声明成`volatile`的数组](http://ifeve.com/volatile-array-visiblity/ "volatile是否能保证数组中元素的可见性?"),其元素并不具备`volatile`语意),每个数组元素实际上都是一个固定的引用,这个引用指向了一个维护着单个`volatile`引用的转发对象。一开始做出这个决定主要是考虑到`Java`内存模型的一致性。但是在这个级别它所需要的间接寻址被证明在一些测试过的平台上能够提升性能。可能是因为访问邻近的元素而降低了缓存争用,这样内存里面的间接寻址会更快一点。 + +实现双端队列的主要挑战来自于同步和他的撤销。尽管在`Java`虚拟机上使用经过优化过的同步工具,对于每个`push`和`pop`操作都需要获取锁还是让这一切成为性能瓶颈。然后根据以下的观察结果我们可以修改`Clik`中的策略,从而为我们提供一种可行的解决方案: +* `push`和`pop`操作仅可以被工作线程的拥有者所调用 +* 对`take`的操作很容易会由于窃取任务线程在某一时间对`take`操作加锁而限制。(双端队列在必要的时间也可以禁止`take`操作。)这样,控制冲突将被降低为两个部分同步的层次 +* `pop`和`take`操作只有在双端队列为空的时候才会发生冲突,否则的话,队列会保证他们在不同的数组元素上面操作。 + +把`top`和`base`索引定义为`volatile`变量可以保证当队列中元素不止一个时,`pop`和`take`操作可以在不加锁的情况下进行。这是通过一种类似于`Dekker`算法来实现的。当`push`预递减到`top`时: +`if (–top >= base) ...` +和`take`预递减到`base`时: +`if(++base < top) ...` +在上述每种情况下他们都通过比较两个索引来检查这样是否会导致双端队列变成一个空队列。一个不对称的规则将用于防止潜在的冲突:`pop`会重新检查状态并在获取锁之后继续(对`take`所持有的也一样),直到队列真的为空才退出。而`take`操作会立即退出,特别是当尝试去获得另外一个任务。 + +使用`volatile`变量索引`push`操作在队列没有满的情况下不需要同步就可以进行。如果队列将要溢出,那么它首先必须要获得队列锁来重新设置队列的长度。其他情况下,只要确保`top`操作排在队列数组槽盛在抑制干涉带之后更新。 + +在随后的初始化实现中,发现有好几种`JVM`并不符合`Java`内存模型中正确读取写入的`volatile`变量的规则。作为一个工作区, +`pop`在持有锁的情况下重试的条件已经被调整为:如果有两个或者更少的元素,并且`take`操作加了第二把锁以确保内存屏障效果,那么重试就会被触发。 +只要最多只有一个索引被拥有者线程丢失这就是满足的,并且只会引起轻微的性能损耗。 +## 3.2 抢断和闲置 + +在抢断式工作框架中,工作线程对于他们所运行的程序对同步的要求一无所知。他们只是构建、发布、弹出、获取、管理状态和执行任务。这种简单的方案使得当所有的线程都拥有很多任务需要去执行的时候,它的效率很高。然而这种方式是有代价的,当没有足够的工作的时候它将依赖于试探法。也就是说,在启动一个主任务,直到它结束,在有些`Fork/Join`算法中都使用了全面停止的同步指针。 + +主要的问题在于当一个工作线程既无本地任务也不能从别的线程中抢断任务时怎么办。如果程序运行在专业的多核处理器上面,那么可以依赖于硬件的忙等待自旋循环的去尝试抢断一个任务。然而,即使这样,尝试抢断还是会增加竞争,甚至会导致那些不是闲置的工作线程降低效率。除此之外,在一个更适合此框架运行的场景中,操作系统应该能够很自信的去运行那些不相关并可运行的进程和线程。 + +`Java`中并没有十分健壮的工作来保证这个,但是在实际中它往往是可以让人接受的。一个抢断失败的线程在尝试另外的抢断之前会降低自己的优先级,在尝试抢断之间执行`Thread.yeild`操作,然后将自己的状态在`FJTaskRunnerGroup`中设置为不活跃的。他们会一直阻塞直到有新的主线程。其他情况下,在进行一定的自旋次数之后,线程将进入休眠阶段,他们会休眠而不是放弃抢断。强化的休眠机制会给人造成一种需要花费很长时间去划分任务的假象。但是这似乎是最好的也是通用的折中方案。框架的未来版本也许会支持额外的控制方法,以便于让程序员在感觉性能受到影响时可以重写默认的实现。 diff --git "a/Java/Java-Map\347\232\204containsKey(Object-key)\345\222\214containsValue(Object-value)\346\226\271\346\263\225.md" "b/Java/Java-Map\347\232\204containsKey(Object-key)\345\222\214containsValue(Object-value)\346\226\271\346\263\225.md" new file mode 100644 index 0000000000..e88df0d4cf --- /dev/null +++ "b/Java/Java-Map\347\232\204containsKey(Object-key)\345\222\214containsValue(Object-value)\346\226\271\346\263\225.md" @@ -0,0 +1,116 @@ +``` +public void testContainsKeyOrValue(){ + + Scanner sc = new Scanner(System.in); + //Key + System.out.println("请输入要查询的学生id:"); + String id = sc.next(); + System.out.println("你输入的学生id为:"+id+",在学生映射表中是否存在"+ + students.containsKey(id)); + if(students.containsKey(id)){ + System.out.println("对应的学生为:"+students.get(id).name); + } + + //Value + System.out.println("请输入要查询的学生姓名:"); + String name = sc.next(); + if(students.containsValue(new Student(null,name))){ + System.out.println("在学生映射表中,确实包含学生:"+name); + } + else{ + System.out.println("在学生映射表中不存在这个学生"); + } + } + +``` +运行结果: + +``` +请输入学生id: +1 +输入学生姓名以创建学生: +小明 +成功添加学生:小明 +请输入学生id: +2 +输入学生姓名以创建学生: +哈哈 +成功添加学生:哈哈 +请输入学生id: +3 +输入学生姓名以创建学生: +极客咯 +成功添加学生:极客咯 +总共有3个学生 +学生:小明 +学生:哈哈 +学生:极客咯 +请输入要查询的学生id: +2 +你输入的学生id为:2,在学生映射表中是否存在true +对应的学生为:哈哈 +请输入要查询的学生姓名: +小明 +在学生映射表中不存在这个学生 +``` + +结果分析: +可以看到,通过containsKey(Object key)方法比较的结果返回true,是我们想要的结果。通过containsValue(Object value)方法比较的结果返回是false,但是我们确实是有一个名字叫小明的学生啊。为什么呢? + +查看containsKey(Object key)和containsValue(Object value)的API说明: + +- containsKey(Object key):Returns true if this map contains a mapping for the specified key. More formally, returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k)). (There can be at most one such mapping.) +- containsValue(Object value):Returns true if this map maps one or more keys to the specified value. More formally, returns true if and only if this map contains at least one mapping to a value v such that (value==null ? v==null : value.equals(v)). This operation will probably require time linear in the map size for most implementations of the Map interface. + +可以看到,都调用了equals()方法进行比较!因此可以回答为什么了,我们的Key是String类型的,String类型的equals()比较的是字符串本身的内容,所以我们根据键去查找学生的结果是true。而Value是Student类型的,equals()是直接用==实现的,==比较的是对象的引用地址,当然返回结果是false(参考equals()与==的区别与实际应用)。所以,要在Map中通过学生的名字判断是否包含该学生,需要重写equals()方法。 +在Student.java中重写equals()方法: + +``` +@Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Student other = (Student) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } +``` +再次运行,得到运行结果: +请输入学生id: +1 +输入学生姓名以创建学生: +小明 +成功添加学生:小明 +请输入学生id: +2 +输入学生姓名以创建学生: +哈哈 +成功添加学生:哈哈 +请输入学生id: +3 +输入学生姓名以创建学生: +极客咯 +成功添加学生:极客咯 +总共有3个学生 +学生:小明 +学生:哈哈 +学生:极客咯 +请输入要查询的学生id: +2 +你输入的学生id为:2,在学生映射表中是否存在true +对应的学生为:哈哈 +请输入要查询的学生姓名: +小明 + +在学生映射表中,确实包含学生:小明 + +结果分析: +通过重写equals()实现了Map中通过学生姓名查找学生对象(containsValue()方法)。 diff --git "a/Java/Java-\346\263\233\345\236\213\350\247\243\346\203\221\344\271\213---extends-T-\345\222\214---super-T-.md" "b/Java/Java-\346\263\233\345\236\213\350\247\243\346\203\221\344\271\213---extends-T-\345\222\214---super-T-.md" new file mode 100644 index 0000000000..2c62b4f5bd --- /dev/null +++ "b/Java/Java-\346\263\233\345\236\213\350\247\243\346\203\221\344\271\213---extends-T-\345\222\214---super-T-.md" @@ -0,0 +1,148 @@ +# **1 为什么要用通配符和边界?** +使用泛型的过程中,经常出现一种很别扭的情况 +比如我们有*Fruit*类,和它的派生类*Apple* +``` +class Fruit {} +class Apple extends Fruit {} +``` +然后有一个最简单的容器:*Plate*类 +盘子里可以放一个泛型的“*东西*” +我们可以对这个东西做最简单的“*放*”和“*取*”的动作:*set( )*和*get( )*方法 + +``` +class Plate{ + private T item; + public Plate(T t){item=t;} + public void set(T t){item=t;} + public T get(){return item;} +} + +``` +现定义一个“*水果盘*”,逻辑上水果盘当然可以装苹果 +``` +Plate p = new Plate(new Apple()); +``` +但实际上Java编译器不允许这个操作。会报错,“*装苹果的盘子*”无法转换成“*装水果的盘子*”。 + +``` +error: incompatible types: Plate cannot be converted to Plate +``` +实际上,编译器认定的逻辑是这样的: +* 苹果 ***IS-A*** 水果 +* 装苹果的盘子 ***NOT-IS-A*** 装水果的盘子 + +所以,就算容器里装的东西之间有继承关系,但`容器之间是没有继承关系` +所以我们不可以把*Plate*的引用传递给*Plate* + +为了让泛型用起来更舒服,Sun的大师们就想出了的办法,来让”*水果盘子*“和”*苹果盘子*“之间发生正当关系 +# **2 上界** +下面就是上界通配符(Upper Bounds Wildcards) +``` +Plate<? extends Fruit> +``` +**一个能放水果以及一切是水果派生类的盘子** +再直白点就是:**啥水果都能放的盘子 +**这和我们人类的逻辑就比较接近了 +Plate<? extends Fruit>和Plate最大的区别就是:**Plate<? extends Fruit>是Plate及Plate的基类 +**直接的好处就是,我们可以用“*苹果盘*”给“*水果盘*”赋值了 +``` +Plate p = new Plate(new Apple()); +``` +再扩展一下,食物分成水果和肉类,水果有苹果和香蕉,肉类有猪肉和牛肉,苹果还有两种青苹果和红苹果 +``` +//Lev 1 +class Food{} + +//Lev 2 +class Fruit extends Food{} +class Meat extends Food{} + +//Lev 3 +class Apple extends Fruit{} +class Banana extends Fruit{} +class Pork extends Meat{} +class Beef extends Meat{} + +//Lev 4 +class RedApple extends Apple{} +class GreenApple extends Apple{} + +``` +在这个体系中,上界通配符`Plate<? extends Fruit>`覆盖下图中蓝色的区域 +![](https://upload-images.jianshu.io/upload_images/4685968-d81db3fcf40a62bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# **3 下界** +相对应的下界通配符(Lower Bounds Wildcards) + +``` +Plate<? super Fruit> +``` +表达的就是相反的概念:**一个能放水果以及一切是水果基类的盘子** +Plate<? super Fruit>是Plate的基类,但不是Plate的基类 +对应刚才那个例子,Plate<? super Fruit>覆盖下图中红色的区域。 +![](https://upload-images.jianshu.io/upload_images/4685968-d58e1ca63eb102fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# **4 上下界通配符的副作用** +边界让Java不同泛型之间的转换更容易了。但不要忘记,这样的转换也有一定的副作用。那就是容器的部分功能可能失效。 + +还是以刚才的Plate为例。我们可以对盘子做两件事,往盘子里set( )新东西,以及从盘子里get( )东西 +``` +class Plate{ + private T item; + public Plate(T t){item=t;} + public void set(T t){item=t;} + public T get(){return item;} +} + +``` +## **4.1 上界不能往里存,只能往外取** +**会使往盘子里放东西的set( )方法失效 +但取东西get( )方法还有效** + +比如下面例子里两个set()方法,插入Apple和Fruit都报错 +``` +Plate p = new Plate(new Apple()); + +//不能存入任何元素 +p.set(new Fruit()); //Error +p.set(new Apple()); //Error + +//读取出来的东西只能存放在Fruit或它的基类里。 +Fruit newFruit1=p.get(); +Object newFruit2=p.get(); +Apple newFruit3=p.get(); //Error + +``` +编译器只知道容器内是`Fruit或者它的派生类`,但`具体是什么类型不知道` +可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?编译器在看到后面用Plate赋值以后,盘子里没有被标上有“苹果”。而是标上一个占位符:**capture#1**,来表示捕获一个Fruit或Fruit的子类,具体是什么类不知道,代号capture#1 +然后无论是想往里插入Apple或者Meat或者Fruit编译器都不知道能不能和这个capture#1匹配,所以就都不允许 + +所以通配符和类型参数的区别就在于,对编译器来说**所有的T都代表同一种类型** +比如下面这个泛型方法里,三个T都指代同一个类型,要么都是String,要么都是Integer... + +``` +public List fill(T... t); +``` +但通配符没有这种约束,Plate单纯的就表示:**盘子里放了一个东西,是什么我不知道** +## **4.2 下界不影响往里存,但往外取只能放在Object对象里** +**使用下界会使从盘子里取东西的get( )方法部分失效,只能存放到Object对象里。set( )方法正常。** + +``` +Plate p=new Plate(new Fruit()); + +//存入元素正常 +p.set(new Fruit()); +p.set(new Apple()); + +//读取出来的东西只能存放在Object类里。 +Apple newFruit3=p.get(); //Error +Fruit newFruit1=p.get(); //Error +Object newFruit2=p.get(); + +``` + +因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制 +既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以 +但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,`元素的类型信息就全部丢失` +# **5 PECS原则** +最后看一下什么是**PECS(Producer Extends Consumer Super)原则**,已经很好理解了 +1. **频繁往外读取内容的,适合用上界Extends** +2. **经常往里插入的,适合用下界Super** diff --git "a/Java/Java-\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230---ThreadLocal-\345\216\237\347\220\206.md" "b/Java/Java-\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230---ThreadLocal-\345\216\237\347\220\206.md" new file mode 100644 index 0000000000..74b4fb31d4 --- /dev/null +++ "b/Java/Java-\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230---ThreadLocal-\345\216\237\347\220\206.md" @@ -0,0 +1,426 @@ +说起CS游戏,应该是每个中二少年的年少回忆了. +游戏开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为1500、0、10. + +设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢? +如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢? +如果共享,那么线程之间的并发修改会导致数据不准确. +能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal +>注意不能将其翻译为线程本地化或本地线程 +英语恰当的名称应该叫作:CopyValueIntoEveryThread + +示例代码 +![](https://upload-images.jianshu.io/upload_images/4685968-061b67d3b7dd0267.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +该示例中,无 set 操作,那么初始值又是如何进入每个线程成为独立拷贝的呢? +首先,虽然`ThreadLocal`在定义时重写了`initialValue()` ,但并非是在`BULLET_ NUMBER_ THREADLOCAL`对象加载静态变量的时候执行; +而是每个线程在`ThreadLocal.get()`时都会执行到; +其源码如下 +![ThreadLocal # get()](https://upload-images.jianshu.io/upload_images/4685968-7dcbc66c969c5307.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +每个线程都有自己的`ThreadLocalMap`; +如果`map ==null`,则直接执行`setInitialValue()`; +如果 map 已创建,就表示 Thread 类的`threadLocals` 属性已初始化完毕; +如果 `e==null`,依然会执行到`setinitialValue()` +`setinitialValue() `的源码如下: +![](https://upload-images.jianshu.io/upload_images/4685968-4837187c5986207a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +这是一个保护方法,CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue() ; +![](https://upload-images.jianshu.io/upload_images/4685968-c4427397d0c04dfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +` getMap `的源码就是提取线程对象t的ThreadLocalMap属性: t. threadLocals. + +> 在`CsGameByThreadLocal`第1处,使用了`ThreadLocalRandom` 生成单独的`Random`实例; +该类在JDK7中引入,它使得每个线程都可以有自己的随机数生成器; +我们要避免`Random`实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一`seed`而导致性能下降. + +我们已经知道了`ThreadLocal`是每一个线程单独持有的; +因为每一个线程都有独立的变量副本,其他线程不能访问,所以不存在线程安全问题,也不会影响程序的执行性能. +`ThreadLocal`对象通常是由`private static`修饰的,因为都需要复制到本地线程,所以非`static`作用不大; +不过,`ThreadLocal`无法解决共享对象的更新问题,下面的实例将证明这点. +因为`CsGameByThreadLocal`中使用的是`Integer `不可变对象,所以可使用相同的编码方式来操作一下可变对象看看 +![](https://upload-images.jianshu.io/upload_images/4685968-00e4a560000fdbd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +输出的结果是乱序不可控的,所以使用某个引用来操作共享对象时,依然需要进行线程同步 +![ThreadLocal和Thread的类图](https://upload-images.jianshu.io/upload_images/4685968-171e96321cfdb7f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +`ThreadLocal` 有个静态内部类`ThreadLocalMap`,它还有一个静态内部类`Entry`; +在Thread中的`ThreadLocalMap`属性的赋值是在`ThreadLocal`类中的`createMap`. + +`ThreadLocal `与`ThreadLocalMap`有三组对应的方法: get()、set()和remove(); +在`ThreadLocal`中对它们只做校验和判断,最终的实现会落在`ThreadLocalMap.`. +`Entry`继承自`WeakReference`,只有一个value成员变量,它的key是ThreadLocal对象 + +再从栈与堆的内存角度看看两者的关系 +![ThreadLocal的弱引用路线图](https://upload-images.jianshu.io/upload_images/4685968-8c7c8f9022d5155d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +一个Thread有且仅有一个`ThreadLocalMap`对象 +一个`Entry`对象的 key 弱引用指向一个`ThreadLocal`对象 +一个`ThreadLocalMap `对象存储多个Entry 对象 +一个`ThreadLocal` 对象可被多个线程共享 +`ThreadLocal`对象不持有Value,Value 由线程的Entry 对象持有. + +Entry 对象源码如下 +![](https://upload-images.jianshu.io/upload_images/4685968-e599cbc7eed64810.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +所有的`Entry`对象都被`ThreadLocalMap`类实例化对象`threadLocals`持有; +当线程执行完毕时,线程内的实例属性均会被垃圾回收,弱引用的`ThreadLocal`,即使线程正在执行,只要`ThreadLocal`对象引用被置成`null`,`Entry`的Key就会自动在下一次Y - GC时被垃圾回收; +而在`ThreadLocal`使用`set()/get()`时,又会自动将那些`key=null`的value 置为`null`,使value能够被GC,避免内存泄漏,现实很骨感, ThreadLocal如源码注释所述: +![](https://upload-images.jianshu.io/upload_images/4685968-7d5574c9b7be6593.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`ThreadLocal`对象通常作为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束. + +### 三个重要方法: +- set() +如果没有set操作的`ThreadLocal`, 很容易引起脏数据问题 +- get() +始终没有get操作的`ThreadLocal`对象是没有意义的 +- remove() +如果没有remove操作,则容易引起内存泄漏 + +如果`ThreadLocal`是非静态的,属于某个线程实例,那就失去了线程间共享的本质属性; +那么`ThreadLocal`到底有什么作用呢? +我们知道,局部变量在方法内各个代码块间进行传递,而类变量在类内方法间进行传递; +复杂的线程方法可能需要调用很多方法来实现某个功能,这时候用什么来传递线程内变量呢? +即`ThreadLocal`,它通常用于同一个线程内,跨类、跨方法传递数据; +如果没有ThreadLocal,那么相互之间的信息传递,势必要靠返回值和参数,这样无形之中,有些类甚至有些框架会互相耦合; +通过将Thread构造方法的最后一个参数设置为true,可以把当前线程的变量继续往下传递给它创建的子线程 +``` +public Thread (ThreadGroup group, Runnable target, String name,long stackSize, boolean inheritThreadLocals) [ + this (group, target, name, stackSize, null, inheritThreadLocals) ; +} +``` +parent为其父线程 +``` +if (inheritThreadLocals && parent. inheritableThreadLocals != null) + this. inheritableThreadLocals = ThreadLocal. createInheritedMap (parent. inheritableThreadLocals) ; +``` +`createlnheritedMap()`其实就是调用`ThreadLocalMap`的私有构造方法来产生一个实例对象,把父线程中不为`null`的线程变量都拷贝过来 +``` +private ThreadLocalMap (ThreadLocalMap parentMap) { + // table就是存储 + Entry[] parentTable = parentMap. table; + int len = parentTable. length; + setThreshold(len) ; + table = new Entry[len]; + + for (Entry e : parentTable) { + if (e != null) { + ThreadLocal key = (ThreadLocal) e.get() ; + if (key != null) { + object value = key. childValue(e.value) ; + Entry c = new Entry(key, value) ; + int h = key. threadLocalHashCode & (len - 1) ; + while (table[h] != null) + h = nextIndex(h, len) ; + table[h] = C; + size++; + } + } +} +``` +很多场景下可通过`ThreadLocal`来透传全局上下文的; +比如用`ThreadLocal`来存储监控系统的某个标记位,暂且命名为traceld. +某次请求下所有的traceld都是一致的,以获得可以统一解析的日志文件; +但在实际开发过程中,发现子线程里的traceld为null,跟主线程的traceld并不一致,所以这就需要刚才说到的`InheritableThreadLocal`来解决父子线程之间共享线程变量的问题,使整个连接过程中的traceld一致. +示例代码如下 +``` +import org.apache.commons.lang3.StringUtils; + +/** + * @author sss + * @date 2019/1/17 + */ +public class RequestProcessTrace { + + private static final InheritableThreadLocal FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL + = new InheritableThreadLocal(); + + public static FullLinkContext getContext() { + FullLinkContext fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get(); + if (fullLinkContext == null) { + FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.set(new FullLinkContext()); + fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get(); + } + return fullLinkContext; + } + + private static class FullLinkContext { + private String traceId; + + public String getTraceId() { + if (StringUtils.isEmpty(traceId)) { + FrameWork.startTrace(null, "JavaEdge"); + traceId = FrameWork.getTraceId(); + } + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + } + +} +``` +使用`ThreadLocal`和`InheritableThreadLocal`透传上下文时,需要注意线程间切换、异常传输时的处理,避免在传输过程中因处理不当而导致的上下文丢失. + +最后,`SimpleDateFormat` 是非线程安全的类,定义为static,会有数据同步风险. +通过源码可以看出,`SimpleDateFormat` 内部有一个`Calendar`对象; +在日期转字符串或字符串转日期的过程中,多线程共享时很可能产生错误; +推荐使用` ThreadLocal`,让每个线程单独拥有这个对象. +![](https://upload-images.jianshu.io/upload_images/4685968-ef1c68f4853af05d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +# ThreadLocal的副作用 +为了使线程安全地共享某个变量,JDK给出了`ThreadLocal`. +但`ThreadLocal`的主要问题是会产生脏数据和内存泄漏; +这两个问题通常是在线程池的线程中使用ThreadLocal引发的,因为线程池有线程复用和内存常驻两是在线程池的线程中使用ThreadLocal 引发的,因为线程池有线程复用和内存常驻两个特点 + +## 1 脏数据 +线程复用会产生脏数据; +由于线程池会重用 `Thread` 对象,与 `Thread` 绑定的静态属性 `ThreadLoca` l变量也会被重用. +如果在实现的线程run()方法中不显式调用`remove()`清理与线程相关的`ThreadLocal`信息,那么若下一个线程不调用`set()`,就可能`get()` 到重用的线程信息; +包括`ThreadLocal`所关联的线程对象的**value**值. + +脏读问题其实十分常见. +比如,用户A下单后没有看到订单记录,而用户B却看到了用户A的订单记录. +通过排查发现是由于 session 优化引发. +在原来的请求过程中,用户每次请求Server,都需要通过 sessionId 去缓存里查询用户的session信息,这样无疑增加了一次调用. +因此,工程师决定采用某框架来缓存每个用户对应的`SecurityContext`, 它封装了session 相关信息. +优化后虽然会为每个用户新建一个 session 相关的上下文,但由于`Threadlocal`没有在线程处理结束时及时`remove()`; +在高并发场景下,线程池中的线程可能会读取到上一个线程缓存的用户信息. +- 示例代码 +![](https://upload-images.jianshu.io/upload_images/4685968-3a7b2585dd559c29.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![输出结果](https://upload-images.jianshu.io/upload_images/4685968-29e24506fb7bbcaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + ## 2 内存泄漏 +在源码注释中提示使用static关键字来修饰`ThreadLocal`. +在此场景下,寄希望于`ThreadLocal`对象失去引用后,触发弱引用机制来回收`Entry`的`Value`就不现实了. +在上例中,如果不进行`remove()`,那么当该线程执行完成后,通过`ThreadLocal`对象持有的String对象是不会被释放的. + +- **以上两个问题的解决办法很简单 +每次用完ThreadLocal时,及时调用`remove()`清理** +=========================== 增添内容 ============================== + + + +# 线程封闭 +**避免并发异常最简单的方法就是线程封闭** +即 把对象封装到一个线程里,只有该线程能看到此对象; +那么该对象就算非线程安全,也不会出现任何并发安全问题. + +使用`ThreadLocal`是实现线程封闭的最佳实践. +`ThreadLocal`内部维护了一个Map,Map的key是每个线程的名称,Map的值就是我们要封闭的对象. +每个线程中的对象都对应着Map中一个值,也就是`ThreadLocal`利用Map实现了对象的线程封闭. + +# What is ThreadLocal +该类提供了线程局部 (thread-local) 变量; +这些变量不同于它们的普通对应物,因为访问某变量(通过其 get /set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本. + +`ThreadLocal` 实例通常是类中的 `private static `字段,希望将状态与某一个线程(e.g. 用户 ID 或事务 ID)相关联. + +一个以`ThreadLocal`对象为键、任意对象为值的存储结构; +有点像`HashMap`,可以保存"key : value"键值对,但一个`ThreadLocal`只能保存一个键值对,各个线程的数据互不干扰. +该结构被附带在线程上,也就是说一个线程可以根据一个`ThreadLocal`对象查询到绑定在这个线程上的一个值. +``` +ThreadLocal localName = new ThreadLocal(); +localName.set("JavaEdge"); +String name = localName.get(); +``` +在线程A中初始化了一个ThreadLocal对象localName,并set了一个值JavaEdge; +同时在线程A中通过get可拿到之前设置的值; +但是如果在线程B中,拿到的将是一个null. + +因为`ThreadLocal`保证了各个线程的数据互不干扰 +看看set(T value)和get()方法的源码 +![返回当前线程该线程局部变量副本中的值](https://upload-images.jianshu.io/upload_images/4685968-0a1ec5f852dec48a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![设置此线程局部变量的当前线程的副本到指定的值,大多数的子类都不需要重写此方法](https://upload-images.jianshu.io/upload_images/4685968-49519e5895333564.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-747ceba517cccf11.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +![Thread#threadLocals](https://upload-images.jianshu.io/upload_images/4685968-c2229753efdc5ad9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +可见,每个线程中都有一个`ThreadLocalMap` +- 执行set时,其值是保存在当前线程的`threadLocals`变量 +- 执行get时,从当前线程的`threadLocals`变量获取 + +所以在线程A中set的值,是线程B永远得不到的 +即使在线程B中重新set,也不会影响A中的值; +保证了线程之间不会相互干扰. + +# 追寻本质 - 结构 +从名字上看猜它类似HashMap,但在`ThreadLocal`中,并无实现Map接口 + +- 在`ThreadLoalMap`中,也是初始化一个大小为16的Entry数组 +![](https://upload-images.jianshu.io/upload_images/4685968-0acf6011ee449403.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- Entry节点对象用来保存每一个key-value键值对 +![](https://upload-images.jianshu.io/upload_images/4685968-9b21b877b6283ec8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +这里的**key 恒为 ThreadLocal**; +通过`ThreadLocal`的`set()`,把`ThreadLocal`对象自身当做key,放进`ThreadLoalMap` +![](http://upload-images.jianshu.io/upload_images/4685968-23eeb08ea0826c82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`ThreadLoalMap`的`Entry`继承`WeakReference` +和HashMap很不同,`Entry`中没有`next`字段,所以不存在链表情形. + +# hash冲突 +无链表,那发生hash冲突时何解? + +先看看`ThreadLoalMap`插入一个 key/value 的实现 +![](https://upload-images.jianshu.io/upload_images/4685968-3f745e3f2d5c22d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 每个`ThreadLocal`对象都有一个hash值 - `threadLocalHashCode` +![](https://upload-images.jianshu.io/upload_images/4685968-973391eefcdad035.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 每初始化一个`ThreadLocal`对象,hash值就增加一个固定大小 +![](https://upload-images.jianshu.io/upload_images/4685968-53f33e581c3f7520.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +在插入过程中,根据`ThreadLocal`对象的hash值,定位至table中的位置i. +过程如下 +- 若当前位置为空,就初始化一个Entry对象置于i; +- 位置i已有对象 + - 若该`Entry`对象的key正是将设置的key,覆盖其value(和HashMap 处理相同); + - 若和即将设置的key 无关,则寻找下一个空位 + +如此,在`get`时,也会根据`ThreadLocal`对象的hash值,定位到table中的位置.然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置. + +可见,set和get如果冲突严重的话,效率很低,因为`ThreadLoalMap`是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为 + +# 内存泄露 +ThreadLocal可能导致内存泄漏,为什么? +先看看Entry的实现: +``` +static class Entry extends WeakReference> { + /** The value associated with this ThreadLocal. */ + Object value; + + Entry(ThreadLocal k, Object v) { + super(k); + value = v; + } +} +``` + +通过之前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中 + +这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。 + +## 避免内存泄露 +既然发现有内存泄露的隐患,自然有应对策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。 + +如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。 +``` + ThreadLocal localName = new ThreadLocal(); + try { + localName.set("JavaEdge"); + // 其它业务逻辑 + } finally { + localName.remove(); + } +``` + +# 题外小话 +首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的. +一般情况下,通过set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的; +各个线程中访问的是不同的对象. + +**另外,说ThreadLocal使得各线程能够保持各自独立的一个对象; +并不是通过set()实现的,而是通过每个线程中的new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。** +通过set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map; +执行get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自线程中的对象. +ThreadLocal实例是作为map的key来使用的. + +如果set()进去的东西本来就是多个线程共享的同一个对象; +那么多个线程的get()取得的还是这个共享对象本身,还是有并发访问问题。 + +# Hibernate中典型的 ThreadLocal 应用 +``` +private static final ThreadLocal threadSession = new ThreadLocal(); + +public static Session getSession() throws InfrastructureException { + Session s = (Session) threadSession.get(); + try { + if (s == null) { + s = getSessionFactory().openSession(); + threadSession.set(s); + } + } catch (HibernateException ex) { + throw new InfrastructureException(ex); + } + return s; +} +``` +首先判断当前线程中有没有放入 session,如果还没有,那么通过`sessionFactory().openSession()`来创建一个session; +再将session `set()`到线程中,实际是放到当前线程的`ThreadLocalMap`; +这时,对于该 session 的唯一引用就是当前线程中的那个ThreadLocalMap; +threadSession 作为这个值的key,要取得这个 session 可以通过threadSession.get(); +里面执行的操作实际是先取得当前线程中的ThreadLocalMap; +然后将threadSession作为key将对应的值取出. +这个 session 相当于线程的私有变量,而不是public的. + +显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了. + +## 如果不用ThreadLocal怎么实现呢? +可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的; +或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法. +但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了 + +总之,`ThreadLocal`不是用来解决对象共享访问问题的; +而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式 + +- 每个线程中都有一个自己的`ThreadLocalMap`类对象; +可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象. +- 将一个共用的`ThreadLocal`静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦. + +当然如果要把本来线程共享的对象通过set()放到线程中也可以,可以实现避免参数传递的访问方式; +但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决; +但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中 + +# **ThreadLocal的应用场合** +我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。 + +可以看到ThreadLocal类中的变量只有这3个int型: + +``` +private final int threadLocalHashCode = nextHashCode(); +private static AtomicInteger nextHashCode = + new AtomicInteger(); +private static final int HASH_INCREMENT = 0x61c88647; +``` +而作为ThreadLocal实例的变量只有 **threadLocalHashCode** +**nextHashCode** 和**HASH_INCREMENT** 是ThreadLocal类的静态变量 +实际上 +- HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量 +- nextHashCode 表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值 + +看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,构造方法`ThreadLocal()`里什么操作都没有,唯一的操作是这句 +``` +private final int threadLocalHashCode = nextHashCode(); +``` +那么nextHashCode()做了什么呢 +``` +private static int nextHashCode() { + return nextHashCode.getAndAdd(HASH_INCREMENT); + } +``` +就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。. + +因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例; +ThreadLocal类主要是作为工具类来使用,那么set()进去的对象是放在哪儿的呢? + +看一下上面的set()方法,两句合并一下成为 +``` +ThreadLocalMap map = Thread.currentThread().threadLocals; +``` +这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中: + +``` +public class Thread implements Runnable { + ...... + + /* ThreadLocal values pertaining to this thread. This map is maintained + * by the ThreadLocal class. */ + ThreadLocal.ThreadLocalMap threadLocals = null; + ...... +} +``` +再看这句: + +``` +if (map != null) + map.set(this, value); +``` +也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样看了代码也就明白了. + +# 参考 +《码出高效:Java开发手册》 diff --git "a/Java/Java-\347\232\204\346\263\250\350\247\243.md" "b/Java/Java-\347\232\204\346\263\250\350\247\243.md" new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ "b/Java/Java-\347\232\204\346\263\250\350\247\243.md" @@ -0,0 +1 @@ + diff --git "a/Java/Java-\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/Java/Java-\347\261\273\345\212\240\350\275\275\345\231\250.md" new file mode 100644 index 0000000000..ee094320a7 --- /dev/null +++ "b/Java/Java-\347\261\273\345\212\240\350\275\275\345\231\250.md" @@ -0,0 +1,205 @@ +把类加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作交给虚拟机之外的类加载器来完成 +这样的好处在于,我们可以自行实现类加载器来加载其他格式的类,只要是二进制字节流就行,这就大大增强了加载器灵活性 + +通常系统自带的类加载器分为三种: +> **启动类加载器**(Bootstrap ClassLoader):由C/C++实现,负责加载`\jre\lib`目录下或者是-Xbootclasspath所指定路径下目录以及系统属性sun.boot.class.path制定的目录中特定名称的jar包到虚拟机内存中。在JVM启动时,通过Bootstrap ClassLoader加载rt.jar,并初始化sun.misc.Launcher从而创建Extension ClassLoader和Application ClassLoader的实例.  +> **需要注意的是**,Bootstrap ClassLoader智慧加载特定名称的类库,比如rt.jar.这意味我们自定义的jar扔到`\jre\lib`也不会被加载.  +> 我们可以通过以下代码,查看Bootstrap ClassLoader到底初始化了那些类库: +> +> ``` +> URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); +> for (URL urL : urLs) { +> System.out.println(urL.toExternalForm()); +> } +> ``` +> +> **扩展类加载器**(Extension Classloader):只有一个实例,由sun.misc.Launcher$ExtClassLoader实现,负责加载`\lib\ext`目录下或是被系统属性java.ext.dirs所指定路径目录下的所有类库。 +> +> **应用程序类加载器**(Application ClassLoader):只有一个实例,由sun.misc.Launcher$AppClassLoader实现,负责加载系统环境变量ClassPath或者系统属性java.class.path制定目录下的所有类库,如果应用程序中没有定义自己的加载器,则该加载器也就是默认的类加载器.该加载器可以通过java.lang.ClassLoader.getSystemClassLoader获取. + +以上三种是我们经常认识最多,除此之外还包括**线程上下文类加载器**(Thread Context ClassLoader)和**自定义类加载器**. + +下来解释一下线程上下文加载器:  +每个线程都有一个类加载器(jdk 1.2后引入),称之为Thread Context ClassLoader,如果线程创建时没有设置,则默认从父线程中继承一个,如果在应用全局内都没有设置,则所有Thread Context ClassLoader为Application ClassLoader.可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,通过Thread.currentThread().getContextClassLoader()来获取.  +我们来想想线程上下文加载器有什么用的?该类加载器容许父类加载器通过子类加载器加载所需要的类库,也就是打破了我们下文所说的双亲委派模型.  +这有什么好处呢?利用线程上下文加载器,我们能够实现所有的代码热替换,热部署,Android中的热更新原理也是借鉴如此的. + +至于自定义加载器就更简单了,JVM运行我们通过自定义的ClassLoader加载相关的类库. + +## 3.1 类加载器的双亲委派模型 + +当一个类加载器收到一个类加载的请求,它首先会将该请求委派给父类加载器去加载,每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该被传入到顶层的启动类加载器(Bootstrap ClassLoader)中,只有当父类加载器反馈无法完成这个列的加载请求时(它的搜索范围内不存在这个类),子类加载器才尝试加载。其层次结构示意图如下:  +![双亲委派模型示意图](http://upload-images.jianshu.io/upload_images/4685968-73c4e96204d512c2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +不难发现,该种加载流程的好处在于: + +1. 可以避免重复加载,父类已经加载了,子类就不需要再次加载 +2. 更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心api,会带来相关隐患。 + +接下来,我们看看双亲委派模型是如何实现的: + +``` + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (getClassLoadingLock(name)) { + // 首先先检查该类已经被加载过了 + Class c = findLoadedClass(name); + if (c == null) {//该类没有加载过,交给父类加载 + long t0 = System.nanoTime(); + try { + if (parent != null) {//交给父类加载 + c = parent.loadClass(name, false); + } else {//父类不存在,则交给启动类加载器加载 + c = findBootstrapClassOrNull(name); + } + } catch (ClassNotFoundException e) { + //父类加载器抛出异常,无法完成类加载请求 + } + + if (c == null) {// + long t1 = System.nanoTime(); + //父类加载器无法完成类加载请求时,调用自身的findClass方法来完成类加载 + c = findClass(name); + sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); + sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); + sun.misc.PerfCounter.getFindClasses().increment(); + } + } + if (resolve) { + resolveClass(c); + } + return c; + } + } + +``` + +这里有些童鞋会问,JVM怎么知道一个某个类加载器的父加载器呢?如果你有此疑问,请重新再看一遍. + +## 3.2 类加载器的特点 + +1. 运行任何一个程序时,总是由Application Loader开始加载指定的类。 +2. 一个类在收到加载类请求时,总是先交给其父类尝试加载。 +3. Bootstrap Loader是最顶级的类加载器,其父加载器为null。 + +## 3.3 类加载的三种方式 + +1. 通过命令行启动应用时由JVM初始化加载含有main()方法的主类。 +2. 通过Class.forName()方法动态加载,会默认执行初始化块(static{}),但是Class.forName(name,initialize,loader)中的initialze可指定是否要执行初始化块。 +3. 通过ClassLoader.loadClass()方法动态加载,不会执行初始化块。 + +## 3.4 自定义类加载器的两种方式 + +1、遵守双亲委派模型:继承ClassLoader,重写findClass()方法。  +2、破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。  +通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。  +自定义类加载的目的是想要手动控制类的加载,那除了通过自定义的类加载器来手动加载类这种方式,还有其他的方式么? + +> 利用现成的类加载器进行加载: +> +> ``` +> 1. 利用当前类加载器 +> Class.forName(); +> +> 2. 通过系统类加载器 +> Classloader.getSystemClassLoader().loadClass(); +> +> 3. 通过上下文类加载器 +> Thread.currentThread().getContextClassLoader().loadClass(); +> ``` +> +> l  +> 利用URLClassLoader进行加载: +> +> ``` +> URLClassLoader loader=new URLClassLoader(); +> loader.loadClass(); +> ``` + +* * * + +**类加载实例演示:**  +命令行下执行HelloWorld.java + +``` +public class HelloWorld{ + public static void main(String[] args){ + System.out.println("Hello world"); + } +} +``` + +该段代码大体经过了一下步骤: + +1. 寻找jre目录,寻找jvm.dll,并初始化JVM. +2. 产生一个Bootstrap ClassLoader; +3. Bootstrap ClassLoader加载器会加载他指定路径下的java核心api,并且生成Extended ClassLoader加载器的实例,然后Extended ClassLoader会加载指定路径下的扩展java api,并将其父设置为Bootstrap ClassLoader。 +4. Bootstrap ClassLoader生成Application ClassLoader,并将其父Loader设置为Extended ClassLoader。 +5. 最后由AppClass ClassLoader加载classpath目录下定义的类——HelloWorld类。 + +我们上面谈到 Extended ClassLoader和Application ClassLoader是通过Launcher来创建,现在我们再看看源代码: + +``` + public Launcher() { + Launcher.ExtClassLoader var1; + try { + //实例化ExtClassLoader + var1 = Launcher.ExtClassLoader.getExtClassLoader(); + } catch (IOException var10) { + throw new InternalError("Could not create extension class loader", var10); + } + + try { + //实例化AppClassLoader + this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); + } catch (IOException var9) { + throw new InternalError("Could not create application class loader", var9); + } + //主线程设置默认的Context ClassLoader为AppClassLoader. + //因此在主线程中创建的子线程的Context ClassLoader 也是AppClassLoader + Thread.currentThread().setContextClassLoader(this.loader); + String var2 = System.getProperty("java.security.manager"); + if(var2 != null) { + SecurityManager var3 = null; + if(!"".equals(var2) && !"default".equals(var2)) { + try { + var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); + } catch (IllegalAccessException var5) { + ; + } catch (InstantiationException var6) { + ; + } catch (ClassNotFoundException var7) { + ; + } catch (ClassCastException var8) { + ; + } + } else { + var3 = new SecurityManager(); + } + + if(var3 == null) { + throw new InternalError("Could not create SecurityManager: " + var2); + } + + System.setSecurityManager(var3); + } + + } +``` + +## 3.5 非常重要 + +双亲委派模型好处:eg,object 类。它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此 object 类在程序的各种加载环境中都是同一个类。 + +在这里呢我们需要注意几个问题:  +1\. 我们知道ClassLoader通过一个类的全限定名来获取二进制流,那么如果我们需要通过自定义类加载其来加载一个Jar包的时候,难道要自己遍历jar中的类,然后依次通过ClassLoader进行加载吗?或者说我们怎么来加载一个jar包呢?  +2\. 如果一个类引用的其他的类,那么这个其他的类由谁来加载?  +3\. 既然类可以由不同的加载器加载,那么如何确定两个类如何是同一个类? + +我们来依次解答这两个问题:  +对于动态加载jar而言,JVM默认会使用第一次加载该jar中指定类的类加载器作为默认的ClassLoader.假设我们现在存在名为sbbic的jar包,该包中存在ClassA和ClassB这两个类(ClassA中没有引用ClassB).现在我们通过自定义的ClassLoaderA来加载在ClassA这个类,那么此时此时ClassLoaderA就成为sbbic.jar中其他类的默认类加载器.也就是,ClassB也默认会通过ClassLoaderA去加载. + +那么如果ClassA中引用了ClassB呢?当类加载器在加载ClassA的时候,发现引用了ClassB,此时类加载如果检测到ClassB还没有被加载,则先回去加载.当ClassB加载完成后,继续回来加载ClassA.换句话说,类会通过自身对应的来加载其加载其他引用的类. + +JVM规定,对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立在java虚拟机中的唯一性,通俗点就是说,在jvm中判断两个类是否是同一个类取决于类加载和类本身,也就是同一个类加载器加载的同一份Class文件生成的Class对象才是相同的,类加载器不同,那么这两个类一定不相同. diff --git "a/Java/Java8-\345\216\237\345\255\220\345\274\271\347\261\273\344\271\213LongAdder\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/Java/Java8-\345\216\237\345\255\220\345\274\271\347\261\273\344\271\213LongAdder\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000000..67f49329e5 --- /dev/null +++ "b/Java/Java8-\345\216\237\345\255\220\345\274\271\347\261\273\344\271\213LongAdder\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,133 @@ +简单来说,这个类用于在多线程情况下的求和。 +![官方文档的说明](https://upload-images.jianshu.io/upload_images/4685968-06c72cfbe4dc4230.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +从关键方法 +#add +![](https://upload-images.jianshu.io/upload_images/4685968-bad92d53219ce616.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +包含了一个Cell数组,`Striped64`的一个内部类 +![](https://upload-images.jianshu.io/upload_images/4685968-d2d7d1ed29af4dc0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`Padded variant of AtomicLong supporting only raw accesses plus CAS` +即`AtomicLong`的填充变体且只支持原始访问和CAS +有一个value变量,并且提供了一个cas方法更新value值 + +接下来看第一个if语句,这句首先判断cells是否还没被初始化,并且尝试对value值进行cas操作。如果cells已经初始化并且cas操作失败,则运行if内部的语句。在进入第一个if语句之后紧接着是另外一个if,这个if有4个判断:cell[]数组是否初始化;cell[]数组虽然初始化了但是数组长度是否为0;该线程所对应的cell是否为null;尝试对该线程对应的cell单元进行cas更新是否失败,如果这些条件有一条为true,则运行最为核心的方法longAccumulate,下面列出这个方法,为了便于理解,直接将对其的分析写为注释。 +![JavaDoc](https://upload-images.jianshu.io/upload_images/4685968-9d6da5cb49142c38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +``` +/** + * 处理涉及初始化,调整大小,创建新Cell,和/或争用的更新案例 + * + * @param x 值 + * @param fn 更新方法 + * @param wasUncontended 调用 + */ + final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) { + int h; + // 获取线程probe的值 + if ((h = getProbe()) == 0) { + // 值为0则初始化 + ThreadLocalRandom.current(); //强制初始化 + h = getProbe(); + wasUncontended = true; + } + boolean collide = false; // True if last slot nonempt + for (;;) { + Cell[] as; Cell a; int n; long v; + // 这个if分支处理上述四个条件中的前两个相似,此时cells数组已经初始化了并且长度大于0 + if ((as = cells) != null && (n = as.length) > 0) { + // 线程对应的cell为null + if ((a = as[(n - 1) & h]) == null) { + // 如果busy锁未被占有 + if (cellsBusy == 0) { // Try to attach new Cell + // 新建一个cell + Cell r = new Cell(x); // Optimistically create + // 检测busy是否为0,并且尝试锁busy + if (cellsBusy == 0 && casCellsBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + //再次确认线程probe所对应的cell为null,将新建的cell赋值 + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + // 解锁 + cellsBusy = 0; + } + if (created) + break; + //如果失败,再次尝试 + continue; // Slot is now non-empty + } + } + collide = false; + } + //置为true后交给循环重试 + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + //尝试给线程对应的cell update + else if (a.cas(v = a.value, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + //在以上条件都无法解决的情况下尝试扩展cell + else if (cellsBusy == 0 && casCellsBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = advanceProbe(h); + } + //此时cells还未进行第一次初始化,进行初始化 + else if (cellsBusy == 0 && cells == as && casCellsBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + cellsBusy = 0; + } + if (init) + break; + } + //busy锁不成功或者忙,则再重试一次casBase对value直接累加 + else if (casBase(v = base, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; // Fall back on using base + } + } + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + * 通过cas实现的自旋锁,用于扩大或者初始化cells + */ + transient volatile int cellsBusy; +``` +从以上分析来看,`longAccumulate`就是为了尽量减少多个线程更新同一个value,实在不行则扩大cell + +`LongAdder`减少冲突的方法以及在求和场景下比`AtomicLong`更高效。 +因为`LongAdder`在更新数值时并非对一个数进行更新,而是分散到多个cell,这样在多线程的情况下可以有效的嫌少冲突和压力,使得更加高效。 +# 使用场景 +适用于统计求和计数的场景,因为它提供了`add`、`sum`方法 +# [](http://blog.jerkybible.com/2018/01/11/Java%E5%B9%B6%E5%8F%91%E6%BA%90%E7%A0%81%E4%B9%8BLongAdder/#LongAdder%E6%98%AF%E5%90%A6%E8%83%BD%E5%A4%9F%E6%9B%BF%E6%8D%A2AtomicLong "LongAdder是否能够替换AtomicLong")LongAdder是否能够替换AtomicLong + +从上面的分析来看是不行的,因为`AtomicLong`提供了很多cas方法,例如`getAndIncrement`、`getAndDecrement`等,使用起来非常的灵活,而`LongAdder`只有`add`和`sum`,使用起来比较受限。 +优点:由于 JVM 会将 64位的double,long 型变量的读操作分为两次32位的读操作,所以低并发保持了 AtomicLong性能,高并发下热点数据被 hash 到多个 Cell,有限分离,通过分散提升了并行度 +但统计时有数据更新,也可能会出现数据误差,但高并发场景有限使用此类,低时还是可以继续 AtomicLong diff --git "a/Java/Java8\346\265\201\347\274\226Stream-DistinctOps,-SliceOps.md" "b/Java/Java8\346\265\201\347\274\226Stream-DistinctOps,-SliceOps.md" new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ "b/Java/Java8\346\265\201\347\274\226Stream-DistinctOps,-SliceOps.md" @@ -0,0 +1 @@ + diff --git "a/Java/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md" "b/Java/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md" new file mode 100644 index 0000000000..47eb49416a --- /dev/null +++ "b/Java/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md" @@ -0,0 +1,348 @@ +# 1 概述 +本文将介绍Map集合的另一个常用类,Hashtable.Hashtable出来的比HashMap早,HashMap1.2才有,而Hashtable在1.0就已经出现了.HashMap和Hashtable实现原理基本一样,都是通过哈希表实现.而且两者处理冲突的方式也一样,都是通过链表法.下面就详细学习下这个类. + +#2 源码解析 +##类总览 +``` +public class Hashtable + extends Dictionary + implements Map, Cloneable, java.io.Serializable +``` +Hashtable并没有去继承AbstractMap,而是选择继承了Dictionary类,Dictionary是个被废弃的抽象类,文档已经说得很清楚了: + +``` +NOTE: This class is obsolete. New implementations should + * implement the Map interface, rather than extending this class. +``` +这个类的方法如下(全是抽象方法): + +``` +public abstract +class Dictionary { + + public Dictionary() { + } + abstract public int size(); + abstract public boolean isEmpty(); + abstract public Enumeration keys(); + abstract public Enumeration elements(); + abstract public V get(Object key); + abstract public V put(K key, V value); + abstract public V remove(Object key); +} +``` +##成员变量 + +``` + //存储键值对的桶数组 + private transient Entry[] table; + + //键值对总数 + private transient int count; + + //容量的阈值,超过此容量将会导致扩容。值为容量*负载因子 + private int threshold; + + //负载因子 + private float loadFactor; + + //hashtable被改变的次数,用于快速失败机制 + private transient int modCount = 0; +``` +成员变量跟HashMap基本类似,但是HashMap更加规范,HashMap内部还定义了一些常量,比如默认的负载因子,默认的容量,最大容量等等。 +##构造方法 + +``` +//可指定初始容量和加载因子 +public Hashtable(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal Load: "+loadFactor); + if (initialCapacity==0) + //初始容量最小值为1 + initialCapacity = 1; + this.loadFactor = loadFactor; + //创建桶数组 + table = new Entry[initialCapacity]; + //初始化容量阈值 + threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); + useAltHashing = sun.misc.VM.isBooted() && + (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); + } + + public Hashtable(int initialCapacity) { + //默认负载因子为0.75 + this(initialCapacity, 0.75f); + } + public Hashtable() { + this(11, 0.75f);//默认容量为11,负载因子为0.75 + } + + public Hashtable(Map t) { + this(Math.max(2*t.size(), 11), 0.75f); + putAll(t); + } +``` + + 1. Hashtable的默认容量为11,默认负载因子为0.75.(HashMap默认容量为16,默认负载因子也是0.75) + 2. Hashtable的容量可以为任意整数,最小值为1,而HashMap的容量始终为2的n次方 + 3. 为避免扩容带来的性能问题,建议指定合理容量。 + +另外我们看到,Hashtable的编码相比较HashMap不是很规范,构造器中出现了硬编码,而HashMap中定义了常量。 + +跟HashMap一样,Hashtable内部也有一个静态类叫Entry,其实是个键值对对象,保存了键和值的引用。也可以理解为一个单链表的结点,因为其持有下一个Entry对象的引用: +##Entry类 + +``` +private static class Entry implements Map.Entry {//键值对对象 + int hash; + final K key; + V value; + Entry next; + protected Entry(int hash, K key, V value, Entry next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + protected Object clone() { + return new Entry<>(hash, key, value, + (next==null ? null : (Entry) next.clone())); + } + // Map.Entry Ops + public K getKey() { + return key; + } + public V getValue() { + return value; + } + public V setValue(V value) { + if (value == null) + throw new NullPointerException(); + V oldValue = this.value; + this.value = value; + return oldValue; + } + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return key.equals(e.getKey()) && value.equals(e.getValue()); + } + public int hashCode() { + return hash ^ value.hashCode(); + } + public String toString() { + return key.toString()+"="+value.toString(); + } + } +``` +再次强调:HashMap和Hashtable存储的是键值对对象,而不是单独的键或值。 +明确了存储方式后,再看put和get方法: +##put方法 + +``` +//向哈希表中添加键值对 +public synchronized V put(K key, V value) { + //确保值不为空 + if (value == null) { + throw new NullPointerException(); + } + // Makes sure the key is not already in the hashtable. + Entry tab[] = table; + //生成键的hash值,若key为null,此方法会抛异常 + int hash = hash(key); + //通过hash值找到其存储位置 + int index = (hash & 0x7FFFFFFF) % tab.length; + //遍历链表 + for (Entry e = tab[index] ; e != null ; e = e.next) { + //若键相同,则直接覆盖旧值 + if ((e.hash == hash) && e.key.equals(key)) { + V old = e.value; + e.value = value; + return old; + } + } + modCount++; + //当前容量超过阈值,需要扩容 + if (count >= threshold) { + //重新构建桶数组,并对数组中所有键值对重哈希,耗时! + rehash(); + tab = table; + hash = hash(key); + index = (hash & 0x7FFFFFFF) % tab.length; + } + Entry e = tab[index]; + //将新结点插到链表首部 + tab[index] = new Entry<>(hash, key, value, e);//生成一个新结点 + count++; + return null; + } +``` +Hasbtable并不允许值和键为空(null),若为空,会抛空指针.大家可能奇怪,put方法在开始处仅对value进行判断,并未对key判断,可能是设计者的疏忽。当然,这并不影响使用,因为当调用hash方法时,若key为空,依然会抛出空指针异常: + +``` +private int hash(Object k) { + if (useAltHashing) { + if (k.getClass() == String.class) { + return sun.misc.Hashing.stringHash32((String) k); + } else { + int h = hashSeed ^ k.hashCode(); + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + } else { + return k.hashCode();//此处可能抛空指针异常 + } + } +``` + + - HashMap计算索引的方式是h&(length-1),而Hashtable用的是模运算,效率上是低于HashMap的 + - Hashtable计算索引时将hash值先与上0x7FFFFFFF,这是为了保证hash值始终为正数 + - 特别需要注意的是这个方法包括下面要讲的若干方法都加了synchronized关键字,也就意味着这个Hashtable是个线程安全的类,这也是它和HashMap最大的不同点 + +#rehash方法 + +``` +protected void rehash() { + //记录旧容量 + int oldCapacity = table.length; + //记录旧的桶数组 + Entry[] oldMap = table; + //可能会溢出 + //扩容为原容量的2倍加1 + int newCapacity = (oldCapacity << 1) + 1; + if (newCapacity - MAX_ARRAY_SIZE > 0) { + if (oldCapacity == MAX_ARRAY_SIZE) + return; + //容量不得超过约定的最大值,若超过依旧保持为约定的最大值 + newCapacity = MAX_ARRAY_SIZE; + } + //创建新的数组 + Entry[] newMap = new Entry[newCapacity]; + //结构改变计数器加一 + modCount++; + threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); + boolean currentAltHashing = useAltHashing; + useAltHashing = sun.misc.VM.isBooted() && + (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); + boolean rehash = currentAltHashing ^ useAltHashing; + table = newMap; + //转移键值对到新数组 + for (int i = oldCapacity ; i-- > 0 ;) { + for (Entry old = oldMap[i] ; old != null ; ) { + Entry e = old; + old = old.next; + if (rehash) { + e.hash = hash(e.key); + } + int index = (e.hash & 0x7FFFFFFF) % newCapacity; + e.next = newMap[index]; + newMap[index] = e; + } + } + } +``` +Hashtable每次扩容,容量都为原来的2倍加1,而HashMap为原来的2倍. +##get方法 + +``` +public synchronized V get(Object key) {//根据键取出对应索引 + Entry tab[] = table; + int hash = hash(key);//先根据key计算hash值 + int index = (hash & 0x7FFFFFFF) % tab.length;//再根据hash值找到索引 + for (Entry e = tab[index] ; e != null ; e = e.next) {//遍历entry链 + if ((e.hash == hash) && e.key.equals(key)) {//若找到该键 + return e.value;//返回对应的值 + } + } + return null;//否则返回null + } +``` +##remove方法 + +``` +public synchronized V remove(Object key) {//删除指定键值对 + Entry tab[] = table; + int hash = hash(key);//计算hash值 + int index = (hash & 0x7FFFFFFF) % tab.length;//计算索引 + for (Entry e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {//遍历entry链 + if ((e.hash == hash) && e.key.equals(key)) {//找到指定键 + modCount++; + if (prev != null) {//修改相关指针 + prev.next = e.next; + } else { + tab[index] = e.next; + } + count--; + V oldValue = e.value; + e.value = null; + return oldValue; + } + } + return null; + } +``` +##clear方法 + +``` +//清空桶数组 +public synchronized void clear() { + Entry tab[] = table; + modCount++; + for (int index = tab.length; --index >= 0; ) + tab[index] = null;//直接置空 + count = 0; + } +``` +##获取键集(keySet)和键值集(entrySet)的方法 + +``` +public Set keySet() { + if (keySet == null)//通过Collections的包装,返回的是线程安全的键集 + keySet = Collections.synchronizedSet(new KeySet(), this); + return keySet; + } + public Set> entrySet() { + if (entrySet==null)//通过Collections的包装,返回的是线程安全的键值集 + entrySet = Collections.synchronizedSet(new EntrySet(), this); + return entrySet; + } +``` +这个KeySet和EntrySet是Hashtable的两个内部类: + +``` +private class KeySet extends AbstractSet { + public Iterator iterator() { + return getIterator(KEYS); + } + public int size() { + return count; + } + public boolean contains(Object o) { + return containsKey(o); + } + public boolean remove(Object o) { + return Hashtable.this.remove(o) != null; + } + public void clear() { + Hashtable.this.clear(); + } + } +``` +#3 总结 + + 1. Hashtable是线程安全的类(HashMap非线程安全) + 2. HashTable不允许null值(key和value都不可以) ;HashMap允许null值(key和value都可以) + 3. HashTable有一个contains(Object value)功能和containsValue(Object +value)功能一样 + 3. Hashtable不允许键重复,若键重复,则新插入的值会覆盖旧值(同HashMap) + 4. HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历 + 4. Hashtable同样是通过链表法解决冲突 + 5. 哈希值的使用不同,HashTable直接使用对象的hashCode; HashMap重新计算hash值,而且用与代替求模 + 5. Hashtable根据hashCode()计算索引时将hash值先与上0x7FFFFFFF,这是为了保证hash值始终为正数 + 6. Hashtable的容量为任意正数(最小为1),而HashMap的容量始终为2的n次方.Hashtable默认容量为 11,HashMap默认容量为16 + 7. Hashtable每次扩容,新容量为旧容量的2倍加1,而HashMap为旧容量的2倍 + 8. Hashtable和HashMap默认负载因子都为0.75 diff --git "a/Java/Java\344\270\216\347\272\277\347\250\213.md" "b/Java/Java\344\270\216\347\272\277\347\250\213.md" new file mode 100644 index 0000000000..eb873095d4 --- /dev/null +++ "b/Java/Java\344\270\216\347\272\277\347\250\213.md" @@ -0,0 +1,61 @@ +并发不一定要依赖多线程(如PHP的多进程并发),但在Java中谈论并发,大多数都与线程脱不开关系 +# 线程的实现 +线程是CPU调度的基本单位。 + +Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的。 +意味着这个方法没有使用或无法使用平台无关的手段来实现。 +# 内核线程(Kernel-Lever Thread,KLT) +直接由操作系统内核(Kermel,下称内核)支持的线程 +由内核来完成线程切换,内核通过操纵调度器(Sheduler) 对线程进行调度,并负责将线程的任务映射到各个处理器上。 +每个内核线程可以视为内核的一个分身,这样OS就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核(Multi-Threads Kernel )。 + +程序一般不会直接去使用KLT,而使用KLT的一种高级接口即轻量级进程(Light Weight Process,LWP),即我们通常意义上所讲的线程,由于每个LWP都由一个KLT支持,因此只有先支持KLT,才能有LWP。这1:1的关系称为`一对一的线程模型`。 +![KLT与LWP之间1:1的关系](https://upload-images.jianshu.io/upload_images/4685968-497d1cfc86f6b084.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 局限性 +由于是基于KLT实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态和内核态中来回切换 +其次,每个LWP都需要有一个KLT的支持,因此LWP要消耗一定的内核资源(如KLT的栈空间),因此一个系统支持LWP的数量是有限的 +#用户线程 +创建,切换和调度各种细节都需要考虑,实现及其困难,已被java、ruby等语言放弃 +# 用户线程混合轻量级进程 + +# Java线程的实现 +用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并 +且可以支持大规模的用户线程并发 +操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。 +在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N :M 的关系 +![用户线程与轮量级进程之间N :M 的关系](https://upload-images.jianshu.io/upload_images/4685968-b64e81020899c37e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +许多UN1X 系列的操作系统,如Solaris、HP-UX 等都提供了N: M 的线程模型实现。 +#.Java 线程 +JDK 1.2 之前是基于称为“绿色线程”(Green-Threads )的用户线程实现 +在JDK 1.2 中替换为基于操作系统原生线程模型来实现 +因此,在目前的JDK 版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java 虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java 线程需要使用哪种线程模型来实现。 +线程模型只对线程的并发规模和操作成本产生影响,对Java 程序的编码和运行过程来说,这些差异都是透明的。 +对于Siun JDK 来说,它的Windows 版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows 和Linux系统提供的线程模型就是一对一的 +而在Solaris 平台中,由于操作系统的线程特性可以同时支持一对一(通过Bound +Threaids或Alternate Libthread实现)及多对多( 通过LWP/Thread Based Synchronization +实现) 的线程模型,因此在Solaris 版的JDK 中也对应提供了两个平台专有的虚拟桃参数: +-XX:+UseLWPSynchronization (默认值) 和-XX:+UseBoyndThreads 来明确指定虚拟 +机使用哪种线程模型。 +# Java线程调度 +- 线程调度 +系统为线程分配处理器使用权的过程,主要调度方式有两种 + - 协同式线程调度(Cooperative Threads-Scheduling) + - 抢占式线程调度(Preemptive Threads-Scheduling ) + +使用协同式调度的多线程系统,线程执行时间由线程本身控制,线程把自己工作执行完后,要主动通知系统切换到另外一个线程上。 +协同式多线程 +- 最大好处 +实现简单,而且由于线程要把自己的事情干完后才进行线程切换,切换操作对线程白己是可知的,所以没有什么线程同步的问题 +- 坏处也很明显 + 线程执行时间不可控制 + +使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身决定,在这种实现线程调度的方式下,线程执行时间系统可控的 +Java使用的线程调度方式就是抢占式调度 + +虽然Java线程调度是系统自动完成的,但是我们还是可“建议”系统给某些线程多分配一点执行时间,可以通过设置线程优先级来完成。Java 语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY 至Thread.MAX_PRIORITY ),在两个线程同时处于Ready 状态时,优先级越高的线程越容易被系统选择执行。 + +Java 的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于OS,虽然现在很多OS都提供线程优先级的概念,但是并不见得能与Java线程的优先级对应,如Solaris中有2147483648 (232 )种优先级,但Windows中就只有7种,比Java 线程优先级多的系统还好说,中间留下一点空位就可以了,但比Java线程优先级少的系统,就不得不出现几个优先级相同的情况了 + +不仅仅是说在一些平台上不同的优先级实际会变得相同这一点,还有其他情况让我们不能太依赖优先级:优先级可能会被系统自行改变。 +例如,在Windows 系统中存在一个称为“优先级推进器”(Priority Boosting,当然它可以被 +关闭掉) 的功能,它的大致作用就是当系统发现一个线程执行得特别“勤奋努力”的话,可能会越过线程优先级去为它分配执行时间。因此,我们不能在程序中通过优先级完全准确地判断一组状态都为Ready 的线程将会先执行哪一个 diff --git "a/Java/Java\344\270\255Collections-sort()\346\226\271\346\263\225\347\232\204\346\274\224\345\217\230.md" "b/Java/Java\344\270\255Collections-sort()\346\226\271\346\263\225\347\232\204\346\274\224\345\217\230.md" new file mode 100644 index 0000000000..7f82c72a90 --- /dev/null +++ "b/Java/Java\344\270\255Collections-sort()\346\226\271\346\263\225\347\232\204\346\274\224\345\217\230.md" @@ -0,0 +1,74 @@ +先看一段代码 +```java + + List list = new ArrayList(); + list.add(1); + list.add(2); + list.add(3); + + Iterator it = list.iterator(); + + Collections.sort(list); + + while (it.hasNext()) { + System.out.println(it.next()); + } +``` +Java7 运行效果 +```java +1 +2 +3 +``` +Java8 运行效果 +![](http://upload-images.jianshu.io/upload_images/4685968-dd0b824ef1f2c8e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +#结果分析 +在上面的代码中,我们先得到list的iterator,然后对list进行排序,最后遍历iterator。 +从Java8的错误信息中可以看出it.next( )方法中检查list是否已经被修改,由于在遍历之前进行了一次排序,所以checkForComodification方法抛出异常ConcurrentModificationException。 +这个可以理解,因为排序,肯定会修改list +但是为啥Java7中没问题呢? + +#源码分析 +首先看checkForComodification方法是如何判断的,如下所示: +```java +final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); +} +``` +可以看出,有两个内部成员变量用来判断是否发生了修改: modCount 和 expectedModCount。 + +Iterator中记录了expectedModCount +List中记录了modCount +checkForComodification方法通过比较modCount 和 expectedModCount来判断是否发生了修改。 + +在Java7中,Collections.sort( list )调用的是Collections自身的sort方法,如下所示: +```java +public static > void sort(List list) { + Object[] a = list.toArray(); + Arrays.sort(a); + ListIterator i = list.listIterator(); + for (int j=0; j> void sort(List list) { + list.sort(null); +} +ArrayList的sort方法实现如下: +![](http://upload-images.jianshu.io/upload_images/4685968-cba099d83fb00e29.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +可以看出最后一行,modCount++修改了modCount字段 +所以checkForComodification方法会抛出异常 + +#关于Java8中Collections.sort方法的修改 +之前,Collection.sort复制list中的元素以排序到数组中,对数组进行排序,然后使用数组中的元素更新列表,并将默认方法List.sort委托给Collection.sort。这不是一个最佳的设计 +从8u20发布起,Collection.sort将被委托给List.sort,这意味着,例如,现有的以ArrayList实例调用Collection.sort的代码将使用由ArrayList实现的最佳排序 + diff --git "a/Java/Java\344\270\255\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/Java/Java\344\270\255\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 0000000000..57f0a9bfce --- /dev/null +++ "b/Java/Java\344\270\255\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,197 @@ +主要用到的是这两个类 +- java.util.regex.Pattern +- java.util.regex.Matcher。 + +Pattern对应正则表达式,一个Pattern与一个String对象关联,生成一个Matcher,它对应Pattern在String中的一次匹配; +调用Matcher对象的find()方法,Matcher对象就会更新为下一次匹配的匹配信息。示例: +```java +Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-]]d{2}"); +String string = "2010-12-20 2011-02-14"; +Matcher matcher = pattern.matcher(string); +while(matcher.find()) { + System.out.println(matcher.group(0)); +} +``` +#Pattern +Pattern是Java语言中的正则表达式对象。 +要使用正则表达式,首先必须从字符串“编译”出Pattern对象,这需要用到`Pattern.compile(String regex)` +e.g +`Pattern pattern = Pattern.compile("a.b+");` + +如果要指定匹配模式,可以在表达式中使用(?modifier)修饰符指定,也可以使用预定义常量。 +下面的两个Pattern对象的生成方法不同,结果却是等价的。 +```java +Pattern pattern = Pattern.compile("(?i)a.b+"); +Pattern pattern = Pattern.compile("a.b+",Pattern.CASE_INSENSITIVE); +``` +如果要同时指定多种模式,可以连写模式修饰符,也可以直接用|运算符将预定义常量连接起来,以下两个Pattern对象也是等价的。 +```java +Pattern pattern = Pattern.compile("(?is)a.b+"); +Pattern pattern = Pattern.compile("a.b+",Pattern.CASE_INSENSITIVE | Pattern.DOTALL); +``` +##Pattern的主要方法 +![](https://upload-images.jianshu.io/upload_images/4685968-2d16306314c523f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +可检验字符串`input`能否由正则表达式`regex`匹配 +因为是静态方法,所以不需要编译生成各个对象,方便随手使用。 +要注意的是,它检验的是“整个字符串能否由表达式匹配”,而不是“表达式能否在字符串中找到匹配”。 +可以认为`regex`的首尾自动加上了匹配字符串起始和结束位置的锚点 \A和\z 。 +``` +Pattern.matches("\\d{6}","a123456"); //false +Pattern.matches("\\d{6}","123456"); //true +``` +![](https://upload-images.jianshu.io/upload_images/4685968-1b2adbbaecc536c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +通常,Pattern对象需要配合下面将要介绍的Matcher一起完成正则操作。如果只用正则表达式来切分字符串,只用Pattern的这个方法也可以。 + +这个方法接收的参数类型是`CharSequence`它可能有点陌生 +![](https://upload-images.jianshu.io/upload_images/4685968-595ead2c2e84e926.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +它是String的父类m因而可以应对常见的各种表示“字符串”的类。下面的代码仅以String为例: +``` +String s = "2018-3-26"; +Pattern pattern = Pattern.compile("\\s+"); +for(String part : pattern.split(s)){ + System.out.println(part); +} +``` +这个方法与上面的方法很相似,只是多了一个参数limit,它用来限定返回的String数组的最大长度。 +也就是说,它规定了字符串至多只能“切”limit-1次。如果不需要对字符串比较大,进行尽可能多的切分,使用这个方法。 +``` +String s = " 2010-12-20 "; +Pattern pattern = Pattern.compile("\\s+"); +for(String part : Pattern.split(s,2)){ + System.out.println(part); +} +``` +既然limit是一个int类型,那么它自然可以设定为各种值,下表总结了limit在各个取值区间对结果的影响(未指定limit时,最终返回包含n个元素的数组,实际能切分的次数是 n-1 ): + +- limit < 0 +等于未设定limit时,保留末尾的空字符串 +- limit = 0 +等于未设定limit时,切分n-1次,忽略末尾的空字符串 +- 0 < limit < n +返回数组包含limit个元素,切分limit-1次,最后一个元素是第limit-1次切分后,右侧剩下的所有文本 +- limit >= n +等于未指定limit时 +![](https://upload-images.jianshu.io/upload_images/4685968-fa810753f0963f0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +用来取消字符串text中所有转义字符的特殊含义,实质就是在字符串首尾添加 \Q 和 \E。 +通常,如果需要把某个字符串作为没有任何特殊意义的正则表达式(比如从外界读入的字符串,用在某个复杂的正则表达式中),就可以使用这个方法: +``` +"aacb".matches("a*.b"); //true +"a*.b".matches("a*.b"); //false +"a*.b".matches("a*.b"); //false +"a*.b".matches(Pattern.quote("a*.b")); //true +``` +#Matcher +Matcher可以理解为“某次具体匹配的结果对象” +把编译好的Pattern对象“应用”到某个String对象上,就获得了作为“本次匹配结果”的Matcher对象。 +之后,就可以通过它获得关于匹配的信息。 +```java +Pattern pattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); +Matcher matcher = pattern.matcher("2010-12-20 2011-02-14"); +while(matcher.find()){ + System.out.println(matcher.group()); +} +``` +对编译好的Pattern对象调用matcher(String text)方法,传入要匹配的字符串text,就得到了Matcher对象,每次调用一次find()方法,如果返回true,就表示“找到一个匹配”,此时可以通过下面的若干方法获得关于本次匹配的信息。 + +1. String group(int n) + +返回当前匹配中第n对捕获括号捕获的文本,如果n为0,则取匹配的全部内容;如果n小于0或者大于最大分组编号数,则报错。 + +2. String group() + +返回当前匹配的全部文本,相当于group(0)。 + +3. int groupCount() + +返回此Matcher对应Pattern对象中包含的捕获分组数目,编号为0的默认分组不计在内。 + +4. int start(n) + +返回当前匹配中第n对捕获括号匹配的文本在原字符串中的起始位置。 + +5. int start() + +返回当前匹配的文本在原字符串中的起始位置,相当于start(0)。 + +6. int end(n) + +返回当前匹配中第n对捕获括号匹配的文本在原字符串中的结束位置。 + +7. int end() + +返回当前匹配的文本在原字符串中的结果位置,相当于end(0)。 + +8. String replaceAll(String replacement) + +如果进行正则表达式替换,一般用到的是Matcher的replaceAll()方法,它会将原有文本中正则表达式能匹配的所有文本替换为replaceement字符串。 + +#String +许多时候只需要临时使用某个正则表达式,而不需要重复使用,这时候每次都生成Pattern对象和Matcher对象再操作显得很烦琐。所以,Java的String类提供了正则表达式操作的静态成员方法,只需要String对象就可以执行正则表达式操作。 +![](https://upload-images.jianshu.io/upload_images/4685968-0cf5b4ba1db44cc8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +这个方法判断当前的String对象能否由正则表达式`regex`匹配。 +请注意,这里的“匹配”指的并不是`regex`能否在`String`内找到匹配,而是指`regex`匹配整个`String`对象,因此非常适合用来做数据校验。 +``` +"123456".matches("\\d{6}"); //true +"a123456".matches("\\d{6}"); //true +2. String replaceFirst(String regex,String replacement) +``` +用来替换正则表达式regex在字符串中第一次能匹配的文本,可以在replacement字符串中用$num引用regex中对应捕获分组匹配的文本。 +`"2010-12-20 2011-02-14".replaceFirst("(\\d{4})-(\\d{2})-(\\d{2})","$2/$3/$1");` +![](https://upload-images.jianshu.io/upload_images/4685968-f1582726822b7e82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +用来进行所有的替换,它的结果等同于Matcher类的`replaceAll()`,replacement字符串中也可以用$num的表示法引用regex中对应捕获分组匹配的文本。 +`"2010-12-20 2011-02-14".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})","$2/$3/$1");` +![image.png](https://upload-images.jianshu.io/upload_images/4685968-5fd716b8e540d5e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +等价于Pattern中对应的split()方法 +# [Java String.split()用法小结](http://www.cnblogs.com/mingforyou/p/3299569.html) + +在java.lang包中有String.split()方法,返回是一个数组 + +我在应用中用到一些,给大家总结一下,仅供大家参考: + +1、如果用“.”作为分隔的话,必须是如下写法,String.split("\\."),这样才能正确的分隔开,不能用String.split("."); + +2、如果用“|”作为分隔的话,必须是如下写法,String.split("\\|"),这样才能正确的分隔开,不能用String.split("|"); + +“.”和“|”都是转义字符,必须得加"\\"; + +3、如果在一个字符串中有多个分隔符,可以用“|”作为连字符,比如,“acount=? and uu =? or n=?”,把三个都分隔出来,可以用String.split("and|or"); + +使用String.split方法分隔字符串时,分隔符如果用到一些特殊字符,可能会得不到我们预期的结果。  + +我们看jdk doc中说明   + +public String[] split(String regex) + + Splits this string around matches of the given regular expression.  + +参数regex是一个 regular-expression的匹配模式而不是一个简单的String,他对一些特殊的字符可能会出现你预想不到的结果,比如测试下面的代码用竖线 | 分隔字符串,你将得不到预期的结果 +``` +String[] aa = "aaa|bbb|ccc".split("|"); + + //String[] aa = "aaa|bbb|ccc".split("\\|"); 这样才能得到正确的结果 + + for (int i = 0 ; i 若是无界阻塞队列,队列不会出现满的情况,所以使用put或offer方法永远不会被阻塞,使用offer方法时,永远返回true +> +> BlockingQueue 不接受 null 元素,抛 NullPointerException +null 被用作指示 poll 操作失败的警戒值(无法通过编译) +> +> +BlockingQueue 实现主要用于生产者/使用者队列,但它另外还支持 Collection 接口。 +因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。 +然而,这种操作通常表现并不高效,只能有计划地偶尔使用,比如在取消排队信息时。 + +> BlockingQueue 的实现是线程安全的 +所有排队方法都可使用内置锁或其他形式的并发控制来自动达到它们的目的 +然而,大量的Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有必要自动执行,除非在实现中特别说明 +因此,举例来说,在只添加 c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常) + +> BlockingQueue 实质上不支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项 +这种功能的需求和使用有依赖于实现的倾向 +例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释 + +#2 生产者和消费者例子 +在介绍具体的阻塞类之前,先来看看阻塞队列最常应用的场景,即生产者和消费者例子 +一般而言,有n个生产者,各自生产产品,并放入队列 +同时有m个消费者,各自从队列中取出产品消费 +当队列已满时(队列可以在初始化时设置Capacity容量),生产者会在放入队列时阻塞;当队列空时,消费者会在取出产品时阻塞。代码如下: +``` +public class BlockingQueueExam { + public static void main(String[] args) throws InterruptedException { + BlockingQueue blockingQueue = new LinkedBlockingQueue<>(3); + ExecutorService service = Executors.newCachedThreadPool(); + for (int i = 0; i < 5; i++) { + service.submit(new Producer("Producer" + i, blockingQueue)); + } + for (int i = 0; i < 5; i++) { + service.submit(new Consumer("Consumer" + i, blockingQueue)); + } + service.shutdown(); + } +} + +class Producer implements Runnable { + private final String name; + private final BlockingQueue blockingQueue; + private static Random rand = new Random(47); + private static AtomicInteger productID = new AtomicInteger(0); + + Producer(String name, BlockingQueue blockingQueue) { + this.name = name; + this.blockingQueue = blockingQueue; + } + + @Override + public void run() { + try { + for (int i = 0; i < 10; i++) { + SECONDS.sleep(rand.nextInt(5)); + String str = "Product" + productID.getAndIncrement(); + blockingQueue.add(str); + //注意,这里得到的size()有可能是错误的 + System.out.println(name + " product " + str + ", queue size = " + blockingQueue.size()); + } + System.out.println(name + " is over"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} + +class Consumer implements Runnable { + private final String name; + private final BlockingQueue blockingQueue; + private static Random rand = new Random(47); + + Consumer(String name, BlockingQueue blockingQueue) { + this.name = name; + this.blockingQueue = blockingQueue; + } + + @Override + public void run() { + try { + for (int i = 0; i < 10; i++) { + SECONDS.sleep(rand.nextInt(5)); + String str = blockingQueue.take(); + //注意,这里得到的size()有可能是错误的 + System.out.println(name + " consume " + str + ", queue size = " + blockingQueue.size()); + } + System.out.println(name + " is over"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} +``` +以上代码中的阻塞队列是LinkedBlockingQueue,初始化容量为3 +生产者5个,每个生产者间隔随机时间后生产一个产品put放入队列,每个生产者生产10个产品 +消费者也是5个,每个消费者间隔随机时间后take取出一个产品进行消费,每个消费者消费10个产品 +可以看到,当队列满时,所有生产者被阻塞 +当队列空时,所有消费者被阻塞 +代码中还用到了AtomicInteger原子整数,用来确保产品的编号不会混乱 + +# 2 **Java里的阻塞队列** +![BlockingQueue的实现类](http://upload-images.jianshu.io/upload_images/4685968-d05fd46243a9bf6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +至JDK8,Java提供了7个阻塞队列 +- ArrayBlockingQueue:数组结构组成的有界阻塞队列 +- LinkedBlockingQueue:链表结构组成的有界(默认MAX_VALUE容量)阻塞队列 +- PriorityBlockingQueue:支持优先级调度的无界阻塞队列,排序基于compareTo或Comparator完成 +- DelayQueue:支持延迟获取元素,在OS调用中较多或者应用于某些条件变量达到要求后需要做的事情 +- SynchronousQueue:一个不存储元素的阻塞队列。 +- LinkedTransferQueue:链表结构的TransferQueue,无界阻塞队列 +- LinkedBlockingDeque:链表结构的双向阻塞队列 +## 2.1 LinkedBlockingQueue和ArrayBlockingQueue +![](https://upload-images.jianshu.io/upload_images/4685968-469fae51c96576fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a8d5673e8779077b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +基于数组的阻塞队列实现,在`ArrayBlockingQueue`内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 + +  ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 +都是FIFO队列 +正如其他Java集合一样,链表形式的队列,其存取效率要比数组形式的队列高 +但是在一些并发程序中,数组形式的队列由于具有一定的可预测性,因此可以在某些场景中获得更好的效率 + +另一个不同点在于,ArrayBlockingQueue支持“公平”策略 +若在构造函数中指定了“公平”策略为true,可以有效避免一些线程被“饿死”,公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性" +`BlockingQueue queue = new ArrayBlockingQueue<>(3, true); ` +总体而言,LinkedBlockingQueue是阻塞队列的最经典实现,在不需要“公平”策略时,基本上使用它就够了 +> 所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞的线程先访问队列 +> 非公平性是对先等待的线程是非公平的,当队列有可用空间时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列 + +为保证公平性,通常会降低吞吐量.我们可以使用以下代码创建一个公平的阻塞队列 +``` +ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true); +``` +![访问者的公平性是使用可重入锁实现的](http://upload-images.jianshu.io/upload_images/4685968-1e4244e433e8c991.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +##2.2 SynchronousQueue同步队列 +![](https://upload-images.jianshu.io/upload_images/4685968-37f19ec95f4dbe98.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +比较特殊的阻塞队列,它具有以下几个特点: +1. 一个插入方法的线程必须等待另一个线程调用取出 +2. 队列没有容量Capacity(或者说容量为0),事实上队列中并不存储元素,它只是提供两个线程进行信息交换的场所 +3. 由于以上原因,队列在很多场合表现的像一个空队列。不能对元素进行迭代,不能peek元素,poll会返回null +4. 队列中不允许存入null元素 +5. SynchronousQueue如同ArrayedBlockingQueue一样,支持“公平”策略 + +下面是一个例子,5个Producer产生产品,存入队列 +5个Consumer从队列中取出产品,进行消费。 + +``` +public class SynchronizeQueueExam { + public static void main(String[] args) { + SynchronousQueue queue = new SynchronousQueue<>(false); + ExecutorService service = Executors.newCachedThreadPool(); + for (int i = 0; i < 5; i++) { + service.submit(new Producer(queue, "Producer" + i)); + } + for (int i = 0; i < 5; i++) { + service.submit(new Consumer(queue, "Consumer" + i)); + } + service.shutdown(); + } + + static class Producer implements Runnable { + private final SynchronousQueue queue; + private final String name; + private static Random rand = new Random(47); + private static AtomicInteger productID = new AtomicInteger(0); + + Producer(SynchronousQueue queue, String name) { + this.queue = queue; + this.name = name; + } + + @Override + public void run() { + try { + for (int i = 0; i < 5; i++) { + TimeUnit.SECONDS.sleep(rand.nextInt(5)); + String str = "Product" + productID.incrementAndGet(); + queue.put(str); + System.out.println(name + " put " + str); + } + System.out.println(name + " is over."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + + static class Consumer implements Runnable { + private final SynchronousQueue queue; + private final String name; + + Consumer(SynchronousQueue queue, String name) { + this.queue = queue; + this.name = name; + } + + @Override + public void run() { + try { + for (int i = 0; i < 5; i++) { + String str = queue.take(); + System.out.println(name + " take " + str); + } + System.out.println(name + " is over."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} +``` +##2.3 PriorityBlockingQueue优先级阻塞队列 +![](https://upload-images.jianshu.io/upload_images/4685968-1873eb6013d54379.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +1. 队列中的元素总是按照“自然顺序”排序,或者根据构造函数中给定的Comparator进行排序 +2. 队列中不允许存在null,也不允许存在不能排序的元素 +3. 对于排序值相同的元素,其序列是不保证的,当然你可以自己扩展这个功能 +4. 队列容量是没有上限的,但是如果插入的元素超过负载,有可能会引起OOM +5. 使用迭代子iterator()对队列进行轮询,其顺序不能保证 +6. 具有BlockingQueue的put和take方法,但是由于队列容量没有上线,所以put方法是不会被阻塞的,但是take方法是会被阻塞的 +7. 可以给定初始容量,这个容量会按照一定的算法自动扩充 + +下面是一个PriorityBlockingQueue的例子,例子中定义了一个按照字符串倒序排列的队列 +5个生产者不断产生随机字符串放入队列 +5个消费者不断从队列中取出随机字符串 +同一个线程取出的字符串基本上是倒序的(因为不同线程同时存元素,因此取的字符串打印到屏幕上往往不是倒序的了) +``` +public class PriorityBlockingQueueExam { + public static void main(String[] args) { + //创建一个初始容量为3,排序为字符串排序相反的队列 + PriorityBlockingQueue queue = new PriorityBlockingQueue<>(3, (o1, o2) -> { + if (o1.compareTo(o2) < 0) { + return 1; + } else if (o1.compareTo(o2) > 0) { + return -1; + } else { + return 0; + } + }); + + ExecutorService service = Executors.newCachedThreadPool(); + for (int i = 0; i < 5; i++) { + service.submit(new Producer("Producer" + i, queue)); + } + for (int i = 0; i < 5; i++) { + service.submit(new Consumer("Consumer" + i, queue)); + } + service.shutdown(); + } + + static class Producer implements Runnable { + private final String name; + private final PriorityBlockingQueue queue; + private static Random rand = new Random(System.currentTimeMillis()); + + Producer(String name, PriorityBlockingQueue queue) { + this.name = name; + this.queue = queue; + } + + @Override + public void run() { + for (int i = 0; i < 10; i++) { + String str = "Product" + rand.nextInt(1000); + queue.put(str); + System.out.println("->" + name + " put " + str); + try { + TimeUnit.SECONDS.sleep(rand.nextInt(5)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + System.out.println(name + " is over"); + } + } + + + static class Consumer implements Runnable { + private final String name; + private final PriorityBlockingQueue queue; + private static Random rand = new Random(System.currentTimeMillis()); + + Consumer(String name, PriorityBlockingQueue queue) { + this.name = name; + this.queue = queue; + } + + @Override + public void run() { + try { + for (int i = 0; i < 10; i++) { + String str = queue.take(); + System.out.println("<-" + name + " take " + str); + TimeUnit.SECONDS.sleep(rand.nextInt(5)); + } + System.out.println(name + " is over"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} +``` +##2.4 DelayQueue +队列中只能存入Delayed接口实现的对象 +![DelayQueue](http://upload-images.jianshu.io/upload_images/4685968-66f774931c2df19a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +![](http://upload-images.jianshu.io/upload_images/4685968-e9e3297ac26ca792.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](http://upload-images.jianshu.io/upload_images/4685968-57058c0b0980efe6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +DelayQueue中存入的对象要同时实现getDelay和compareTo +- getDelay方法是用来检测队列中的元素是否到期 +- compareTo方法是用来给队列中的元素进行排序 +![DelayQueue持有一个PriorityBlockingQueue](http://upload-images.jianshu.io/upload_images/4685968-9d79acebc32249fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +每个Delayed对象实际上都放入了这个队列,并按照compareTo方法进行排序 + +当队列中对象的getDelay方法返回的值<=0(即对象已经超时)时,才可以将对象从队列中取出 +若使用take方法,则方法会一直阻塞,直到队列头部的对象超时被取出 +若使用poll方法,则当没有超时对象时,直接返回null + +总结来说,有如下几个特点: +1. 队列中的对象都是Delayed对象,它实现了getDelay和compareTo +2. 队列中的对象按照优先级(按照compareTo)进行了排序,队列头部是最先超时的对象 +3. take方法会在没有超时对象时一直阻塞,直到有对象超时;poll方法会在没有超时对象时返回null。 +4. 队列中不允许存储null,且iterator方法返回的值不能确保按顺序排列 + +下面是一个列子,特别需要注意getDelay和compareTo方法的实现: + +``` +public class DelayQueueExam { + public static void main(String[] args) throws InterruptedException { + DelayQueue queue = new DelayQueue<>(); + for (int i = 0; i < 10; i++) { + queue.put(new DelayElement(1000 * i, "DelayElement" + i)); + } + while (!queue.isEmpty()) { + DelayElement delayElement = queue.take(); + System.out.println(delayElement.getName()); + } + } + + static class DelayElement implements Delayed { + private final long delay; + private long expired; + private final String name; + + DelayElement(int delay, String name) { + this.delay = delay; + this.name = name; + expired = System.currentTimeMillis() + delay; + } + + public String getName() { + return name; + } + + @Override + public long getDelay(TimeUnit unit) { + return unit.convert(expired - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + @Override + public int compareTo(Delayed o) { + long d = (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); + return (d == 0) ? 0 : ((d < 0) ? -1 : 1); + } + } +} +``` +DelayQueue通过PriorityQueue,使得超时的对象最先被处理,将take对象的操作阻塞住,避免了遍历方式的轮询,提高了性能。在很多需要回收超时对象的场景都能用上 +### BlockingDeque阻塞双向队列 +BlockingDeque中各种特性上都非常类似于BlockingQueue,事实上它也继承自BlockingQueue,它们的不同点主要在于BlockingDeque可以同时从队列头部和尾部增删元素。 +因此,总结一下BlockingDeque的四组增删元素的方法: +第一组,抛异常的方法,包括addFirst(e),addLast(e),removeFirst(),removeLast(),getFirst()和getLast(); +第二组,返回特殊值的方法,包括offerFirst(e) ,offerLast(e) ,pollFirst(),pollLast(),peekFirst()和peekLast(); +第三组,阻塞的方法,包括putFirst(e),putLast(e),takeFirst()和takeLast(); +第四组,超时的方法,包括offerFirst(e, time, unit),offerLast(e, time, unit),pollFirst(time, unit)和pollLast(time, unit)。 +BlockingDeque目前只有一个实现类LinkedBlockingDeque,其用法与LinkedBlockingQueue非常类似,这里就不给出实例了。 +### TransferQueue传输队列 +TransferQueue继承自BlockingQueue,之所以将它独立成章,是因为它是一个非常重要的队列,且提供了一些阻塞队列所不具有的特性。 +简单来说,TransferQueue提供了一个场所,生产者线程使用transfer方法传入一些对象并阻塞,直至这些对象被消费者线程全部取出。前面介绍的SynchronousQueue很像一个容量为0的TransferQueue。 +下面是一个例子,一个生产者使用transfer方法传输10个字符串,两个消费者线程则各取出5个字符串,可以看到生产者在transfer时会一直阻塞直到所有字符串被取出: + +``` +public class TransferQueueExam { + public static void main(String[] args) { + TransferQueue queue = new LinkedTransferQueue<>(); + ExecutorService service = Executors.newCachedThreadPool(); + service.submit(new Producer("Producer1", queue)); + service.submit(new Consumer("Consumer1", queue)); + service.submit(new Consumer("Consumer2", queue)); + service.shutdown(); + } + + static class Producer implements Runnable { + private final String name; + private final TransferQueue queue; + + Producer(String name, TransferQueue queue) { + this.name = name; + this.queue = queue; + } + @Override + public void run() { + System.out.println("begin transfer objects"); + + try { + for (int i = 0; i < 10; i++) { + queue.transfer("Product" + i); + System.out.println(name + " transfer "+"Product"+i); + } + System.out.println("after transformation"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(name + " is over"); + } + } + + static class Consumer implements Runnable { + private final String name; + private final TransferQueue queue; + private static Random rand = new Random(System.currentTimeMillis()); + + Consumer(String name, TransferQueue queue) { + this.name = name; + this.queue = queue; + } + + @Override + public void run() { + try { + for (int i = 0; i < 5; i++) { + String str = queue.take(); + System.out.println(name + " take " + str); + TimeUnit.SECONDS.sleep(rand.nextInt(5)); + } + System.out.println(name + " is over"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} +``` +上面的代码中只使用了transfer方法,TransferQueue共包括以下方法: +1. transfer(E e),若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则会将元素e插入到队列尾部,并进入阻塞状态,直到有消费者线程取走该元素。 +2. tryTransfer(E e),若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),即立刻移交之; 否则返回false,并且不进入队列,这是一个非阻塞的操作。 +3. tryTransfer(E e, long timeout, TimeUnit unit) 若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则会将元素e插入到队列尾部,并且等待被消费者线程获取消费掉,若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除。 +4. hasWaitingConsumer() 判断是否存在消费者线程。 +5. getWaitingConsumerCount() 获取所有等待获取元素的消费线程数量。 +再来看两个生产者和两个消费者的例子: + +``` +public class TransferQueueExam2 { + public static void main(String[] args) { + TransferQueue queue = new LinkedTransferQueue<>(); + ExecutorService service = Executors.newCachedThreadPool(); + service.submit(new Producer("Producer1", queue)); + service.submit(new Producer("Producer2", queue)); + service.submit(new Consumer("Consumer1", queue)); + service.submit(new Consumer("Consumer2", queue)); + service.shutdown(); + } + + static class Producer implements Runnable { + private final String name; + private final TransferQueue queue; + + Producer(String name, TransferQueue queue) { + this.name = name; + this.queue = queue; + } + + @Override + public void run() { + System.out.println(name + " begin transfer objects"); + + try { + for (int i = 0; i < 5; i++) { + queue.transfer(name + "_Product" + i); + System.out.println(name + " transfer " + name + "_Product" + i); + } + System.out.println(name + " after transformation"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(name + " is over"); + } + } + + static class Consumer implements Runnable { + private final String name; + private final TransferQueue queue; + private static Random rand = new Random(System.currentTimeMillis()); + + Consumer(String name, TransferQueue queue) { + this.name = name; + this.queue = queue; + } + + @Override + public void run() { + try { + for (int i = 0; i < 5; i++) { + String str = queue.take(); + System.out.println(name + " take " + str); + TimeUnit.SECONDS.sleep(rand.nextInt(5)); + } + System.out.println(name + " is over"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} +``` +它的作者Doug Lea 这样评价它:TransferQueue是一个聪明的队列,它是ConcurrentLinkedQueue, SynchronousQueue (在公平模式下), 无界的LinkedBlockingQueues等的超集。 +所以,在合适的场景中,请尽量使用TransferQueue,目前它只有一个实现类LinkedTransferQueue。 + +### ConcurrentLinkedQueue并发链接队列 +#### 1 并发与并行 +写到此处时,应该认真梳理一下关于多线程编程中的一些名词了,具体包括多线程(MultiThread)、并发(Concurrency)和并行(Parrellism)。多线程的概念应该是比较清晰的,是指计算机和编程语言都提供线程的概念,多个线程可以同时在一台计算机中运行。 +而并发和并行则是两个非常容易混淆的概念,第一种区分方法是以程序在计算机中的执行方式来区分。我称之为“并发执行”和“并行执行”的区分: +并发执行是指多个线程(例如n个)在一台计算机中宏观上“同时”运行,它们有可能是一个CPU轮换的处理n个线程,也有可能是m个CPU以各种调度策略来轮换处理n个线程; +并行执行是指多个线程(n个)在一台计算机的多个CPU(m个,m>=n)上微观上同时运行,并行执行时操作系统不需要调度这n个线程,每个线程都独享一个CPU持续运行直至结束。 +第二种区分方法则是“并发编程”和“并行编程”的区别: +并发编程可以理解为多线程编程,并发编程的代码必定以“并发执行”的方式运行; +并行编程则是一种更加特殊的编程方法,它需要使用特殊的编程语言(例如Cilk语言),或者特殊的编程框架(例如Parallel Java 2 Library)。另外,我在本系列的第一篇中提到的Fork-Join框架也是一种并行编程框架。 +#### 2 并发的基础 +理解了并发的概念,我们再来看首次遇到的带有并发字眼(Concurrent)的类ConcurrentLinkedQueue,并发链接队列。 +目前看来,可以这么认为,在java.util.concurrency包内,凡是带有Concurrent字眼的类,都是以CAS为基础的非阻塞工具类。例如ConcurrentLinkedQueue、ConcurrentLinkedDeque、ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet。 +好了,那么什么是CAS呢?CAS即CompareAndSwap“比较并交换”,具体的细节将会在后续的关于原子变量(Atomic)章节中介绍。简而言之,当代的很多CPU提供了一种CAS指令,由于指令运行不会被打断,因此依赖这种指令就可以设计出一种不需要锁的非阻塞并发算法,依赖这种算法,就可以设计出各种并发类。 +#### 3 各类队列的例子 +下面的例子中,我们使用参数控制,分别测试了四种队列在多个线程同时存储变量时的表现: + +``` +public class ConcurrentLinkedQueueExam { + private static final int TEST_INT = 10000000; + + public static void main(String[] args) throws InterruptedException { + long start = System.currentTimeMillis(); + Queue queue = null; + if (args.length < 1) { + System.out.println("Usage: input 1~4 "); + System.exit(1); + } + int param = Integer.parseInt(args[0]); + switch (param) { + case 1: + queue = new LinkedList<>(); + break; + case 2: + queue = new LinkedBlockingQueue<>(); + break; + case 3: + queue = new ArrayBlockingQueue(TEST_INT * 5); + break; + case 4: + queue = new ConcurrentLinkedQueue<>(); + break; + default: + System.out.println("Usage: input 1~4 "); + System.exit(2); + } + System.out.println("Using " + queue.getClass().getSimpleName()); + + ExecutorService service = Executors.newCachedThreadPool(); + for (int i = 0; i < 5; i++) { + service.submit(new Putter(queue, "Putter" + i)); + } + TimeUnit.SECONDS.sleep(2); + for (int i = 0; i < 5; i++) { + service.submit(new Getter(queue, "Getter" + i)); + } + service.shutdown(); + service.awaitTermination(1, TimeUnit.DAYS); + long end = System.currentTimeMillis(); + System.out.println("Time span = " + (end - start)); + System.out.println("queue size = " + queue.size()); + } + + static class Putter implements Runnable { + private final Queue queue; + private final String name; + + Putter(Queue queue, String name) { + this.queue = queue; + this.name = name; + } + + + @Override + public void run() { + for (int i = 0; i < TEST_INT; i++) { + queue.offer(1); + } + System.out.println(name + " is over"); + } + } + + static class Getter implements Runnable { + private final Queue queue; + private final String name; + + Getter(Queue queue, String name) { + this.queue = queue; + this.name = name; + } + + @Override + public void run() { + int i = 0; + while (i < TEST_INT) { + synchronized (Getter.class) { + if (!queue.isEmpty()) { + queue.poll(); + i++; + } + } + } + System.out.println(name + " is over"); + } + } +} +``` + +``` +输入1,结果如下: + +Using LinkedList +… +Time span = 16613 +queue size = 10296577 +输入2,结果如下: + +Using LinkedBlockingQueue +… +Time span = 16847 +queue size = 0 +输入3,结果如下: + +Using ArrayBlockingQueue +… +Time span = 6815 +queue size = 0 +输入4,结果如下: + +Using ConcurrentLinkedQueue +… +Time span = 22802 +queue size = 0 +``` +分析运行的结果,有如下结论: +第一,非并发类例如LinkedList在多线程环境下运行是会出错的,结果的最后一行输出了队列的size值,只有它的size值不等于0,这说明在多线程运行时许多poll操作并没有弹出元素,甚至很多offer操作也没有能够正确插入元素。其他三种并发类都能够在多线程环境下正确运行; +第二,并发类也不是完全不需要注意加锁,例如这一段代码: + +``` +while (i < TEST_INT) { + synchronized (Getter.class) { + if (!queue.isEmpty()) { + queue.poll(); + i++; + } + } +} +``` +如果不加锁,那么isEmpty和poll之间有可能被其他线程打断,造成结果的不确定性。 +第三,本例中LinkedBlockingQueue和ArrayBlockingQueue并没有因为生产-消费关系阻塞,因为容量设置得足够大。它们的元素插入和弹出操作是加锁的,而ConcurrentLinkedQueue的元素插入和弹出操作是不加锁的,而观察性能其实并没有数量级上的差异(有待进一步测试)。 +第四,ArrayBlockingQueue性能明显好于LinkedBlockingQueue,甚至也好于ConcurrentLinkedQueue,这是因为它的内部存储结构是原生数组,而其他两个是链表,需要new一个Node。同时,链表也会造成更多的GC。 +### ConcurrentLinkedDeque并发链接双向队列 +ConcurrentLinkedDeque与ConcurrentLinkedQueue非常类似,不同之处仅在于它是一个双向队列。 + +##3 **阻塞队列的实现原理** + +> Java的并发队列,具体包括BlockingQueue阻塞队列、BlockingDeque阻塞双向队列、TransferQueue传输队列、ConcurrentLinkedQueue并发链接队列和ConcurrentLinkedDeque并发链接双向队列。BlockingQueue和BlockingDeque的内部使用锁来保护元素的插入弹出操作,同时它们还提供了生产者-消费者场景的阻塞方法;TransferQueue被用来在多个线程之间优雅的传递对象;ConcurrentLinkedQueue和ConcurrentLinkedDeque依靠CAS指令,来实现非阻塞的并发算法。 + + +若队列为空,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前队列有元素的呢?让我们看看JDK是如何实现的。 +***使用通知模式实现***。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。通过查看源码发现ArrayBlockingQueue使用了Condition来实现,代码如下。 + +``` + private final Condition notFull; + private final Condition notEmpty; + + public ArrayBlockingQueue(int capacity, boolean fair) { + // 省略其他代码 + notEmpty = lock.newCondition(); + notFull = lock.newCondition(); + + public void put(E e) throws InterruptedException { + checkNotNull(e); + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (count == items.length) + notFull.await(); + enqueue(e); + } finally { + lock.unlock(); + } + } + + public E take() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + while (count == 0) + notEmpty.await(); + return dequeue(); + } finally { + lock.unlock(); + } + } + + private void enqueue(E x) { + // assert lock.getHoldCount() == 1; + // assert items[putIndex] == null; + final Object[] items = this.items; + items[putIndex] = x; + if (++putIndex == items.length) + putIndex = 0; + count++; + notEmpty.signal(); + } + +``` +当往队列里插入一个元素时,如果队列不可用,那么阻塞生产者主要通过 +LockSupport.park(this)来实现。 + +``` + public final void await() throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + Node node = addConditionWaiter(); + int savedState = fullyRelease(node); + int interruptMode = 0; + while (!isOnSyncQueue(node)) { + LockSupport.park(this); + if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) + break; + } + if (acquireQueued(node, savedState) && interruptMode != THROW_IE) + interruptMode = REINTERRUPT; + if (node.nextWaiter != null) // clean up if cancelled + unlinkCancelledWaiters(); + if (interruptMode != 0) + reportInterruptAfterWait(interruptMode); + } +``` + +继续进入源码,发现调用setBlocker先保存一下将要阻塞的线程,然后调用unsafe.park阻塞当前线程。 + +``` + public static void park(Object blocker) { + Thread t = Thread.currentThread(); + setBlocker(t, blocker); + unsafe.park(false, 0L); + setBlocker(t, null); + } +``` +unsafe.park是个native方法,代码如下。 + +``` +public native void park(boolean isAbsolute, long time); +``` +park这个方法会阻塞当前线程,只有以下4种情况中的一种发生时,该方法才会返回。 + + - 与park对应的unpark执行或已经执行时。“已经执行”是指unpark先执行,然后再执行park的情况。 + - 线程被中断时。 + - 等待完time参数指定的毫秒数时。 + - 异常现象发生时,这个异常现象没有任何原因。 diff --git "a/Java/Java\344\270\255\347\232\204VO,PO\347\255\211.md" "b/Java/Java\344\270\255\347\232\204VO,PO\347\255\211.md" new file mode 100644 index 0000000000..df7182aae0 --- /dev/null +++ "b/Java/Java\344\270\255\347\232\204VO,PO\347\255\211.md" @@ -0,0 +1,156 @@ +O/R Mapping 是 Object Relational Mapping(对象关系映射)的缩写。 +通俗点讲,就是将对象与关系数据库绑定,用对象来表示关系数据。 +在O/R Mapping的世界里,有两个基本的也是重要的需要了解,即VO,PO。 + +VO,值对象(Value Object) +PO,持久对象(Persisent Object) +它们是由一组属性及其get/set组成。从结构上看,它们并没有什么不同的地方。但从其意义和本质上来看是完全不同的。 + +# 1. +- VO +new关键字创建,由GC回收 +- PO +向数据库中添加新数据时创建,删除数据库中数据时削除的。并且它只能存活在一个数据库连接中,断开连接即被销毁。 + +#2. +- VO是值对象,精确点讲它是业务对象,是存活在业务层的,是业务逻辑使用的,它存活的目的就是为数据提供一个生存的地方。 +- PO则是有状态的,每个属性代表其当前的状态。它是物理数据的对象表示。使用它,可以使我们的程序与物理数据解耦,并且可以简化对象数据与物理数据之间的转换。 + +# 3. +- VO的属性是根据当前业务的不同而不同的,也就是说,它的每一个属性都一一对应当前业务逻辑所需要的数据的名称。 +- PO的属性是跟数据库表的字段一一对应的。 + +## PO对象需要实现序列化接口。 + +**PO是持久化对象**,它只是将物理数据实体的一种对象表示,为什么需要它?因为它可以简化我们对于物理实体的了解和耦合,简单地讲,可以简化对象的数据转换为物理数据的编程。VO是什么?它是值对象,准确地讲,它是业务对象,是生活在业务层的,是业务逻辑需要了解,需要使用的,再简单地讲,它是概念模型转换得到的。 + +首先说PO和VO吧,它们的关系应该是相互独立的,一个VO可以只是PO的部分,也可以是多个PO构成,同样也可以等同于一个PO(当然我是指他们的属性)。正因为这样,PO独立出来,数据持久层也就独立出来了,它不会受到任何业务的干涉。又正因为这样,业务逻辑层也独立开来,它不会受到数据持久层的影响,业务层关心的只是业务逻辑的处理,至于怎么存怎么读交给别人吧!不过,另外一点,如果我们没有使用数据持久层,或者说没有使用hibernate,那么PO和VO也可以是同一个东西,虽然这并不好。 + +* * * + +### PO(persistant object) 持久对象 + +在o/r映射的时候出现的概念,如果没有o/r映射,没有这个概念存在了。通常对应数据模型(数据库),本身还有部分业务逻辑的处理。可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作。 + +# VO(value object) 值对象 +通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。 +但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要. +个人觉得同DTO(数据传输对象),在web上传递。 + +### TO(Transfer Object),数据传输对象 + +在应用程序不同tie(关系)之间传输的对象 + +### BO(business object) 业务对象 + +从业务模型的角度看,见UML元件领域模型中的领域对象。封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作。 + +### POJO(plain ordinary java object) 简单无规则java对象 + +纯的传统意义的java对象。就是说在一些Object/Relation Mapping工具中,能够做到维护数据库表记录的persisent object完全是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法。我的理解就是最基本的Java Bean,只有属性字段及setter和getter方法!。 + +### DAO(data access object) 数据访问对象 + +是一个sun的一个标准j2ee设计模式,这个模式中有个接口就是DAO,它负持久层的操作。为业务层提供接口。此对象用于访问数据库。通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合VO, 提供数据库的CRUD操作... + +### O/R Mapper 对象/关系 映射 + +定义好所有的mapping之后,这个O/R Mapper可以帮我们做很多的工作。通过这些mappings,这个O/R Mapper可以生成所有的关于对象保存,删除,读取的SQL语句,我们不再需要写那么多行的DAL代码了。 +* * * + +> **_PO_** +> +> ### persistant object 持久对象 +> +> 最形象的理解就是一个PO就是数据库中的一条记录。 +> +> 好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。 +> **_BO_** +> +> ### business object 业务对象 +> +> 主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 +> +> 比如一个简历,有教育经历、工作经历、社会关系等等。 +> +> 我们可以把教育经历对应一个PO,工作经历对应一个PO,社会关系对应一个PO。 +> +> 建立一个对应简历的BO对象处理简历,每个BO包含这些PO。 +> +> 这样处理业务逻辑时,我们就可以针对BO去处理。 +> **_VO_** : +> +> ### value object 值对象 +> +> ViewObject表现层对象 +> +> 主要对应界面显示的数据对象。对于一个WEB页面,用一个VO对象对应整个界面的值。 +> **_DTO_** : +> +> ### Data Transfer Object数据传输对象 +> +> 主要用于远程调用等需要大量传输对象的地方。 +> +> 比如我们一张表有100个字段,那么对应的PO就有100个属性。 +> +> 但是我们界面上只要显示10个字段, +> +> 客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端, +> +> 这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服>务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就>转为VO +> **_POJO_** : +> +> ### plain ordinary java object 简单java对象 +> +> 个人感觉POJO是最常见最多变的对象,是一个中间对象,也是我们最常打交道的对象。 +> +> 一个POJO持久化以后就是PO +> +> 直接用它传递、传递过程中就是DTO +> +> 直接用来对应表示层就是VO +> **_DAO_**: +> +> ### data access object数据访问对象 +> +> 这个大家最熟悉,和上面几个O区别最大,基本没有互相转化的可能性和必要. +> +> 主要用来封装对数据库的访问。通过它可以把POJO持久化为PO,用PO组装出来VO、DTO + + + +**_VO:值对象、视图对象_** + +**_PO:持久对象_** + +**_QO:查询对象_** + +**_DAO:数据访问对象_** + +**_DTO:数据传输对象_** + +* * * + +**_struts 里的 ActionForm 就是个VO;_** + +**_hibernate里的 实体bean就是个PO,也叫POJO;_** + +**_hibernate里的Criteria 就相当于一个QO;_** + +**_在使用hibernate的时候我们会定义一些查询的方法,这些方法写在接口里,可以有不同的实现类.而这个接口就可以说是个DAO._** + +**_个人认为QO和DTO差不多._** + +* * * + +PO或叫BO,与数据库最接近的一层,是ORM中的O,基本上是数据库字段对应BO中的一个属性,为了同步与安全性考虑,最好只给DAO或者Service调用,而不要用packcode,backingBean,或者BO调。 + +**_DAO,数据访问层,把VO,backingBean中的对象可以放入。。。。 + +DTO,很少用,基本放入到DAO中,只是起到过渡的作用。 + +QO,是把一些与持久性查询操作与语句放入。。 + +VO,V层中用到的基本元素与方法等放其中。如果要其调用BO,则要做BO转换VO,VO转换BO操作。VO的好处是其页面的元素属性多于BO,可起到很好的作用。。。。_** + + diff --git "a/Java/Java\344\270\255\347\232\204\351\224\201\344\274\230\345\214\226.md" "b/Java/Java\344\270\255\347\232\204\351\224\201\344\274\230\345\214\226.md" new file mode 100644 index 0000000000..46b849f712 --- /dev/null +++ "b/Java/Java\344\270\255\347\232\204\351\224\201\344\274\230\345\214\226.md" @@ -0,0 +1,125 @@ +我们知道synchronized是重量级锁,效率不怎么样,不过在JDK6中对synchronize的实现进行了各种优化,使得它显得不是那么重了,那么JVM采用了那些优化手段呢 +# 锁优化 +如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。  +锁主要存在四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态 +他们会随着竞争的激烈而逐渐升级。 +注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率 +## 自旋锁 +线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。 +同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。 +  +所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。 + +怎么等待呢?执行一段无意义的循环即可(自旋)。 +  +自旋等待不能替代阻塞,先不说对处理器数量的要求,虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。 +如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。 +所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。  + +自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整;  +如果通过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来诸多不便。假如我将参数调整为10,但是系统很多线程都是等你刚刚退出的时候就释放了锁(假如你多自旋一两次就可以获取锁),你是不是很尴尬。于是JDK1.6引入自适应的自旋锁,让虚拟机会变得越来越聪明。 +## 适应自旋锁 +JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。 +所谓自适应就意味着自旋的次数不再是固定的,它由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 + +它怎么做呢? +线程如果自旋成功了,那么下次自旋的次数会更多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。  + +有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确,虚拟机会变得越来越聪明。 +## 锁消除 +为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这时JVM会对这些同步锁进行锁消除。 + +锁消除的依据是逃逸分析的数据支持。 +  +如果不存在竞争,为什么还需要加锁呢?所以锁消除可以节省毫无意义的请求锁的时间。 +变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是对于我们程序员来说这还不清楚么?但有时候程序并不是我们所想的那样,我们虽然没有显示使用锁,但是我们在使用一些JDK的内置API时,如StringBuffer、Vector、HashTable等,这时候会存在隐形的加锁操作。比如StringBuffer的append()方法,Vector的add()方法 + +``` + public void vectorTest(){ + Vector vector = new Vector(); + for(int i = 0 ; i < 10 ; i++){ + vector.add(i + ""); + } + + System.out.println(vector); + } +``` +在运行这段代码时,JVM可以明显检测到变量vector没有逃逸出方法vectorTest()之外,所以JVM可以大胆地将vector内部的加锁操作消除。 +## 锁粗化 +我们知道在使用同步锁的时候,需要让同步块的作用范围尽可能小,即仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁  + +在大多数的情况下,上述观点是正确的,本人也一直坚持着这个观点。但是如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,所以引入锁粗化的概念: +就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁 + +如上面实例:vector每次add的时候都需要加锁操作,JVM检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到for循环外 + +## 轻量级锁 +主要目的是在没有多线程竞争的前提下,减少传统的重量级锁使用OS的互斥量(mutex)产生的性能消耗。 +当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤如下:  +###**获取锁**  +1. 判断当前对象是否处于无锁状态(hashcode、0、01) + - 是 +JVM将首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word) + - 否 +执行3  +2. JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指正 + - 成功(表示竞争到锁) +将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操作 + - 失败 +执行3  +3. 判断当前对象的Mark Word是否指向当前线程的栈帧 + - 是(表示当前线程已经持有当前对象的锁) +直接执行同步代码块 + - 否(只能说明该锁对象已经被其他线程抢占) +轻量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态 +###**释放锁**  +轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下:  +1. 取出获取轻量级锁时保存在Displaced Mark Word中的数据  +2. 用CAS操作将取出的数据替换到当前对象的Mark Word中 + - 成功 +说明释放锁成功 + - 失败 +执行3  +3. CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程 + +对于轻量级锁,其性能提升的依据是 +"对于绝大部分的锁,在整个生命周期内都是不会存在竞争的" +如果打破这个依据则除了互斥的开销外,还有额外的CAS操作,因此在有多线程竞争的情况下,轻量级锁比重量级锁更慢 + +* * * + +下图是轻量级锁的获取和释放过程  +![](http://upload-images.jianshu.io/upload_images/4685968-835dc27a4cab0094.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 偏向锁 +目的:在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。 +上面提到了轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的。 +那么偏向锁是如何来减少不必要的CAS操作呢? +我们可以查看Mark work的结构就明白了。 +只需要检查是否为偏向锁、锁标识为以及ThreadID即可,处理流程如下:  +###**获取锁**  +1. 检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01  +2. 若为可偏向状态,则测试线程ID是否为当前线程ID + - 是 +执行5 + - 否 +执行3  +3. 如果线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争 + - 成功 +将Mark Word的线程ID替换为当前线程ID, + - 失败 +执行4  +4. CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块 +5. 执行同步代码块 +###**释放锁**  +偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。 +偏向锁的撤销需要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤如下:  +1. 暂停拥有偏向锁的线程,判断锁对象石是否还处于被锁定状态 +2. 撤销偏向锁,恢复到无锁状态(01)或者轻量级锁的状态 + +* * * + +下图是偏向锁的获取和释放流程  +![](http://upload-images.jianshu.io/upload_images/4685968-15051bba71218318?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 重量级锁 +重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高 diff --git "a/Java/Java\344\270\255\347\261\273\345\236\213\345\217\202\346\225\260\342\200\234-T-\342\200\235\345\222\214\346\227\240\347\225\214\351\200\232\351\205\215\347\254\246\342\200\234---\342\200\235\347\232\204\345\214\272\345\210\253.md" "b/Java/Java\344\270\255\347\261\273\345\236\213\345\217\202\346\225\260\342\200\234-T-\342\200\235\345\222\214\346\227\240\347\225\214\351\200\232\351\205\215\347\254\246\342\200\234---\342\200\235\347\232\204\345\214\272\345\210\253.md" new file mode 100644 index 0000000000..7bdb763e2b --- /dev/null +++ "b/Java/Java\344\270\255\347\261\273\345\236\213\345\217\202\346\225\260\342\200\234-T-\342\200\235\345\222\214\346\227\240\347\225\214\351\200\232\351\205\215\347\254\246\342\200\234---\342\200\235\347\232\204\345\214\272\345\210\253.md" @@ -0,0 +1,72 @@ +首先要区分开两种不同的场景: +- 声明一个泛型类或泛型方法 +类型参数“”主要用于第一种,声明泛型类或泛型方法 +- 使用泛型类或泛型方法 +无界通配符“”主要用于第二种,使用泛型类或泛型方法 + +# 1 声明泛型类的类型参数 +List最应该出现的地方,应该是定义一个泛型List容器 +但List是库里自带的容器,看看ArrayList的源码头一行: +![](https://upload-images.jianshu.io/upload_images/4685968-84ea03662073cea7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +ArrayList中的“E”也是类型参数。只是表示容器中元素Element的时候,习惯用“E” +换一个简单的例子,我们自己定义一个新泛型容器叫Box。 +``` +class Box{ + private T item1; + private T item2; +} +``` +为什么这里要用类型参数?因为这是一种”约束“,为了保证Box里的item1, item2都是同一个类型T。Box,代表两个item都是String。Box里两个item都是Integer。 + +List容器库里都帮我们写好了,所以我们是不会去定义List的 + +那什么时候会出现List +要么是作为泛型类的成员字段或成员方法的参数间接出现。还是刚才Box的例子, +``` +class Box{ + private List item; + public List get(){return item;} + public void set(List t){item=t;} +} +``` +现在Box类里有三个地方出现了List: +- 成员字段item的类型 +- get( )方法的返回值 +- set( )方法的参数 + +这里写成List为了表示和Box类型参数保持一致 +# 2 声明泛型方法 +另外一种会出现List的地方是泛型方法 +比如Function类的reduce是个静态泛型方法,负责对列表里的所有元素求和 +这里的List出现在参数,函数返回值和函数内部,也是为了保持泛型类型的一致性 +``` +class Fuction{ + public static List reduce(List list){ + //...do something + } +} +``` +# 3 声明泛型类不能用无界通配符 +反观List,首先要明确`通配符不能拿来声明泛型` +像下面这样用通配符"?"来表示类型参数的约束是不行的 +![Error Example](https://upload-images.jianshu.io/upload_images/4685968-ffe6bcebda2d1168.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +通配符是拿来使用定义好的泛型的 +比如用声明List容器的变量类型,然后用一个实例对象给它赋值的时候就比较灵活。 +![](https://upload-images.jianshu.io/upload_images/4685968-b0642daae444a300.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 4 的坑 +List这个写法非常坑。因为,这时候通配符会捕获具体的String类型,但编译器不叫它String,而是起个临时的代号,比如”capture#1“ +所以以后再也不能往list里存任何元素,包括String,唯一能存的就是null +![](https://upload-images.jianshu.io/upload_images/4685968-7f914a83dbef3b07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-973e44c8d7a90e88.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +另外如果拿List做参数,也会有奇妙的事情发生。还是刚才Box的例子,有get()和set()两个方法,一个存,一个取。 +![](https://upload-images.jianshu.io/upload_images/4685968-1ae09c9e4b5cfdb0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +新的getSet()方法,只是把item先用get()方法读出来,然后再用set()方法存回去。按理说不可能有问题。实际运行却会报错。 +``` +error: incompatible types: Object cannot be converted to capture#1 +``` +原因和前面一样,通配符box.set()的参数类型被编译器捕获,命名为capture#1,和box.get()返回的Object对象无法匹配 + +解决方法,是要给getSet()方法写一个辅助函数 +![](https://upload-images.jianshu.io/upload_images/4685968-7e4ad9a8b84347bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 5. 有界通配符 +实际更常用的是或者两种,带有上下界的通配符 diff --git "a/Java/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md" "b/Java/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md" new file mode 100644 index 0000000000..fa277f0abb --- /dev/null +++ "b/Java/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md" @@ -0,0 +1,371 @@ +# 1 Java内存模型(JMM)的意义 +![](https://upload-images.jianshu.io/upload_images/4685968-92a1fdf48be19e06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-23e5e9a49adeb5a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![JMM 与硬件内存架构对应关系](https://upload-images.jianshu.io/upload_images/4685968-5e1374a0abf9c929.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![JMM抽象结构图](https://upload-images.jianshu.io/upload_images/4685968-d4ea8843a88a940d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,规定 +- 线程如何,何时能看到其他线程修改过的共享变量的值 +- 在必要时如何同步地访问共享变量 + +以实现让Java程序在各种平台下都能达到一致性的内存访问效果。 +# 2 主内存与工作内存 +- Java内存模型的主要目标是定义`各个变量的访问规则` +即在虚拟机中将变量存储到内存和从内存中取出变量值这样的底层细节 + +此处的`变量`包括了实例域,静态域和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不存在竞争 + +为了获得比较好的执行效率,JMM并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权限。 + +JMM`规定` +- 所有的变量都存储在主内存(Main Memory) +- 每条线程有自己的工作内存(Working Memory) +保存了该线程使用到的`变量的主内存副本拷贝`(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝!) +线程对变量的所有操作(读,赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量 +volatile变量依然有工作内存的拷贝,只是他特殊的操作顺序性规定,看起来如同直接在主内存读写 +不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均要通过主内存 +![线程、主内存、工作内存三者的交互关系](https://upload-images.jianshu.io/upload_images/4685968-12209b12d578a2ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-bb3d37c155cd82d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +JVM模型与JMM不是同一层次的内存划分,基本是没有关系的,硬要对应起来,从变量,内存,工作内存的定义来看 + - 主内存 === Java堆中的对象实例数据部分 + - 工作内存 === 虚拟机栈中的部分区域 + +从更底层的层次来说 +- 主内存直接对应于物理硬件的内存 +- 为了更好的运行速度,虚拟机(甚至硬件系统的本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存器中,因为程序运行时主要访问读写的是工作内存 +# 3 内存间同步操作 +一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节 +JMM定义了以下8种操作来完成,都具备`原子性` +- lock(锁定) +作用于主内存变量,把一个变量标识为一条线程独占的状态 +- unlock(解锁) +作用于主内存变量,把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定 +unlock之前必须将变量值同步回主内存 +- read(读取) +作用于主内存变量,把一个变量的值从主内存传输到工作内存,以便随后的load +- load(载入) +作用于工作内存变量,把read从主内存中得到的变量值放入工作内存的变量副本 +- use(使用) +作用于工作内存变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作 +- assign(赋值) +作用于工作内存变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 +- store(存储) +作用于工作内存变量,把工作内存中一个变量的值传送到主内存,以便随后的write操作使用 +- write(写入) +作用于主内存变量,把store操作从工作内存中得到的值放入主内存的变量中 +![JMM 同步操作](https://upload-images.jianshu.io/upload_images/4685968-9dd14e316ab68184.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 把一个变量从主内存`复制`到工作内存 +就要顺序执行read和load + +- 把变量从工作内存`同步`回主内存 +就要顺序地执行store和write操作 + +JMM只要求上述两个操作必须`按序执行`,而没有保证连续执行 +也就是说read/load之间、store/write之间可以插入其它指令 +如对主内存中的变量a,b访问时,一种可能出现的顺序是read a->readb->loadb->load a + +JMM规定执行上述八种基础操作时必须满足如下 +## 3.1 同步规则 +- 不允许read/load、store/write操作之一单独出现 +不允许一个变量从主内存读取了但工作内存不接收,或从工作内存发起回写但主内存不接收 +- 不允许一个线程丢弃它的最近的assign +即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存 +- 新变量只能在主内存“诞生”,不允许在工作内存直接使用一个未被初始化(load或assign)的变量 +换话说就是一个变量在实施use,store之前,必须先执行过assign和load +- 如果一个变量事先没有被load锁定,则不允许对它执行unlock,也不允许去unlock一个被其它线程锁定的变量 +- 对一个变量执行unloack前,必须把此变量同步回主内存中(执行store,write) + +**`volatile`**变量可以被看作是一种 **"轻量的 `synchronized`** +可以说是JVM提供的最轻量级的同步机制 + +当一个变量定义为volatile后 +- 保证此变量对所有线程的可见性 +# 4 原子性(Atomicity) +一次只允许一个线程持有某锁,一次只有一个线程能使用共享数据 + +由JMM直接保证的原子性变量操作包括read、load、use、assign、store和write六个,大致可以认为基础数据类型的访问读写是原子性的 + +如果应用场景需要一个更大范围的原子性保证,JMM还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock与unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐匿地使用这两个操作,这两个字节码指令反映到Java代码中就是同步块synchronized关键字,因此在synchronized块之间的操作也具备原子性 +#5 可见性(Visibility) +当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改 + +由于现代可共享内存的多处理器架构可能导致一个线程无法马上(甚至永远)看到另一个线程操作产生的结果。所以 Java 内存模型规定了 JVM 的一种最小保证:什么时候写入一个变量对其他线程可见。 + +在现代可共享内存的多处理器体系结构中每个处理器都有自己的缓存,并周期性的与主内存协调一致。假设线程 A 写入一个变量值 V,随后另一个线程 B 读取变量 V 的值 +在下列情况下,线程 B 读取的值可能不是线程 A 写入的最新值: +- 执行线程 A 的处理器把变量 V 缓存到寄存器中。 +- 执行线程 A 的处理器把变量 V 缓存到自己的缓存中,但还没有同步刷新到主内存中去。 +- 执行线程 B 的处理器的缓存中有变量 V 的旧值。 + +JMM通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性,无论是普通变量还是volatile变量都是如此 + +普通变量与volatile变量的区别是 +`volatile的特殊规则保证了新值能立即同步到主内存,以及每使用前立即从内存刷新` +因此volatile保证了线程操作时变量的可见性,而普通变量则不能保证 + +除了volatile,Java还有两个关键字能实现可见性 +- synchronized +由“对一个变量执行`unlock`前,必须先把此变量同步回主内存中(执行`store`和`write`)”这条规则获得的 +- final +被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见final字段的值 + +必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程可见,对域中的值做赋值和返回的操作通常是原子性的,但递增/减并不是 + +volatile对所有线程是立即可见的,对volatile变量所有的写操作都能立即返回到其它线程之中,换句话说,volatile变量在各个线程中是一致的,但并非基于volatile变量的运算在并发下是安全的 + +volatile变量在各线程的工作内存中不存在一致性问题(在各个线程的工作内存中volatile变量也可以存在不一致,但由于 +`每次使用之前都要先刷新 ` ,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但Java里的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的 +```java +public class Atomicity { + int i; + void f(){ + i++; + } + void g(){ + i += 3; + } +} +``` +编译后文件 +```java +void f(); + 0 aload_0 [this] + 1 dup + 2 getfield concurrency.Atomicity.i : int [17] + 5 iconst_1 + 6 iadd + 7 putfield concurrency.Atomicity.i : int [17] + // Method descriptor #8 ()V + // Stack: 3, Locals: 1 + void g(); + 0 aload_0 [this] + 1 dup + 2 getfield concurrency.Atomicity.i : int [17] + 5 iconst_3 + 6 iadd + 7 putfield concurrency.Atomicity.i : int [17] +} +``` +每个操作都产生了一个 get 和 put ,之间还有一些其他的指令 +因此在获取和修改之间,另一个线程可能会修改这个域 +所以,这些操作不是原子性的 + +再看下面这个例子是否符合上面的描述 +```java +public class AtomicityTest implements Runnable { + private int i = 0; + public int getValue() { + return i; + } + + private synchronized void evenIncrement() { + i++; + i++; + } + + public void run() { + while(true) + evenIncrement(); + } + + public static void main(String[] args) { + ExecutorService exec = Executors.newCachedThreadPool(); + AtomicityTest at = new AtomicityTest(); + exec.execute(at); + while(true) { + int val = at.getValue(); + if(val % 2 != 0) { + System.out.println(val); + System.exit(0); + } + } + } +} +output: +1 +``` +该程序将找到奇数值并终止 +尽管`return i `原子性,但缺少同步使得其数值可以在处于不稳定的中间状态时被读取 +由于 i 不是 volatile ,存在可视性问题 +getValue() 和 evenIncrement() 必须synchronized + + +对于基本类型的读/写操作被认为是安全的原子性操作 +但当对象处于不稳定状态时,仍旧很有可能使用原子性操作来访问他们 +最明智的做法是遵循同步的规则 + +**volatile 变量只保证可见性** +在不符合以下条件规则的运算场景中,仍需要通过加锁(使用synchronized或JUC中的原子类)来保证`原子性` +- 运算结果不依赖变量的当前值,或者能确保只有单一的线程修改变量的值 +- 变量不需要与其它的状态变量共同参与不可变类约束 + +基本上,若一个域可能会被多个任务同时访问or这些任务中至少有一个是写任务,那就该将此域设为volatile +当一个域定义为 volatile 后,将具备 +> **1.保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,其它线程每次使用前立即从主内存刷新** +但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成 +> **2.禁止指令重排序。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障**(指令重排序时不能把后面的指令重排序到内存屏障之前的位置) +这些操作的目的是用线程中的局部变量维护对该域的精确同步 +#6 指令重排序 +编译器生成指令的次序,可以不同于源代码所暗示的“显然”版本。重排序后的指令,对于优化执行以及成熟的全局寄存器分配算法的使用,都是大有脾益的,它使得程序在计算性能上有了很大的提升。 + +重排序类型包括: +- 编译器生成指令的次序,可以不同于源代码所暗示的“显然”版本。 +- 处理器可以乱序或者并行的执行指令。 +- 缓存会改变写入提交到主内存的变量的次序。 + +有序性:**即程序执行的顺序按照代码的先后顺序执行** + + +使用volatile变量的第二个语义是`禁止指令重排序优化` + +普通变量仅保证该方法执行过程所有依赖赋值结果的地方能获取到正确结果,而不保证变量赋值操作的顺序与代码执行顺序一致 +因为在一个线程的方法执行过程中无法感知到这一点,这也就是JMM中描述的所谓的 +`线程内表现为串行的语义(Within-Thread As-If-Serial Sematics)` +``` +Map configOptions; +char[] configText; +//此变量必须定义为volatile +volatile boolean initialized = false; + +//假设以下代码在线程A中执行 + +//模拟读取配置信息,当读取完成后 +//将initialized设置为true来通知其它线程配置可用 +configOptions = new HashMap(); +configText = readConfigFile(fileName); +processConfigOptions(configText, configOptions); +initialized = true; + +//假设以下代码在线程B中执行 + +//等线程A待initialized为true,代表线程A已经把配置信息初始化完成 +while(!initialized) { + sleep(); +} +//使用线程A中初始化好的配置信息 +doSomethingWithConfig(); +``` +如果定义`initialized`时没有使用`volatile`,就可能会由于指令重排序优化,导致位于线程A中最后一行的代码`initialized = true`被提前执行,这样在线程B中使用配置信息的代码就可能出现错误,而`volatile`关键字则可以完美避免 + +volatile变量读操作性能消耗与普通变量几乎无差,但写操作则可能会稍慢,因为它需要在代码中插入许多内存屏障指令来保证处理器不发生乱序执行 +不过即便如此,大多数场景下volatile的总开销仍然要比锁小,我们在volatile与锁之中选择的`唯一依据仅仅是volatile的语义能否满足使用场景的需求` +![单例模式](https://upload-images.jianshu.io/upload_images/4685968-da1ded2979016345.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![字节码指令](https://upload-images.jianshu.io/upload_images/4685968-c1ddcbde64dfe68d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![汇编指令](https://upload-images.jianshu.io/upload_images/4685968-63333e96dda3f0aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +`volatile`修饰的变量,赋值后(前面`mov %eax,0x150 (%esi)` 这句便是赋值操作) 多执行了一个`1ock add1 $ 0x0,(%esp)`,这相当于一个内存屏障(Memory Barrier/Fence,指重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU 访问内存时,并不需要内存屏障 +但如果有两个或更多CPU 访问同一块内存,且其中有一个在观测另一个,就需要内存屏障来保证一致性了 + +这句指令中的`add1 $0x0, (%esp)`(把ESP 寄存器的值加0) 显然是一个空操作(采用这个空操作而不是空操作指令`nop` 是因为IA32手册规定`lock `前缀不允许配合`nop` 指令使用),关键在于lock 前缀,查询IA32 手册,它的作用是使得本CPU 的Cache写入内存,该写入动作也会引起别的CPU 或者别的内核无效化(Inivalidate) 其Cache,这种操作相当于对Cache 中的变量做了一次`store和write`。所以通过这样一个空操作,可让前面volatile 变量的修改对其他CPU 立即可见。 + +那为何说它禁止指令重排序呢? + 硬件架构上,指令重排序指CPU 采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。但并不是说指令任意重排,CPU需要能正确处理指令依赖情况以保障程序能得出正确的执行结果 +譬如指令1把地址A中的值加10,指令2把地址A 中的值乘以2,指令3把地址B 中的值减去了,这时指令1和指令2是有依赖的,它们之间的顺序不能重排,(A+10) *2 与A*2+10显然不等,但指令3 可以重排到指令i、2之前或者中间,只要保证CPU 执行后面依赖到A、B值的操作时能获取到正确的A 和B 值即可。所以在本CPU 中,重排序看起来依然是有序的。因此`lock add1 $0x0,(%esp)` 指令把修改同步到内存时,意味着所有之前的操作都已经执行完成,这样便形成了“指令重排序无法越过内存屏障”的效果 + +举个例子 +``` +int i = 0; +boolean flag = false; +i = 1; //语句1 +flag = true; //语句2 +``` +从代码顺序上看,语句1在2前,JVM在真正执行这段代码的时候会保证**语句1一定会在语句2前面执行吗?**不一定,为什么呢?**这里可能会发生指令重排序(Instruction Reorder)** +比如上面的代码中,语句1/2谁先执行对最终的程序结果并无影响,就有可能在执行过程中,语句2先执行而1后**虽然处理器会对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同,**靠什么保证?**数据依赖性** + +> **编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序** + +举例 +``` +double pi = 3.14; //A +double r = 1.0; //B +double area = pi * r * r; //C +``` +![三个操作的数据依赖关系](http://upload-images.jianshu.io/upload_images/4685968-73d51df8127c3f33?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。 +因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。 +但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序 +![该程序的两种执行顺序](http://upload-images.jianshu.io/upload_images/4685968-d3ab423cc91a8857?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +这里所说的数据依赖性仅针对**单个处理器中执行的指令序列和单个线程中执行的操作**,在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果 +但在**多线程程序中,**对存在控制依赖的操作重排序,可能会改变程序的执行结果。这是就需要**内存屏障来保证可见性了** + +回头看一下JMM对volatile 变量定义的特殊规则 +假定T 表示一个线程,V 和W 分别表示两个volatile变量,那么在进行read, load, use,assign,store,write时需要满定如下规则 +- 只有当线程T 对变量V 执行的前一个动作是load ,线程T 方能对变量V 执行use;并且,只有当线程T 对变量V 执行的后一个动作是`use`,线程T才能对变量V执行load.线程T 对变量V 的`use `可认为是和线程T对变量V的load,read相关联,必须连续一起出现(这条规则要求在工作内存中,每次使用V前都必须先从主内存刷新最新的值语,用于保证能看见其他线程对变量V所做的修改后的值) +- 只有当线程T 对变量V 执行的前一个动作是 `assign` ,线程T才能对变量V 执行`store` +并且,只有当线程T对变量V执行的后一个动作是`store` ,线程T才能对变量V执行`assign ` +线程T对变量V的`assign`可以认为是和线程T对变量V的store,write相关联,必须连续一起出现(这条规则要求在工作内存中,每次修改V 后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改) +- 假定动作A 是线程T 对变量V实施的`use`或`assign `,假定动作F 是和动作A 相关联的`load `或`store `,假定动作P 是和动作F 相应的对变量V 的`read` 或`write` +类似的,假定动作B 是线程T 对变量W 实施的`use `或`assign` 动作,假定动作G是和动作B 相关联的`load `或`store`,假定动作Q 是和动作G 相应的对变量W的`read`或`write` +如果A 先于B,那么P先于Q (这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同) +## 6.1 对于Long和double型变量的特殊规则 +对于32位平台,64位的操作需要分两步来进行,与主存的同步。所以可能出现“半个变量”的状态。 +在实际开发中,目前各种平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待,因此我们在编码时一般不需要把用到的long和double变量专门声明为volatile。 +## 6.2 内存屏障 +### 6.2.1 分类 +- Load Barrier 读屏障 +- Store Barrier写屏障 + +### 6.2.2 有序性(Ordering) +JMM中程序的天然有序性可以总结为一句话: +`如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。` +前半句是指“线程内表现为串行语义” +后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象 + +Java提供了volatile和synchronized保证线程之间操作的有序性 +volatile本身就包含了禁止指令重排序的语义 +synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。 + +### 6.2.3 先行发生原则 +如果JMM中所有的有序性都只靠volatile和synchronized,那么有一些操作将会变得很繁琐,但我们在编写Java并发代码时并没有感到这一点,这是因为Java语言中有一个`先行发生(Happen-Before)`原则 +这个原则非常重要,它是判断数据是否存在竞争,线程是否安全的主要依赖。 + +先行发生原则是指JMM中定义的两项操作之间的依序关系 +如果说操作A先行发生于操作B,就是在说发生B前,A产生的影响能被B观察到,“影响”包含了修改内存中共享变量的值、发送了消息、调用了方法等。意味着什么呢?如下例: +``` +//线程A中执行 +i = 1; + +//线程B中执行 +j = i; + +//线程C中执行 +i = 2; +``` +下面是JMM下一些”天然的“先行发生关系,无须任何同步器协助就已经存在,可以在编码中直接使用 +如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序 +- 程序次序规则(Pragram Order Rule) +在一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环结构。 +- 监视器锁法则(Monitor Lock Rule ) +一个`unlock`先行发生于后面对同一个锁的`lock`。这里必须强调的是同一个锁,而”后面“是指时间上的先后。 +- volatile变量规则(Volatile Variable Rule) +对一个volatile变量的写操作先行发生于后面对这个变量的读取操作,这里的”后面“同样指时间上的先后顺序。 +- 线程启动规则(Thread Start Rule) +Thread对象的start()方法先行发生于此线程的每一个动作。 +- 线程终止规则(Thread Termination Rule) +线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。 +- 线程中断规则(Thread Interruption Rule) +对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生 +- 对象终结规则(Finalizer Rule) +一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始 +- 传递性(Transitivity) +如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论 + +一个操作”时间上的先发生“不代表这个操作会是”先行发生“,那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢?也是不成立的,一个典型的例子就是指令重排序 +所以时间上的先后顺序与先行发生原则之间基本没有什么关系,所以衡量并发安全问题一切必须以先行发生原则为准。 + +### 6.2.4 作用 +> **1.阻止屏障两侧的指令重排序 +> 2.强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效** +* 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据 +* 对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见 + +**Java的内存屏障实际上也是上述两种的组合,完成一系列的屏障和数据同步功能** +> **LoadLoad屏障:**对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。 +> **StoreStore屏障:**对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。 +> **LoadStore屏障:**对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。 +> **StoreLoad屏障:**对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能 + +`volatile`的内存屏障策略非常严格保守 +> **在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障 +> 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障** + +由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性 diff --git "a/Java/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md" "b/Java/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md" new file mode 100644 index 0000000000..f769405b49 --- /dev/null +++ "b/Java/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md" @@ -0,0 +1,224 @@ + +#**jdk动态代理实例** +jdk动态代理模式里面有个拦截器的概念,在jdk中,只要实现了**InvocationHandler**接口的类就是一个**拦截器类** +还使用了些反射的相关概念。 +拦截器的概念不了解没关系,假如写了个请求到action,经过拦截器,然后才会到action。然后继续有之后的操作。 +拦截器就像一个过滤网,一层层的过滤,只要满足一定条件,才能继续向后执行。 +**拦截器的作用**:控制目标对象的目标方法的执行。 + +**拦截器的具体操作步骤: +1.引入类:目标类和一些扩展方法相关的类。 +2.赋值:调用构造函数给相关对象赋值 +3.合并逻辑处理:在invoke方法中把所有的逻辑结合在一起。最终决定目标方法是否被调用。** + +下面看具体的代码实例: +```java +目标接口类: +package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy; + +/** + * 目标接口: + * 包含目标方法的声明 + */ +public interface TargetInterface { + /** + * 目标方法 + */ + void business(); +} + +目标类: +package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy; + /** + * 被代理的类 + * 目标对象类 + * 实现目标接口. + * 继而实现目标方法。 + */ +public class TargetObject implements TargetInterface { + + /** + * 目标方法(即目标操作) + */ + @Override + public void business() { + System.out.println("business"); + } + +} + +拦截器: +package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * 动态代理-拦截器 + */ +public class MyInterceptor implements InvocationHandler { + private Object target;//目标类 + + public MyInterceptor(Object target) { + this.target = target; + } + + /** + * args 目标方法的参数 + * method 目标方法 + */ + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("aaaaa");//切面方法a(); + //。。。 + method.invoke(this.target, args);//调用目标类的目标方法 + //。。。 + System.out.println("bbbbb");//切面方法f(); + return null; + } +} + +具体通过调用代理对象,来调用目标对象的目标方法的具体测试: +[java] view plain copy +package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy; + +import java.lang.reflect.Proxy; + +public class MainTest { + public static void main(String[] args) { + //目标对象 + TargetObject target = new TargetObject(); + //拦截器 + MyInterceptor myInterceptor = new MyInterceptor(target); + + /* + * Proxy.newProxyInstance参数: + * 1、目标类的类加载器 + * 2、目标类的所有的接口 + * 3、拦截器 + */ + //代理对象,调用系统方法自动生成 + TargetInterface proxyObj = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInterceptor); + proxyObj.business(); + } +} +``` +看完代码实例,要**对这个动态代理进一步理解,要考虑到以下的问题。 +1、代理对象是由谁产生的?** +JVM,不像上次的静态代理,我们自己得new个代理对象出来。 +**2、代理对象实现了什么接口?** +实现的接口是目标对象实现的接口。 +同静态代理中代理对象实现的接口。那个继承关系图还是相同的。 +代理对象和目标对象都实现一个共同的接口。就是这个接口。 +所以Proxy.newProxyInstance()方法返回的类型就是这个接口类型。 +**3、代理对象的方法体是什么?** +代理对象的方法体中的内容就是拦截器中invoke方法中的内容。所有代理对象的处理逻辑,控制是否执行目标对象的目标方法。都是在这个方法里面处理的。 +**4、拦截器中的invoke方法中的method参数是在什么时候赋值的?** +在客户端,代理对象调用目标方法的时候,此实例中为:proxyObj.business();实际上进入的是拦截器中的invoke方法,这个时候 +,拦截器中的invoke方法中的method参数会被赋值。 + +**最后,为啥这个方式叫做jdk动态代理呢?** +因为这个动态代理对象是用jdk的相关代码生成的,所以这个叫**jdk动态代理**。 +后面的cglib动态代理,就是因为要用到cglib的jar包,所以才叫**cglib动态代理**。 + +了解了一个,那么另一个也就差不多啦。就继续往下看吧。 + +为什么要使用这个**cglib来实现这个动态代理**呢?因为spring框架要用。 + +具体的代码实现如下: +```java +目标对象类: +package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy; + +/** + * 被代理的类 + * 目标对象类 + */ +public class TargetObject { + + /** + * 目标方法(即目标操作) + */ + public void business() { + System.out.println("business"); + } + +} + +拦截器类: +package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import java.lang.reflect.Method; + +/** + * 动态代理-拦截器 + */ +public class MyInterceptor implements MethodInterceptor { + private Object target;//目标类 + + public MyInterceptor(Object target) { + this.target = target; + } + + /** + * 返回代理对象 + * 具体实现,暂时先不追究。 + */ + public Object createProxy() { + Enhancer enhancer = new Enhancer(); + enhancer.setCallback(this);//回调函数 拦截器 + //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。 + enhancer.setSuperclass(this.target.getClass()); + return enhancer.create(); + } + + /** + * args 目标方法的参数 + * method 目标方法 + */ + @Override + public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { + System.out.println("aaaaa");//切面方法a(); + //。。。 + method.invoke(this.target, objects);//调用目标类的目标方法 + //。。。 + System.out.println("bbbbb");//切面方法f(); + return null; + } +} + +测试类: +[java] view plain copy +package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy; + +public class MainTest { + public static void main(String[] args) { + //目标对象 + TargetObject target = new TargetObject(); + //拦截器 + MyInterceptor myInterceptor = new MyInterceptor(target); + //代理对象,调用cglib系统方法自动生成 + //注意:代理类是目标类的子类。 + TargetObject proxyObj = (TargetObject) myInterceptor.createProxy(); + proxyObj.business(); + } +} + +区别: +首先从文件数上来说,cglib比jdk实现的少了个接口类。因为cglib返回的代理对象是目标对象的子类。而jdk产生的代理对象和目标对象都实现了一个公共接口。 + + +``` +动态代理分为两种: + * jdk的动态代理 + * 代理对象和目标对象实现了共同的接口 + * 拦截器必须实现InvocationHanlder接口 + + * cglib的动态代理 + * 代理对象是目标对象的子类 + * 拦截器必须实现MethodInterceptor接口 + * hibernate中session.load采用的是cglib实现的 diff --git "a/Java/Java\345\244\232\347\272\277\347\250\213\344\270\255join\346\226\271\346\263\225\347\232\204\347\220\206\350\247\243.md" "b/Java/Java\345\244\232\347\272\277\347\250\213\344\270\255join\346\226\271\346\263\225\347\232\204\347\220\206\350\247\243.md" new file mode 100644 index 0000000000..a7db05b47a --- /dev/null +++ "b/Java/Java\345\244\232\347\272\277\347\250\213\344\270\255join\346\226\271\346\263\225\347\232\204\347\220\206\350\247\243.md" @@ -0,0 +1,87 @@ +许多同学刚开始学Java 多线程时可能不会关主Join 这个动作,因为不知道它是用来做什么的,而当需要用到类似的场景时却有可能会说Java 没有提供这种功能。 + +当我们将一个大任务划分为多个小任务,多个小任务由多个线程去完成时,显然它们完成的先后顺序不可能完全一致。在程序中希望各个线程执行完成后,将它们的计算结果最终合并在一起,换句话说,要等待多个线程将子任务执行完成后,才能进行合并结果的操作。 +这时就可以选择使用Join 了,Join 可以帮助我们轻松地搞定这个问题,否则就需要用个循环去不断判定每个线程的状态。 + +在实际生活中,就像把任务分解给多个人去完成其中的各个板块,但老板需要等待这些人全部都完成后才认为这个阶段的任务结束了,也许每个人的板块内部和别人还有相互的接口依赖,如果对方接口没有写好,自己的这部分也不算完全完成,就会发生类似于合并的动作(到底要将任务细化到什么粒度,完全看实际场景和自己对问题的理解)。下面用段简单的代码米说明Join 的使用。 + +thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 +![](https://upload-images.jianshu.io/upload_images/4685968-f6a1f06c3ef70293.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +```java +package com.sss.test; + +import java.util.Random; + +/** + * @author Shusheng Shi + */ +public class ThreadJoinTest { + static class Computer extends Thread { + private int start; + private int end; + private int result; + private int[] array; + + public Computer(int[] array, int start, int end) { + this.array=array; + this.start=start; + this.end=end; + } + + @Override + public void run() { + for (int i=start; i < end; i++) { + result+=array[i]; + } + + if (result < 0) { + result&=Integer.MAX_VALUE; + } + + } + + public int getResult() { + return result; + + } + } + + private final static int COUNTER=10000000; + + public static void main(String[] args) throws InterruptedException { + int[] array=new int[COUNTER]; + Random random=new Random(); + for (int i=0; i < COUNTER; i++) { + array[i]=Math.abs( random.nextInt() ); + } + long start=System.currentTimeMillis(); + Computer c1=new Computer( array, 0, COUNTER ); + Computer c2=new Computer( array, COUNTER / 2, COUNTER ); + c1.start(); + c2.start(); + c1.join(); + c2.join(); + System.out.println( System.currentTimeMillis() - start ); + System.out.println( (c1.getResult() + c2.getResult()) & Integer.MAX_VALUE ); + } +} +``` +这个例子或许不太好,只是1000 万个随机数叠加,为了防此CPU计算过快,在计算中增加一些判定操作,最后再将计算完的两个值输出,也输出运算时间。如果在有多个CPU的机器上做测试,就会发现数据量大时,多个线程计算具有优势,但是这个优势非常小, +而且在数据量较小的情况下,单线程会更快些。为何单线程可能会更快呢? +最主要的原因是线程在分配时就有开销(每个线程的分配过程本身就高要执行很多条底层代码,这些代码的执行相当于很多条CPU 叠加运算的指令),Join 操作过程还有其他的各种开销。 +如果尝试将每个线程叠加后做一些其他的操作,例如IO读写、字符串处理等操作,多线程的优势就出来了,因为这样总体计算下来后,线程的创建时间是可以被忽略 + +所以我们在考量系统的综合性能时不能就一一个点或某种测试就轻易得出一一个最终结论,定要考虑更多的变动因素。 + +那么使用多线程带来更多的是上下文切换的开销,多线程操作的共享对象还会有锁瓶 +否则就是非线程安全的。 +颈, +综合考量各种开销因素、时间、空间, +最后利用大量的场景测试来证明推理是有 +指导性的,如果只是一味地为了用多线程而使用多线程,则往往很多事情可能会适得 +其反 +Join5 ?是语法层面的线程合并,其实它更像是当前线程处于BLOCKEN 状态时去等待 +I :他线程结束的事件,而且是逐个去Join。换句话说,Join 的顺序并不一一定是线程真正结 +束的顺序,要保证线程结束的顺J 字性,它还无法实现,即使在本例中它也不是唯一的实现 +方式,本章后面会提到许多基于并发编程工具的方式来实现会更加理想,管理也会更加体 +系化,能适应更多的业务场景需求。 diff --git "a/Java/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/Java/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md" new file mode 100644 index 0000000000..9897731976 --- /dev/null +++ "b/Java/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -0,0 +1,789 @@ +# What + +Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。 + +那么为什么需要序列化呢? + +第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。 + +第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。 +#How +本部分以序列化到文件为例讲解Java序列化的基本用法。 +```java +package test; + +import java.io.*; + +/** + * @author v_shishusheng + * @date 2018/2/7 + */ +public class SerializableTest { + public static void main(String[] args) throws Exception { + FileOutputStream fos = new FileOutputStream("temp.out"); + ObjectOutputStream oos = new ObjectOutputStream(fos); + TestObject testObject = new TestObject(); + oos.writeObject(testObject); + oos.flush(); + oos.close(); + + FileInputStream fis = new FileInputStream("temp.out"); + ObjectInputStream ois = new ObjectInputStream(fis); + TestObject deTest = (TestObject) ois.readObject(); + System.out.println(deTest.testValue); + System.out.println(deTest.parentValue); + System.out.println(deTest.innerObject.innerValue); + } +} + +class Parent implements Serializable { + + private static final long serialVersionUID = -4963266899668807475L; + + public int parentValue = 100; +} + +class InnerObject implements Serializable { + + private static final long serialVersionUID = 5704957411985783570L; + + public int innerValue = 200; +} + +class TestObject extends Parent implements Serializable { + + private static final long serialVersionUID = -3186721026267206914L; + + public int testValue = 300; + + public InnerObject innerObject = new InnerObject(); +} +``` +程序执行完用sublime打开temp.out文件,可以看到 +```java +aced 0005 7372 0017 636f 6d2e 7373 732e +7465 7374 2e54 6573 744f 626a 6563 74d3 +c67e 1c4f 132a fe02 0002 4900 0974 6573 +7456 616c 7565 4c00 0b69 6e6e 6572 4f62 +6a65 6374 7400 1a4c 636f 6d2f 7373 732f +7465 7374 2f49 6e6e 6572 4f62 6a65 6374 +3b78 7200 1363 6f6d 2e73 7373 2e74 6573 +742e 5061 7265 6e74 bb1e ef0d 1fc9 50cd +0200 0149 000b 7061 7265 6e74 5661 6c75 +6578 7000 0000 6400 0001 2c73 7200 1863 +6f6d 2e73 7373 2e74 6573 742e 496e 6e65 +724f 626a 6563 744f 2c14 8a40 24fb 1202 +0001 4900 0a69 6e6e 6572 5661 6c75 6578 +7000 0000 c8 +``` +# Why +调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?temp.out文件中的二进制分别代表什么意思? +## 1. ObjectStreamClass类 + +方文档对这个类的介绍如下 + +> Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method. + +可以看到是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。 + +##2.序列化:writeObject() +调用wroteObject()进行序列化之前会先调用ObjectOutputStream的构造函数生成一个ObjectOutputStream对象,构造函数如下: +```java +public ObjectOutputStream(OutputStream out) throws IOException { + verifySubclass(); + // bout表示底层的字节数据容器 + bout = new BlockDataOutputStream(out); + handles = new HandleTable(10, (float) 3.00); + subs = new ReplaceTable(10, (float) 3.00); + enableOverride = false; + writeStreamHeader(); // 写入文件头 + bout.setBlockDataMode(true); // flush数据 + if (extendedDebugInfo) { + debugInfoStack = new DebugTraceInfoStack(); + } else { + debugInfoStack = null; + } +} +``` +构造函数中首先会把bout绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法,该方法实现如下: +```java +protected void writeStreamHeader() throws IOException { + bout.writeShort(STREAM_MAGIC); + bout.writeShort(STREAM_VERSION); +} +``` +在writeStreamHeader()方法中首先会往底层字节容器中写入表示序列化的Magic Number以及版本号,定义为 +```java +/** + * Magic number that is written to the stream header. + */ +final static short STREAM_MAGIC = (short)0xaced; + +/** + * Version number that is written to the stream header. + */ +final static short STREAM_VERSION = 5; +``` +接下来会调用writeObject()方法进行序列化,实现如下: +```java +public final void writeObject(Object obj) throws IOException { + if (enableOverride) { + writeObjectOverride(obj); + return; } + try { + // 调用writeObject0()方法序列化 + writeObject0(obj, false); + } catch (IOException ex) { + if (depth == 0) { + writeFatalException(ex); + } + throw ex; + } +} +``` +正常情况下会调用writeObject0()进行序列化操作,该方法实现如下: +```java + private void writeObject0(Object obj, boolean unshared) + throws IOException + { + boolean oldMode = bout.setBlockDataMode(false); + depth++; + try { + // handle previously written and non-replaceable objects + int h; + if ((obj = subs.lookup(obj)) == null) { + writeNull(); + return; + } else if (!unshared && (h = handles.lookup(obj)) != -1) { + writeHandle(h); + return; + } else if (obj instanceof Class) { + writeClass((Class) obj, unshared); + return; + } else if (obj instanceof ObjectStreamClass) { + writeClassDesc((ObjectStreamClass) obj, unshared); + return; + } + + // check for replacement object + Object orig = obj; + // 获取要序列化的对象的Class对象 + Class cl = obj.getClass(); + ObjectStreamClass desc; + for (;;) { + // REMIND: skip this check for strings/arrays? + Class repCl; + // 创建描述cl的ObjectStreamClass对象 + desc = ObjectStreamClass.lookup(cl, true); + if (!desc.hasWriteReplaceMethod() || + (obj = desc.invokeWriteReplace(obj)) == null || + (repCl = obj.getClass()) == cl) + { + break; + } + cl = repCl; + } + if (enableReplace) { + Object rep = replaceObject(obj); + if (rep != obj && rep != null) { + cl = rep.getClass(); + desc = ObjectStreamClass.lookup(cl, true); + } + obj = rep; + } + + // if object replaced, run through original checks a second time + if (obj != orig) { + subs.assign(orig, obj); + if (obj == null) { + writeNull(); + return; + } else if (!unshared && (h = handles.lookup(obj)) != -1) { + writeHandle(h); + return; + } else if (obj instanceof Class) { + writeClass((Class) obj, unshared); + return; + } else if (obj instanceof ObjectStreamClass) { + writeClassDesc((ObjectStreamClass) obj, unshared); + return; + } + } + + // 根据实际的类型进行不同的写入操作 + // remaining cases + if (obj instanceof String) { + writeString((String) obj, unshared); + } else if (cl.isArray()) { + writeArray(obj, desc, unshared); + } else if (obj instanceof Enum) { + writeEnum((Enum) obj, desc, unshared); + } else if (obj instanceof Serializable) { + // 被序列化对象实现了Serializable接口 + writeOrdinaryObject(obj, desc, unshared); + } else { + if (extendedDebugInfo) { + throw new NotSerializableException( + cl.getName() + "\n" + debugInfoStack.toString()); + } else { + throw new NotSerializableException(cl.getName()); + } + } + } finally { + depth--; + bout.setBlockDataMode(oldMode); + } + } +``` +从代码里面可以看到,程序会 +- 生成一个描述被序列化对象类的类元信息的ObjectStreamClass对象 +- 根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到, + - 对于String类型、数组类型和Enum可以直接进行序列化 + - 如果被序列化对象实现了Serializable对象,则会调用writeOrdinaryObject()方法进行序列化 +这里可以解释一个问题:Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。 + +答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化。 + +因此,序列化过程接下来会执行到writeOrdinaryObject()这个方法中,该方法实现如下: +```java + private void writeOrdinaryObject(Object obj, + ObjectStreamClass desc, + boolean unshared) + throws IOException + { + if (extendedDebugInfo) { + debugInfoStack.push( + (depth == 1 ? "root " : "") + "object (class \"" + + obj.getClass().getName() + "\", " + obj.toString() + ")"); + } + try { + desc.checkSerialize(); + + // 写入Object标志位 + bout.writeByte(TC_OBJECT); + // 写入类元数据 + writeClassDesc(desc, false); + handles.assign(unshared ? null : obj); + if (desc.isExternalizable() && !desc.isProxy()) { + writeExternalData((Externalizable) obj); // 写入被序列化的对象的实例数据 + } else { + writeSerialData(obj, desc); + } + } finally { + if (extendedDebugInfo) { + debugInfoStack.pop(); + } + } + } +``` +在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object +```java +/** + * new Object. + */ +final static byte TC_OBJECT = (byte)0x73; +``` +接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下: +```java +private void writeClassDesc(ObjectStreamClass desc, boolean unshared) + throws IOException +{ + int handle; + if (desc == null) { + // 如果desc为null + writeNull(); + } else if (!unshared && (handle = handles.lookup(desc)) != -1) { + writeHandle(handle); + } else if (desc.isProxy()) { + writeProxyDesc(desc, unshared); + } else { + writeNonProxyDesc(desc, unshared); + } +} +``` +在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法 +```java +private void writeNull() throws IOException { + // TC_NULL = (byte)0x70; + // 表示对一个Object引用的描述的结束 + bout.writeByte(TC_NULL); +} +``` +如果不为null,则一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下: +```java +private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) + throws IOException +{ + // TC_CLASSDESC = (byte)0x72; + // 表示一个新的Class描述符 + bout.writeByte(TC_CLASSDESC); + handles.assign(unshared ? null : desc); + + if (protocol == PROTOCOL_VERSION_1) { + // do not invoke class descriptor write hook with old protocol + desc.writeNonProxy(this); + } else { + writeClassDescriptor(desc); + } + + Class cl = desc.forClass(); + bout.setBlockDataMode(true); + if (cl != null && isCustomSubclass()) { + ReflectUtil.checkPackageAccess(cl); + } + annotateClass(cl); + bout.setBlockDataMode(false); + bout.writeByte(TC_ENDBLOCKDATA); + + writeClassDesc(desc.getSuperDesc(), false); +} +``` +在这个方法中首先会写入一个字节的TC_CLASSDESC,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()方法写入实际的类元信息,writeNonProxy()实现如下: +```java +void writeNonProxy(ObjectOutputStream out) throws IOException { + out.writeUTF(name); // 写入类的名字 + out.writeLong(getSerialVersionUID()); // 写入类的序列号 + + byte flags = 0; + // 获取类的标识 + if (externalizable) { + flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; + int protocol = out.getProtocolVersion(); + if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { + flags |= ObjectStreamConstants.SC_BLOCK_DATA; + } + } else if (serializable) { + flags |= ObjectStreamConstants.SC_SERIALIZABLE; + } + if (hasWriteObjectData) { + flags |= ObjectStreamConstants.SC_WRITE_METHOD; + } + if (isEnum) { + flags |= ObjectStreamConstants.SC_ENUM; + } + out.writeByte(flags); // 写入类的flag + + out.writeShort(fields.length); // 写入对象的字段的个数 + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + out.writeByte(f.getTypeCode()); + out.writeUTF(f.getName()); + if (!f.isPrimitive()) { + // 如果不是原始类型,即是对象或者Interface + // 则会写入表示对象或者类的类型字符串 + out.writeTypeString(f.getTypeString()); + } + } +} +``` +writeNonProxy()方法中会按照以下几个过程来写入数据: +- 1. 调用writeUTF()方法写入对象所属类的名字,对于本例中name = com.sss.test.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下: +```java +void writeUTF(String s, long utflen) throws IOException { + if (utflen > 0xFFFFL) { + throw new UTFDataFormatException(); + } + // 写入两个字节的s的长度 + writeShort((int) utflen); + if (utflen == (long) s.length()) { + writeBytes(s); + } else { + writeUTFBody(s); + } + } +``` +- 2. 接下来会调用writeLong()方法写入类的序列号UID,UID是通过getSerialVersionUID()方法来获取。 +- 3. 接着会判断被序列化的对象所属类的flag,并写入底层字节容器中(占用两个字节)。类的flag分为以下几类: + - final static byte SC_EXTERNALIZABLE = 0×04;表示该类为Externalizable类,即实现了Externalizable接口。 + - final static byte SC_SERIALIZABLE = 0×02;表示该类实现了Serializable接口。 + - final static byte SC_WRITE_METHOD = 0×01;表示该类实现了Serializable接口且自定义了writeObject()方法。 + - final static byte SC_ENUM = 0×10;表示该类是个Enum类型。 +对于本例中flag = 0×02表示只是Serializable类型。 +- 4. 依次写入被序列化对象的字段的元数据。 +<1> 首先会写入被序列化对象的字段的个数,占用两个字节。本例中为2,因为TestObject类中只有两个字段,一个是int类型的testValue,一个是InnerObject类型的innerValue。 +<2> 依次写入每个字段的元数据。每个单独的字段由ObjectStreamField类来表示。 + +1.写入字段的类型码,占一个字节。 类型码的映射关系如下 +![ 类型码的映射关系](http://upload-images.jianshu.io/upload_images/4685968-e7b2078da0bee13c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +2.调用writeUTF()方法写入每个字段的名字。注意,writeUTF()方法会先写入名字占用的字节数。 +3.如果被写入的字段不是基本类型,则会接着调用writeTypeString()方法写入代表对象或者类的类型字符串,该方法需要一个参数,表示对应的类或者接口的字符串,最终调用的还是writeString()方法,实现如下 +```java +private void writeString(String str, boolean unshared) throws IOException { + handles.assign(unshared ? null : str); + long utflen = bout.getUTFLength(str); + if (utflen <= 0xFFFF) { + // final static byte TC_STRING = (byte)0x74; + // 表示接下来的字节表示一个字符串 + bout.writeByte(TC_STRING); + bout.writeUTF(str, utflen); + } else { + bout.writeByte(TC_LONGSTRING); + bout.writeLongUTF(str, utflen); + } +} +``` +在这个方法中会先写入一个标志位TC_STRING表示接下来的数据是一个字符串,接着会调用writeUTF()写入字符串。 + +执行完上面的过程之后,程序流程重新回到writeNonProxyDesc()方法中 +```java +private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) + throws IOException +{ + // 其他省略代码 + + // TC_ENDBLOCKDATA = (byte)0x78; + // 表示对一个object的描述块的结束 + bout.writeByte(TC_ENDBLOCKDATA); + + writeClassDesc(desc.getSuperDesc(), false); // 尾递归调用,写入父类的类元数据 +} +``` +接下来会写入一个字节的标志位TC_ENDBLOCKDATA表示对一个object的描述块的结束。 + +然后会调用writeClassDesc()方法,传入父类的ObjectStreamClass对象,写入父类的类元数据。 + +需要注意的是writeClassDesc()这个方法是个递归调用,调用结束返回的条件是没有了父类,即传入的ObjectStreamClass对象为null,这个时候会写入一个字节的标识位TC_NULL. + +在递归调用完成写入类的类元数据之后,程序执行流程回到wriyeOrdinaryObject()方法中, +```java +private void writeOrdinaryObject(Object obj, + ObjectStreamClass desc, + boolean unshared) throws IOException +{ + // 其他省略代码 + try { + desc.checkSerialize(); + // 其他省略代码 + if (desc.isExternalizable() && !desc.isProxy()) { + writeExternalData((Externalizable) obj); + } else { + writeSerialData(obj, desc); // 写入被序列化的对象的实例数据 + } + } finally { + if (extendedDebugInfo) { + debugInfoStack.pop(); + } + } +} +``` +从上面的分析中我们可以知道,当写入类的元数据的时候,是先写子类的类元数据,然后递归调用的写入父类的类元数据。 + +接下来会调用writeSerialData()方法写入被序列化的对象的字段的数据,方法实现如下: +```java +private void writeSerialData(Object obj, ObjectStreamClass desc) + throws IOException +{ + // 获取表示被序列化对象的数据的布局的ClassDataSlot数组,父类在前 + ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); + for (int i = 0; i < slots.length; i++) { + ObjectStreamClass slotDesc = slots[i].desc; + if (slotDesc.hasWriteObjectMethod()) { + // 如果被序列化对象自己实现了writeObject()方法,则执行if块里的代码 + + // 一些省略代码 + } else { + // 调用默认的方法写入实例数据 + defaultWriteFields(obj, slotDesc); + } + } +} +``` +在这个方法中首先会调用getClassDataSlot()方法获取被序列化对象的数据的布局,关于这个方法官方文档中说明如下: +```java + +/** + * Returns array of ClassDataSlot instances representing the data layout + * (including superclass data) for serialized objects described by this + * class descriptor. ClassDataSlots are ordered by inheritance with those + * containing "higher" superclasses appearing first. The final + * ClassDataSlot contains a reference to this descriptor. + */ + ClassDataSlot[] getClassDataLayout() throws InvalidClassException; +``` +需要注意的是这个方法会把从父类继承的数据一并返回,并且表示从父类继承的数据的ClassDataSlot对象在数组的最前面。 + +对于没有自定义writeObject()方法的对象来说,接下来会调用defaultWriteFields()方法写入数据,该方法实现如下: +```java +private void defaultWriteFields(Object obj, ObjectStreamClass desc) + throws IOException +{ + // 其他一些省略代码 + + int primDataSize = desc.getPrimDataSize(); + if (primVals == null || primVals.length < primDataSize) { + primVals = new byte[primDataSize]; + } + // 获取对应类中的基本数据类型的数据并保存在primVals字节数组中 + desc.getPrimFieldValues(obj, primVals); + // 把基本数据类型的数据写入底层字节容器中 + bout.write(primVals, 0, primDataSize, false); + + // 获取对应类的所有的字段对象 + ObjectStreamField[] fields = desc.getFields(false); + Object[] objVals = new Object[desc.getNumObjFields()]; + int numPrimFields = fields.length - objVals.length; + // 把对应类的Object类型(非原始类型)的对象保存到objVals数组中 + desc.getObjFieldValues(obj, objVals); + for (int i = 0; i < objVals.length; i++) { + // 一些省略的代码 + + try { + // 对所有Object类型的字段递归调用writeObject0()方法写入对应的数据 + writeObject0(objVals[i], + fields[numPrimFields + i].isUnshared()); + } finally { + if (extendedDebugInfo) { + debugInfoStack.pop(); + } + } + } +} +``` +可以看到,在这个方法中会做下面几件事情: + +<1> 获取对应类的基本类型的字段的数据,并写入到底层的字节容器中。 +<2> 获取对应类的Object类型(非基本类型)的字段成员,递归调用writeObject0()方法写入相应的数据。 + +从上面对写入数据的分析可以知道,写入数据是是按照先父类后子类的顺序来写的。 + +至此,Java序列化过程分析完毕,总结一下,在本例中序列化过程如下: + +![](http://upload-images.jianshu.io/upload_images/4685968-7826ad825f2ef569.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +现在可以来分析下第二步中写入的temp.out文件的内容了。 +```java +aced Stream Magic +0005 序列化版本号 +73 标志位:TC_OBJECT,表示接下来是个新的Object +72 标志位:TC_CLASSDESC,表示接下来是对Class的描述 +0020 类名的长度为32 +636f 6d2e 6265 6175 7479 626f 7373 2e73 com.beautyboss.s +6c6f 6765 6e2e 5465 7374 4f62 6a65 6374 logen.TestObject +d3c6 7e1c 4f13 2afe 序列号 +02 flag,可序列化 +00 02 TestObject的字段的个数,为2 +49 TypeCode,I,表示int类型 +0009 字段名长度,占9个字节 +7465 7374 5661 6c75 65 字段名:testValue +4c TypeCode:L,表示是个Class或者Interface +000b 字段名长度,占11个字节 +696e 6e65 724f 626a 6563 74 字段名:innerObject +74 标志位:TC_STRING,表示后面的数据是个字符串 +0023 类名长度,占35个字节 +4c63 6f6d 2f62 6561 7574 7962 6f73 732f Lcom/beautyboss/ +736c 6f67 656e 2f49 6e6e 6572 4f62 6a65 slogen/InnerObje +6374 3b ct; +78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束 +``` +接下来开始写入数据,从父类Parent开始 +```java +0000 0064 parentValue的值:100 +0000 012c testValue的值:300 +``` +接下来是写入InnerObject的类元信息 +```java +73 标志位,TC_OBJECT:表示接下来是个新的Object +72 标志位,TC_CLASSDESC:表示接下来是对Class的描述 +0021 类名的长度,为33 +636f 6d2e 6265 6175 7479 626f 7373 com.beautyboss +2e73 6c6f 6765 6e2e 496e 6e65 724f .slogen.InnerO +626a 6563 74 bject +4f2c 148a 4024 fb12 序列号 +02 flag,表示可序列化 +0001 字段个数,1个 +49 TypeCode,I,表示int类型 +00 0a 字段名长度,10个字节 +69 6e6e 6572 5661 6c75 65 innerValue +78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束 +70 标志位:TC_NULL,Null object reference. +0000 00c8 innervalue的值:200 +``` +##3. 反序列化:readObject() +反序列化过程就是按照前面介绍的序列化算法来解析二进制数据。 + +有一个需要注意的问题就是,如果子类实现了Serializable接口,但是父类没有实现Serializable接口,这个时候进行反序列化会发生什么情况? + +答:如果父类有默认构造函数的话,即使没有实现Serializable接口也不会有问题,反序列化的时候会调用默认构造函数进行初始化,否则的话反序列化的时候会抛出.InvalidClassException:异常,异常原因为no valid constructor。 +# Other +## 1. static和transient字段不能被序列化。 +序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);这句代码来获取需要被序列化的字段,getSerialFields()方法实际上是调用getDefaultSerialFields()方法的,getDefaultSerialFields()实现如下: +```java +private static ObjectStreamField[] getDefaultSerialFields(Class cl) { + Field[] clFields = cl.getDeclaredFields(); + ArrayList list = new ArrayList<>(); + int mask = Modifier.STATIC | Modifier.TRANSIENT; + + for (int i = 0; i < clFields.length; i++) { + if ((clFields[i].getModifiers() & mask) == 0) { + // 如果字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去 + list.add(new ObjectStreamField(clFields[i], false, true)); + } + } + int size = list.size(); + return (size == 0) ? NO_FIELDS : + list.toArray(new ObjectStreamField[size]); +} +``` +从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉。 + +在进行反序列化的时候会给默认值。 +##2. 如何实现自定义序列化和反序列化? +只需要被序列化的对象所属的类定义了void writeObject(ObjectOutputStream oos)和void readObject(ObjectInputStream ois)方法即可,Java序列化和反序列化的时候会调用这两个方法,那么这个功能是怎么实现的呢? + +1. 在ObjectClassStream类的构造函数中有下面几行代码: +```java +cons = getSerializableConstructor(cl); +writeObjectMethod = getPrivateMethod(cl, "writeObject", + new Class[] { ObjectOutputStream.class }, + Void.TYPE); +readObjectMethod = getPrivateMethod(cl, "readObject", + new Class[] { ObjectInputStream.class }, + Void.TYPE); +readObjectNoDataMethod = getPrivateMethod( + cl, "readObjectNoData", null, Void.TYPE); +hasWriteObjectData = (writeObjectMethod != null); +``` + +```java +cons = getSerializableConstructor(cl); +writeObjectMethod = getPrivateMethod(cl, "writeObject", + new Class[] { ObjectOutputStream.class }, + Void.TYPE); +readObjectMethod = getPrivateMethod(cl, "readObject", + new Class[] { ObjectInputStream.class }, + Void.TYPE); +readObjectNoDataMethod = getPrivateMethod( + cl, "readObjectNoData", null, Void.TYPE); +hasWriteObjectData = (writeObjectMethod != null); +``` + +```java +cons = getSerializableConstructor(cl); +writeObjectMethod = getPrivateMethod(cl, "writeObject", + new Class[] { ObjectOutputStream.class }, + Void.TYPE); +readObjectMethod = getPrivateMethod(cl, "readObject", + new Class[] { ObjectInputStream.class }, + Void.TYPE); +readObjectNoDataMethod = getPrivateMethod( + cl, "readObjectNoData", null, Void.TYPE); +hasWriteObjectData = (writeObjectMethod != null); +``` +getPrivateMethod()方法实现如下: +```java +private static Method getPrivateMethod(Class cl, String name, + Class[] argTypes, + Class returnType) +{ + try { + Method meth = cl.getDeclaredMethod(name, argTypes); + meth.setAccessible(true); + int mods = meth.getModifiers(); + return ((meth.getReturnType() == returnType) && + ((mods & Modifier.STATIC) == 0) && + ((mods & Modifier.PRIVATE) != 0)) ? meth : null; + } catch (NoSuchMethodException ex) { + return null; + } +} +``` +可以看到在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。 + +2. 在调用writeSerialData()方法写入序列化数据的时候有 +```java +private void writeSerialData(Object obj, ObjectStreamClass desc) + throws IOException +{ + ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); + for (int i = 0; i < slots.length; i++) { + ObjectStreamClass slotDesc = slots[i].desc; + if (slotDesc.hasWriteObjectMethod()) { + // 其他一些省略代码 + try { + curContext = new SerialCallbackContext(obj, slotDesc); + bout.setBlockDataMode(true); + // 在这里调用用户自定义的方法 + slotDesc.invokeWriteObject(obj, this); + bout.setBlockDataMode(false); + bout.writeByte(TC_ENDBLOCKDATA); + } finally { + curContext.setUsed(); + curContext = oldContext; + if (extendedDebugInfo) { + debugInfoStack.pop(); + } + } + + curPut = oldPut; + } else { + defaultWriteFields(obj, slotDesc); + } + } +} +``` +首先会调用hasWriteObjectMethod()方法判断有没有自定义的writeObject(),代码如下 +```java +boolean hasWriteObjectMethod() { + return (writeObjectMethod != null); +} +``` +hasWriteObjectMethod()这个方法仅仅是判断writeObjectMethod是不是等于null,而上面说了,如果用户自定义了void writeObject(ObjectOutputStream oos)这么个方法,则writeObjectMethod不为null,在if()代码块中会调用slotDesc.invokeWriteObject(obj, this);方法,该方法中会调用用户自定义的writeObject()方法。 + + +Java编程思想相关知识点 + +当程序运行时,有关对象的信息就存储在了内存当中,但是当程序终止时,对象将不再继续存在。我们需要一种储存对象信息的方法,使我们的程序关闭之后他还继续存在,当我们再次打开程序时,可以轻易的还原当时的状态。这就是对象序列化的目的。 + +java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并且能够在以后将这个字节序列完全恢复为原来的对象,甚至可以通过网络传播。 这意味着序列化机制自动弥补了不同OS之间的差异. + +如此,java实现了“轻量级持久性”,为啥是轻量级,因为在java中我们还不能直接通过一个类似public这样的关键字直接使一个对象序列化,并让系统自动维护其他细节问题。因此我们只能在程序中显示地序列化与反序列化 + +对象序列化的概念加入到语言中是为了支持两种主要特性: +- java的远程方法调用(RMI),它使存活于其他计算机上的对象使用起来就像存活于本机上一样。当远程对象发送消息时,需要通过对象序列化来传输参数和返回值。 +- 对于Java Bean来说,对象序列化是必须的。使用一个Bean时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动的时候进行后期恢复,这种具体工作就是由对象序列化完成的。 + +使用——对象实现Serializable接口(仅仅是一个标记接口,没有任何方法)。 + +序列化一个对象: + 1. 创建某些OutputStream对象 + 2. 将其封装在一个ObjectOutputStream对象内 + 3. 只需调用writeObject()即可将对象序列化 + 注:也可以为一个String调用writeObject();也可以用与DataOutputStream相同的方法写入所有基本数据类型(它们具有同样的接口) + +反序列化 + 将一个InputStream封装在ObjectInputStream内,然后调用readObject()。最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们 + +对象序列化不仅保存了对象的“全景图”,而且能够追踪对象内所包含的所有引用,并保存这些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。这种情况有时被称为“对象网”。 + +例子: + ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"); + out.writeObject(w); + out.close(); + ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"); + String s = (String)in.readObject(); + + 在对一个Serializable对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。 +整个对象都是通过InputStream中取得数据恢复而来的。 + +寻找类:必须保证java虚拟机能够找到相关的.class文件。找不到就会得到一个ClassNotFOundExcption的异常。 + +序列化的控制——通过实现Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。 + 1. Externalizable接口继承了Serializable接口,增加了两个方法,writeExternal()和readExternal(),这两个方法会在序列化和反序列化还原的过程中被自动调用。 + 2. Externalizable对象,在还原的时候所有普通的默认构造器都会被调用(包括在字段定义时的初始化)(只有这样才能使Externalizable对象产生正确的行为),然后调用readExternal(). + 3. 如果我们从一个Externalizable对象继承,通常需要调用基类版本的writeExternal()和readExternal()来为基类组件提供恰当的存储和恢复功能。 + 4. 为了正常运行,我们不仅需要在writeExternal()方法中将来自对象的重要信息写入,还必须在readExternal()中恢复数据 + + 防止对象的敏感部分被序列化,两种方式: + 1. 将类实现Externalizable,在writeExternal()内部只对所需部分进行显示的序列化 + 2. 实现Serializable,用transient(瞬时)关键字(只能和Serializable一起使用)逐个字段的关闭序列化,他的意思:不用麻烦你保存或恢复数据——我自己会处理。 + +Externalizable的替代方法 + 1. 实现Serializable接口,并添加名为writeObject()和readObject()的方法,这样一旦对象被序列化或者被反序列化还原,就会自动的分别调用writeObject()和readObject()的方法(它们不是接口的一部分,接口的所有东西都是public的)。只要提供这两个方法,就会使用它们而不是默认的序列化机制。 + 2. 这两个方法必须具有准确的方法特征签名,但是这两个方法并不在这个类中的其他方法中调用,而是在ObjectOutputStream和ObjectInputStream对象的writeObject()和readObject()方法 + [图片上传失败...(image-c92672-1517928660769)] + 3. 技巧:在你的writeObject()和readObject()内部调用defaultWriteObject()和defaultReadObject来选择执行默认的writeObject()和readObject();如果打算使用默认机制写入对象的非transient部分,那么必须调用defaultwriteObject()和defaultReadObject(),且作为writeObject()和readObject()的第一个操作。 + +使用“持久性” + 1. 只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事;无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),我们都可以被写出。 + 2. Class是Serializable的,因此只需要直接对Class对象序列化,就可以很容易的保存static字段,任何情况下,这都是一种明智的做法。但是必须自己动手去实现序列化static的值。 + 使用serializeStaticState()和deserializeStaticState()两个static方法,它们是作为存储和读取过程的一部分被显示的调用的 + 3. 安全问题:序列化会将private数据保存下来,对于你关心的安全问题,应将其标记为transient。但是这之后,你还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些private变量。 diff --git "a/Java/Java\345\274\200\345\217\221\344\272\272\345\221\230\345\277\205\345\244\207linux\345\221\275\344\273\244.md" "b/Java/Java\345\274\200\345\217\221\344\272\272\345\221\230\345\277\205\345\244\207linux\345\221\275\344\273\244.md" new file mode 100644 index 0000000000..50d20a5002 --- /dev/null +++ "b/Java/Java\345\274\200\345\217\221\344\272\272\345\221\230\345\277\205\345\244\207linux\345\221\275\344\273\244.md" @@ -0,0 +1,758 @@ +管道符“|”将两个命令隔开,左边命令的输出作为右边命令的输入。连续使用管道意味着第一个命令的输出会作为 第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推 +#1 文件管理 +## which + 用于查找文件 +会在环境变量$PATH设置的目录里查找符合条件的文件。 +### 语法 +``` +which [文件...] +``` +### 参数: +``` +-n<文件名长度>  指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。 +-p<文件名长度>  与-n参数相同,但此处的<文件名长度>包括了文件的路径。 +-w  指定输出时栏位的宽度。 +-V  显示版本信息。 +``` +### 实例 +使用指令"which"查看指令"bash"的绝对路径,输入如下命令: +![](https://upload-images.jianshu.io/upload_images/4685968-f799e89b19bd48b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +上面的指令执行后,输出信息如下所示: +![](https://upload-images.jianshu.io/upload_images/4685968-1f7df7394763eac3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +##cp +复制文件或目录 +`cp [options] source dest` +`cp [options] source... directory` +-a:此选项通常在复制目录时使用,它保留链接、文件属性,并复制目录下的所有内容。其作用等于dpR参数组合。 +-d:复制时保留链接。这里所说的链接相当于Windows系统中的快捷方式。 +-f:覆盖已经存在的目标文件而不给出提示。 +-i:与-f选项相反,在覆盖目标文件之前给出提示,要求用户确认是否覆盖,回答"y"时目标文件将被覆盖。 +-p:除复制文件的内容外,还把修改时间和访问权限也复制到新文件中。 +-r:若给出的源文件是一个目录文件,此时将复制该目录下所有的子目录和文件 +-l:不复制文件,只是生成链接文件。 +使用指令"cp"将当前目录"test/"下的所有文件复制到新目录"newtest"下 +`$ cp –r test/ newtest ` +## chmod +文件调用权限三级 : 文件拥有者、群组、其他 +利用 chmod 控制文件如何被他人所调用。 +- r 表示可读取 +- w 表示可写入 +- x 表示可执行 +- X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。 + +chmod也可以用数字来表示权限如 : +chmod abc file +其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。 + +r=4,w=2,x=1 +若要rwx属性则4+2+1=7; +若要rw-属性则4+2=6; +若要r-x属性则4+1=5。 +将文件 file1.txt 设为所有人皆可读取 : +``` +chmod ugo+r file1.txt +将文件 file1.txt 设为所有人皆可读取 + +chmod a+r file1.txt +将文件 file1.txt 与 file2.txt 设为该文件拥有者,与其所属同一个群体者可写入,但其他以外的人则不可写入 : + +chmod ug+w,o-w file1.txt file2.txt + +将 ex1.py 设定为只有该文件拥有者可以执行 +chmod u+x ex1.py + +将目前目录下的所有文件与子目录皆设为任何人可读取 +chmod -R a+r * +此外chmod也可以用数字来表示权限如 : + +chmod 777 file +语法为: + +chmod abc file +其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。 + +r=4,w=2,x=1 +若要rwx属性则4+2+1=7; +若要rw-属性则4+2=6; +若要r-x属性则4+1=5。 +chmod a=rwx file +和 + +chmod 777 file +效果相同 + +chmod ug=rwx,o=x file +和 + +chmod 771 file +``` +## cat +功能:连接文件并打印到标准输出设备 +`cat [-AbeEnstTuv] [--help] [--version] fileName` +-n 或 --number:由 1 开始对所有输出的行数编号 +-b 或 --number-nonblank:和 -n 相似,只不过对于空白行不编号 + +实例: +把 textfile1 的文档内容加上行号后输入 textfile2 这个文档里: +`cat -n textfile1 > textfile2` + +把 textfile1 和 textfile2 的文档内容加上行号(空白行不加)之后将内容附加到 textfile3 文档里: +`cat -b textfile1 textfile2 >> textfile3` + +清空 /etc/test.txt 文档内容: +`cat /dev/null > /etc/test.txt` + +cat 也可以用来制作镜像文件。例如要制作软盘的镜像文件,将软盘放好后输入: +`cat /dev/fd0 > OUTFILE` + +相反的,如果想把 image file 写到软盘,输入: +`cat IMG_FILE > /dev/fd0` +## more +类似 `cat` ,不过会以一页一页的形式显示,更方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能(与 vi 相似),使用中的说明文件,请按 h +`more [-dlfpcsu] [-num] [+/pattern] [+linenum] [fileNames..] ` +### 参数 +-num 一次显示的行数 +-d 提示使用者,在画面下方显示 [Press space to continue, 'q' to quit.] ,如果使用者按错键,则会显示 [Press 'h' for instructions.] 而不是 '哔' 声 +-l 取消遇见特殊字元 ^L(送纸字元)时会暂停的功能 +-f 计算行数时,以实际上的行数,而非自动换行过后的行数(有些单行字数太长的会被扩展为两行或两行以上) +-p 不以卷动的方式显示每一页,而是先清除萤幕后再显示内容 +-c 跟 -p 相似,不同的是先显示内容再清除其他旧资料 +-s 当遇到有连续两行以上的空白行,就代换为一行的空白行 +-u 不显示下引号 (根据环境变数 TERM 指定的 terminal 而有所不同) ++/pattern 在每个文档显示前搜寻该字串(pattern),然后从该字串之后开始显示 ++num 从第 num 行开始显示 +fileNames 欲显示内容的文档,可为复数个数 +### 实例 +``` +逐页显示 testfile 文档内容,如有连续两行以上空白行则以一行空白行显示。 +more -s testfile + +从第 20 行开始显示 testfile 之文档内容。 +more +20 testfile +``` +##tac +从最后一行开始显示内容,并将所有内容输出 +##head:只显示前几行 + +##tail +查看文件的内容,有一个常用的参数 -f 常用于查阅正在改变的日志文件 +`tail -10 someFile` +查看文件后 10 行内容 +`head -10 someFile` +查看文件前 10 行内容 +`tail -f someFile` +用于调试,实时查看文件内容,会把 filename 文件里的最尾部的内容显示在屏幕上,并且不断刷新,只要 filename 更新就可以看到最新的文件内容 +`tail [参数] [文件] ` +``` +-f 循环读取 +-q 不显示处理信息 +-v 显示详细的处理信息 +-c<数目> 显示的字节数 +-n<行数> 显示行数 +--pid=PID 与-f合用,表示在进程ID,PID死掉之后结束. +-q, --quiet, --silent 从不输出给出文件名的首部 +-s, --sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒 +``` +###实例 +``` +要显示 notes.log 文件最后 10 行 +tail notes.log + +要跟踪名为 notes.log 的文件增长情况 +tail -f notes.log +此命令显示 notes.log 文件最后 10 行 +当将某些行添加至 notes.log 文件时,tail 命令会继续显示这些行 +显示一直继续,直到您按下(Ctrl-C)组合键停止显示。 + +显示文件 notes.log 的内容,从第 20 行至文件末尾: +tail +20 notes.log + +显示文件 notes.log 的最后 10 个字符: +tail -c 10 notes.log +``` +##nl:和 cat 一样,只是 nl 要显示行号 +## make +编译 +-j :指定作业数。 + + +##man rm ( rm --help ) +查看帮助 +# 2 磁盘管理 +## pwd +显示工作目录 +执行pwd指令可立刻得知您目前所在的工作目录的绝对路径名称。 + +## cd +切换当前工作目录至 dirName +若目录名称省略,则变换 home 目录 + +## mkdir +建立名称为 dirName 子目录 +* -p 确保目录名称存在,不存在的就建一个 + + +# 3 文档编辑 +## grep +用于查找文件里符合条件的字符串。 +grep指令用于查找内容包含指定的范本样式的文件,如果发现某文件的内容符合所指定的范本样式,预设grep指令会把含有范本样式的那一列显示出来。若不指定任何文件名称,或是所给予的文件名为"-",则grep指令会从标准输入设备读取数据。 +-r或--recursive 此参数的效果和指定"-d recurse"参数相同。 +-v或--revert-match 反转 + +反向查找。前面各个例子是查找并打印出符合条件的行,通过"-v"参数可以打印出不符合条件行的内容。 +查找文件名中包含 test 的文件中不包含test 的行,此时,使用的命令为: +`grep -v test *test*` +# 4 系统管理 +## groupadd +创建一个新的工作组,新工作组的信息将被添加到系统文件中 +- 语法 +groupadd(选项)(参数) +``` +g:指定新建工作组的[id](http://man.linuxde.net/id "id命令"); +-r:创建系统工作组,系统工作组的组ID小于500; +-K:覆盖配置文件“/ect/[login](http://man.linuxde.net/login "login命令").defs”; +-o:允许添加组ID号不唯一的工作组。 +``` +- 参数 +组名:指定新建工作组的组名 +- 实例 +建立一个新组,并设置组ID加入系统 +``` +groupadd -g 344 linuxde +``` +此时在/etc/passwd文件中产生一个组ID(GID)是344的项目 + + +## ps +显示当前进程 (process) 的状态 +- -A 显示进程信息 +![](https://upload-images.jianshu.io/upload_images/4685968-512aff1e593d1599.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- -u user 显示指定用户信息 +![](https://upload-images.jianshu.io/upload_images/4685968-7c7a71f3b9dfb4b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- -ef 显示所有命令,连带命令行 +![](https://upload-images.jianshu.io/upload_images/4685968-e3ae42585f4e4276.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- pstree |grep java +查看进程树 +![](https://upload-images.jianshu.io/upload_images/4685968-bd8146a54c354da0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +## rpm -aq|grep php +查看安装介质 + + +## ls +显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录)。 +` ls [-alrtAFR] [name...]` +``` +-a 显示所有文件及目录 (ls内定将文件名或目录名称开头为"."的视为隐藏档,不会列出) +-h 用"K","M","G"来显示文件和目录的大小。 +-l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出 = ll +-r 将文件以相反次序显示(原定依英文字母次序) +-t 将文件依建立时间之先后次序列出 +-A 同 -a ,但不列出 "." (目前目录) 及 ".." (父目录) +-F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/" +-R 若目录下有文件,则以下之文件亦皆依序列出 +``` +列出根目录(\)下的所有目录: +``` +# ls / +bin dev lib media net root srv upload www +boot etc lib64 misc opt sbin sys usr +home lost+found mnt proc selinux tmp var +``` +列出目前工作目录下所有名称是 s 开头的文件,越新的排越后面 : +`ls -ltr s*` +将 /bin 目录以下所有目录及文件详细资料列出 : +`ls -lR /bin` +列出目前工作目录下所有文件及目录;目录于名称后加 "/", 可执行档于名称后加 "*" : +`ls -AF` + + +find / -name libNativeMethod.so +等同 ll |grep someFile + +#grep someText * +在当前目录所有文本中查找 + +#ifconfig +IP 地址配置,可以使用 setup 命令启动字符界面来配置 + +#env +环境配置,相当 window 下 set + +env |grep PATH +查看环境变量 + +#export +相当于 set classpath + +#echo +输出变量名 + +#netstat -npl +查看端口 + +#lsof -i :22 +查看端口进程 + +#cp from to +拷贝文件 + +cp -fr ./j2sdk1.4.2_04 /usr/java +拷贝目录 + + + +# mv +用来为文件或目录改名、或将文件或目录移入其它位置。 +## 语法 +`mv [options] source dest` +`mv [options] source... directory` +参数说明: +- -i: 若指定目录已有同名文件,则先询问是否覆盖旧文件; +- -f: 在mv操作要覆盖某已有的目标文件时不给任何指示; +## mv参数设置与运行结果 +mv 文件名 文件名 :将源文件名改为目标文件名 +mv 文件名 目录名 :将文件移动到目标目录 +mv 目录名 目录名 :目标目录已存在,将源目录移动到目标目录;目标目录不存在则改名 +mv 目录名 文件名 :出错 +## 实例 +将文件 aaa 更名为 bbb : +`mv aaa bbb` +将info目录放入logs目录中。注意,如果logs目录不存在,则该命令将info改名为logs。 +`mv info/ logs ` +再如将/usr/student下的所有文件和目录移到当前目录下,命令行为: +`$ mv /usr/student/* . ` + +#rm -r +递归删除, -f 表示 force + +>somefile +清空文件内容 + +which java +查看 java 进程对应的目录 + +#who +显示当前用户 + +#users +显示当前会话 + +#zip -r filename.zip filesdir +某个文件夹打 zip 包 + +#unzip somefile.zip +解压 zip 文档到当前目录 + +gunzip somefile.cpio.gz +解压 .gz + +cpio -idmv < somefile.cpio +CPIO 操作 + +ps auxwww|sort -n -r -k 5|head -5 +按资源占用情况来排序,第一个 5 表示第几列,第二个 5 表示前几位 + +hostname -i +显示本机机器名,添加 i ,显示 etc/hosts 对应 ip 地址 + +rpm -ivh some.rpm +安装软件 + +rpm -Uvh some.rpm +更新软件 + +rpm -qa |grep somesoftName +是否已安装某软件 +# 5 备份压缩 +## tar +备份文件 +是用来建立,还原备份文件的工具程序,它可以加入,解开备份文件内的文件。 +##参数: +-f<备份文件>或--file=<备份文件> 指定备份文件。 +-v或--verbose 显示指令执行过程。 +-x或--extract或--get 从备份文件中还原文件。 +-z或--gzip或--ungzip 通过gzip指令处理备份文件。 +###实例 +压缩文件 非打包 +```java +# touch a.c +# tar -czvf test.tar.gz a.c //压缩 a.c文件为test.tar.gz +a.c +``` +列出压缩文件内容 +```java +# tar -tzvf test.tar.gz +-rw-r--r-- root/root 0 2010-05-24 16:51:59 a.c +``` +解压文件 +tar -xzvf test.tar.gz a.c +打包 +tar –cvzf somefile.tar.gz fileDir + + +#shutdown -i6 -y 0 +立即重启服务器 + +#reboot +立即重启服务器,相当于 shutdow –r now + +#halt + +立即关机, shutdown -h + +shutdonw -r 23:30 + +shutdown -r +15 + +shutdonw -r +30 + +定时重启 + +gdmsetup + +启动系统配置管理界面,需要在图形界面执行 + +setup + +启动文字配置管理界面 + +vi /etc/sysconfig/network + +修改机器名 , 然后要重启机器或者 service network restart + +#locale +显示系统语言 + +export LANG=zh_CN.GBK + +设定系统语言,解决 consol 中文乱码 + +ln -s src_full_file the_link_name + +创建软链接 + +last + +倒序查看已登陆用户历史 + +history + +查看历史命令 + + + +date -s 10/09/2009 + +修改日期 + +date -s 13:24:00 + +修改时间,直接 date 显示时间 + +df -k + +查看文件磁盘空间 + +df -v + +查看文件空间 + +du + +查看磁盘空间使用情况 + +#free + +查看内存使用情况 +# 6 系统管理 +## top +实时显示 process 的动态 +d : 改变显示的更新速度,或是在交谈式指令列( interactive command)按 s +q : 没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高的优先序执行 +c : 切换显示模式,共有两种模式,一是只显示执行档的名称,另一种是显示完整的路径与名称S : 累积模式,会将己完成或消失的子行程 ( dead child process ) 的 CPU time 累积起来 +s : 安全模式,将交谈式指令取消, 避免潜在的危机 +i : 不显示任何闲置 (idle) 或无用 (zombie) 的行程 +n : 更新的次数,完成后将会退出 top +b : 批次档模式,搭配 "n" 参数一起使用,可以用来将 top 的结果输出到档案内 +显示进程信息 + +# top +显示完整命令 +# top -c +以批处理模式显示程序信息 + +# top -b +以累积模式显示程序信息 + +# top -S +设置信息更新次数 + +top -n 2 + +//表示更新两次后终止更新显示 +设置信息更新时间 + +# top -d 3 + +//表示更新周期为3秒 +显示指定的进程信息 + +# top -p 139 + +//显示进程号为139的进程信息,CPU、内存占用率等 +显示更新十次后退出 + +top -n 10 +使用者将不能利用交谈式指令来对行程下命令 + +top -s +将更新显示二次的结果输入到名称为 top.log 的档案里 + +top -n 2 -b < top.log +vmstat 5 10 + +没 5 秒刷新一次,刷新 10 次; time 、 timex 、 uptime 、 iostat 、 sar + +cat /proc/cpuinfo|grep processor|wc – l + +获取 cpu 个数 +## service +service +打印指定服务的命令行使用帮助。 +service start +启动指定的系统服务 +service stop +停止指定的系统服务 +service restart +重新启动指定的系统服务,即先停止(stop),然后再启动(start)。 +chkconfig --list +查看系统服务列表,以及每个服务的运行级别。 +chkconfig on +设置指定服务开机时自动启动。 +chkconfig off +设置指定服务开机时不自动启动。 + +ntsysv + +以全屏幕文本界面设置服务开机时是否自动启动。 +service mysqld start + +启动 mysql 服务,其他如 + +service mysqld stop + +停止 mysql 服务 + +serice mysqld status + +显示 mysql 服务状态 + +service –status-al +查看已有服务 + +# Systemd +设计目标是,为系统的启动和管理提供一套完整的解决方案。 +根据 Linux 惯例,字母d是守护进程(daemon)的缩写。 Systemd 这个名字的含义,就是它要守护整个系统。 +Systemd 并不是一个命令,而是一组命令,涉及到系统管理的方方面面。 +## systemctl +是 Systemd 的主命令,用于管理系统。 +Systemd 可以管理所有系统资源。不同的资源统称为 Unit(单位)。 +Unit 一共分成12种。 + +Service unit:系统服务 +Target unit:多个 Unit 构成的一个组 +Device Unit:硬件设备 +Mount Unit:文件系统的挂载点 +Automount Unit:自动挂载点 +Path Unit:文件或路径 +Scope Unit:不是由 Systemd 启动的外部进程 +Slice Unit:进程组 +Snapshot Unit:Systemd 快照,可以切回某个快照 +Socket Unit:进程间通信的 socket +Swap Unit:swap 文件 +Timer Unit:定时器 + +systemctl list-units命令可以查看当前系统的所有 Unit 。 + +netstat -nap | grep port + +# yum( Yellow dog Updater, Modified) +是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器。 + +基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软体包,无须繁琐地一次次下载、安装。 + +yum提供了查找、安装、删除某一个、一组甚至全部软件包的命令,而且命令简洁而又好记。 +##语法 +```java +yum [options] [command] [package ...] +``` +- options:可选,选项包括 + - -h(帮助) + - -y当安装过程提示选择全部为"yes" + - -q(不显示安装的过程)等等。 +- command:要进行的操作。 +- package操作的对象。 +##常用命令 +1.列出所有可更新的软件清单命令:yum check-update +2.更新所有软件命令:yum update +3.仅安装指定的软件命令:yum install +4.仅更新指定的软件命令:yum update +5.列出所有可安裝的软件清单命令:yum list +6.删除软件包命令:yum remove +7.查找软件包 命令:yum search +8.清除缓存命令: +yum clean packages: 清除缓存目录下的软件包 +yum clean headers: 清除缓存目录下的 headers +yum clean oldheaders: 清除缓存目录下旧的 headers +yum clean, yum clean all (= yum clean packages; yum clean oldheaders) :清除缓存目录下的软件包及旧的headers + +#rpm (redhat package manager) +用于管理套件。 +原本是 Red Hat Linux 发行版专门用来管理 Linux 各项套件的程序,由于它遵循 GPL 规则且功能强大方便,因而广受欢迎。逐渐受到其他发行版的采用。RPM 套件管理方式的出现,让 Linux 易于安装,升级,间接提升了 Linux 的适用度。 +##参数说明: +-a  查询所有套件 +-q  使用询问模式,当遇到任何问题时,rpm指令会先询问用户 +## chkconfig +检查,设置系统的各种服务。 +查询操作系统在每一个执行等级中会执行哪些系统服务 +```java +chkconfig [--add][--del][--list][系统服务] 或 chkconfig [--level <等级代号>][系统服务][on/off/reset] +``` +##参数: +```java +--add  增加所指定的系统服务,让chkconfig指令得以管理它,并同时在系统启动的叙述文件内增加相关数据。 +--del  删除所指定的系统服务,不再由chkconfig指令管理,并同时在系统启动的叙述文件内删除相关数据。 +--level<等级代号>  指定读系统服务要在哪一个执行等级中开启或关毕。 +``` +```java +列出chkconfig所知道的所有命令。 +# chkconfig -list +``` +```java +开启服务。 +# chkconfig telnet/mysqld on //开启Telnet/mysql服务 +# chkconfig -list //列出chkconfig所知道的所有的服务的情况 +``` +```java +关闭服务 +# chkconfig telnet off //关闭Telnet服务 +# chkconfig -list //列出chkconfig所知道的所有的服务的情况 +``` +# Ctrl命令 +##Ctrl C + kill foreground process +发送 SIGINT 信号给前台进程组中的所有进程,强制终止程序的执行 +##Ctrl Z + suspend foreground process + 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程,而并非结束进程,用户可以使用使用fg/bg操作恢复执行前台或后台的进程。fg命令在前台恢复执行被挂起的进 程,此时可以使用ctrl-z再次挂起该进程,bg命令在后台恢复执行被挂起的进程,而此时将无法使用ctrl-z 再次挂起该进程;一个比较常用的功能: +正在使用vi编辑一个文件时,需要执行shell命令查询一些需要的信息,可以使用ctrl-z挂起vi,等执行 完shell命令后再使用fg恢复vi继续编辑你的文件(当然,也可以在vi中使用!command方式执行shell命令, 但是没有该方法方便)。 + +##ctrl-d: +Terminate input, or exit shell +一个特殊的二进制值,表示 EOF,作用相当于在终端中输入exit后回车 +##ctrl-/ +发送 SIGQUIT 信号给前台进程组中的所有进程,终止前台进程并生成 core 文件 + +##ctrl-s +中断控制台输出 + +##ctrl-q +恢复控制台输出 + +##ctrl-l +清屏 + + + +其实,控制字符都是可以通过stty命令更改的,可在终端中输入命令"stty -a"查看终端配置 +![](http://upload-images.jianshu.io/upload_images/4685968-e43eb0501196bbea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +## kill +kill PID +杀掉某进程 +kill -9 PID +此命令将信号 9(SIGKILL 信号)发送到有效用户拥有的所有进程,即使是那些在其他工作站上启动以及属于其他进程组的进程也是如此。如果一个您请求的列表正被打印,它也被停止。 +##source +也称为“点命令”,也就是一个点符号(.) +常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。 +```shell +source filename 或 . filename +``` +source命令除了上述的用途之外,还有一个另外一个用途。在对编译系统核心时常常需要输入一长串的命令,如: +```shell +make mrproper +make menuconfig +make dep +make clean +make bzImage +………… +``` +如果把这些命令做成一个文件,让它自动顺序执行,对于需要多次反复编译系统核心的用户来说会很方便,而用source命令就可以做到这一点,它的作用就是把一个文件的内容当成shell来执行, +先在linux的源代码目录下(如/usr/src/linux-2.4.20)建立一个文件,如make_command,在其中输入一下内容: +```shell +make mrproper && +make menuconfig && +make dep && +make clean && +make bzImage && +make modules && +make modules_install && +cp arch/i386/boot/bzImage /boot/vmlinuz_new && +cp System.map /boot && +vi /etc/lilo.conf && +lilo -v +``` +文件建立好之后,每次编译核心的时候,只需要在/usr/src/linux-2.4.20下输入: +`source make_command` +即可,如果你用的不是lilo来引导系统,可以把最后两行去掉,配置自己的引导程序来引导内核。 +顺便补充一点,&&命令表示顺序执行由它连接的命令,但是只有它之前的命令成功执行完成了之后才可以继续执行它后面的命令。 +##问题:linux下有些工具安装之后,除了要修改root下的.bashfile(也就是添加个环境变量) ,还要修改etc/profile 下的环境变量 , 两个profile是干什么用的?区别? + +解答: +- /etc/profile :这个文件是每个用户登录时都会运行的环境变量设置,属于系统级别的环境变量,设置在里 面的东西对所有用户适用 +- .bashfile 是单用户登录时比如root会运行的,只对当前用户适用,而且只有在你使用的也是bash作为shell时才行. rpm是red hat,fedora,centos这几个发行版使用的安装包,和其它tar.gz的区别是有个文件头,多了一些信息。 rpm包多数是二进制文件,可以直接运行的,但tar.gz包很多是源代码,要编译后才能运行。 二进制文件和windows下的exe文件一个意思,可以直接运行。 +##whereis +只能用于程序名的搜索,而且只搜索二进制文件(参数-b)、man说明文件(参数-m)和源代码文件(参数-s)。如果省略参数,则返回所有信息。 + +##fuser +查询文件、目录、socket端口和文件系统的使用进程 +###1.查询文件和目录使用者 +fuser最基本的用法是查询某个文件或目录被哪个进程使用: +```java +# fuser -v ./ + USER PID ACCESS COMMAND +./: dailidong 17108 ..c.. bash + root 25559 ..c.. sudo + root 26772 ..c.. bash +``` + ###2.查询端口使用者 +```java +# fuser -vn tcp 3306 + + USER PID ACCESS COMMAND +3306/tcp: mysql 2535 F.... mysqld +``` +#在 vim 命令模式 +:noh 取消/ sth 的搜索结果高亮特效 +## ip +查看本机的 IP 地址 +``` +ip addr show +``` +![结果](https://upload-images.jianshu.io/upload_images/4685968-3e2b6e161f3fd27c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 软件包管理器 +apt-get是Debian、Ubuntu、Linux Mint、elementary OS等Linux发行版的默认软件包管理器 +apt-get purge packagename 等同于 apt-get remove packagename --purge +## apt-get remove +只删除软件包,不删除配置文件 +##apt-get purge +删除软件包并删除配置文件 + +配置文件只包括/etc目录中的软件服务使用的配置信息,不包括home目录中的 diff --git "a/Java/Java\345\274\202\345\270\270\344\271\213IllegalMonitorStateException.md" "b/Java/Java\345\274\202\345\270\270\344\271\213IllegalMonitorStateException.md" new file mode 100644 index 0000000000..c96d2fe7e2 --- /dev/null +++ "b/Java/Java\345\274\202\345\270\270\344\271\213IllegalMonitorStateException.md" @@ -0,0 +1,48 @@ +# JavaDoc +Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor. +其实意思就是说,也就是当前的线程不是此对象监视器的所有者。也就是要在当前线程锁定对象,才能用锁定的对象此行这些方法,需要用到synchronized ,锁定什么对象就用什么对象来执行  notify(), notifyAll(),wait(), wait(long), wait(long, int)操作,否则就会报IllegalMonitorStateException + +A thread becomes the owner of the object's monitor in one of three ways: +1\. By executing a synchronized instance method of that object. +2\. By executing the body of a synchronized statement that synchronizes on the object. +3\. For objects of type Class, by executing a synchronized static method of that class. +通过以下三种方法之一,线程可以成为此对象监视器的所有者: + +* 执行此对象的同步 (Sychronized) 实例方法 +* 执行在此对象上进行同步的 synchronized 语句的正文 +* 对于 Class 类型的对象,执行该类的同步静态方法 + +也就是在说,就是需要在调用wait()或者notify()之前,必须使用synchronized语义绑定住被wait/notify的对象。 + +# 解决方法: +通过实现加锁的方式实现线程同步时产生的并发问题 +## 1 锁定方法所属的实例对象 +``` +public synchronized void method(){ + //然后就可以调用:this.notify()... + //或者直接调用notify()... +} +``` +## 2 锁定方法所属的实例的Class +``` +public Class Test{ + public static synchronized void method(){ + //然后调用:Test.class.notify()... + } +} +``` +## 3 锁定其他对象 +``` +public Class Test{ +public Object lock = new Object(); + public static void method(){ + synchronized (lock) { + //需要调用 lock.notify(); + } + } +} +``` +# 总结 +线程操作的wait()、notify()、notifyAll()只能在同步控制方法或同步控制块内调用 +如果在非同步控制方法或控制块里调用,程序能通过编译,但运行的时候,将得到 IllegalMonitorStateException 异常,并伴随着一些含糊信息,比如 ‘当前线程不是拥有者’。 +其实异常的含义是 调用wait()、notify()、notifyAll()的任务在调用这些方法前必须 ‘拥有’(获取)对象的锁。” diff --git "a/Java/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" "b/Java/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" new file mode 100644 index 0000000000..d0b58f6995 --- /dev/null +++ "b/Java/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" @@ -0,0 +1,91 @@ +##1.1 jps +类似Linux的ps,但是jps只用于列出Java的进程 +可以方便查看Java进程的启动类,传入参数和JVM参数等 +直接运行,不加参数,列出Java程序的进程ID以及Main函数等名称 +![jps命令本质也是Java程序](https://upload-images.jianshu.io/upload_images/4685968-9c2189ded9cbd53e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![-m 输出传递给Java进程的参数](https://upload-images.jianshu.io/upload_images/4685968-15d03dbd5f802109.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![-l 输出主函数的完整路径](https://upload-images.jianshu.io/upload_images/4685968-bd8f57565d346543.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![-q 只输出进程ID](https://upload-images.jianshu.io/upload_images/4685968-e388ace92f303daf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![-v 显示传递给jvm的参数](https://upload-images.jianshu.io/upload_images/4685968-9a3bab93f94ce6c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +##1.2 jstat +用于观察Java应用程序运行时信息的工具,详细查看堆使用情况以及GC情况 +![jstat -options](https://upload-images.jianshu.io/upload_images/4685968-fbc855e949719c77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 1.2.1 jstat -class pid +显示加载class的数量及所占空间等信息 +![-compiler -t:显示JIT编译的信息](https://upload-images.jianshu.io/upload_images/4685968-723f9f0823227204.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 1.2.2 -gc pid +可以显示gc的信息,查看gc的次数及时间 +![](https://upload-images.jianshu.io/upload_images/4685968-6b81e96c9ce4a122.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 1.2.3 -gccapacity +比-gc多了各个代的最大值和最小值 +![jstat -gccapacity 3661](https://upload-images.jianshu.io/upload_images/4685968-8f0c0568a833c939.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-b5dec1ef483c3985.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### -gccause +最近一次GC统计和原因 +![](https://upload-images.jianshu.io/upload_images/4685968-19e7194758e4b938.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- LGCC:上次GC原因 +- GCC:当前GC原因 +![ -gcnew pid:new对象的信息。](https://upload-images.jianshu.io/upload_images/4685968-30740d4582274fde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-e8e917f5b46b831c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### jstat -gcnewcapacity pid:new对象的信息及其占用量 +![](https://upload-images.jianshu.io/upload_images/4685968-5d42263a5cfb4520.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +![-gcold 显示老年代和永久代信息](https://upload-images.jianshu.io/upload_images/4685968-27925ae2707ab9cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-ddc55146d27ff8d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![-gcoldcapacity展现老年代的容量信息](https://upload-images.jianshu.io/upload_images/4685968-7468e008a50dc204.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-049dcf0ac4d634b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### -gcutil +显示GC回收相关信息 +![](https://upload-images.jianshu.io/upload_images/4685968-3abed42f97b9ffc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f906ce1ad06007a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### -printcompilation +当前VM执行的信息 +![jstat -printcompilation 3661](https://upload-images.jianshu.io/upload_images/4685968-c13976c3f1895bab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### 还可以同时加两个数 +![输出进程4798的ClassLoader信息,每1秒统计一次,共输出2次](https://upload-images.jianshu.io/upload_images/4685968-0c2724b93209e8f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +##1.3 jinfo +`jinfo